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 {0, 0, false}, // pt_dead
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_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_MOD, 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 // range of colors to choose from in hex RRGGBB (like HTML color tags),
73 // randomly interpolated at spawn
74 unsigned int color[2];
75 // a random texture is chosen in this range (note the second value is one
76 // past the last choosable, so for example 8,16 chooses any from 8 up and
78 // if start and end of the range are the same, no randomization is done
80 // range of size values randomly chosen when spawning, plus size increase over time
82 // range of alpha values randomly chosen when spawning, plus alpha fade
84 // how long the particle should live (note it is also removed if alpha drops to 0)
86 // how much gravity affects this particle (negative makes it fly up!)
88 // how much bounce the particle has when it hits a surface
89 // if negative the particle is removed on impact
91 // if in air this friction is applied
92 // if negative the particle accelerates
94 // if in liquid (water/slime/lava) this friction is applied
95 // if negative the particle accelerates
97 // these offsets are added to the values given to particleeffect(), and
98 // then an ellipsoid-shaped jitter is added as defined by these
99 // (they are the 3 radii)
101 // stretch velocity factor (used for sparks)
102 float originoffset[3];
103 float velocityoffset[3];
104 float originjitter[3];
105 float velocityjitter[3];
106 float velocitymultiplier;
107 // an effect can also spawn a dlight
108 float lightradiusstart;
109 float lightradiusfade;
112 qboolean lightshadow;
115 particleeffectinfo_t;
117 #define MAX_PARTICLEEFFECTNAME 256
118 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
120 #define MAX_PARTICLEEFFECTINFO 4096
122 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
124 static int particlepalette[256];
126 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
127 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
128 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
129 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
130 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
131 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
132 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
133 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
134 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
135 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
136 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
137 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
138 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
139 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
140 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
141 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
142 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
143 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
144 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
145 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
146 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
147 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
148 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
149 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
150 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
151 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
152 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
153 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
154 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
155 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
156 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
157 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
160 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
161 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
162 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
164 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
166 // texture numbers in particle font
167 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
168 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
169 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
170 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
171 static const int tex_rainsplash = 32;
172 static const int tex_particle = 63;
173 static const int tex_bubble = 62;
174 static const int tex_raindrop = 61;
175 static const int tex_beam = 60;
177 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
178 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
179 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
180 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
181 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
182 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
183 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
184 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
185 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
186 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
187 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
188 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
189 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
190 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
191 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
192 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
193 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
194 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
195 cvar_t cl_particles_novis = {CVAR_SAVE, "cl_particles_novis", "0", "turn off PVS culling of particles"};
196 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
197 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
198 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
201 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
207 particleeffectinfo_t *info = NULL;
208 const char *text = textstart;
210 effectinfoindex = -1;
211 for (linenumber = 1;;linenumber++)
214 for (arrayindex = 0;arrayindex < 16;arrayindex++)
215 argv[arrayindex][0] = 0;
218 if (!COM_ParseToken_Simple(&text, true, false))
220 if (!strcmp(com_token, "\n"))
224 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
230 #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;}
231 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
232 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
233 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
234 #define readfloat(var) checkparms(2);var = atof(argv[1])
235 if (!strcmp(argv[0], "effect"))
240 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
242 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
245 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
247 if (particleeffectname[effectnameindex][0])
249 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
254 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
258 // if we run out of names, abort
259 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
261 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
264 info = particleeffectinfo + effectinfoindex;
265 info->effectnameindex = effectnameindex;
266 info->particletype = pt_alphastatic;
267 info->tex[0] = tex_particle;
268 info->tex[1] = tex_particle;
269 info->color[0] = 0xFFFFFF;
270 info->color[1] = 0xFFFFFF;
274 info->alpha[1] = 256;
275 info->alpha[2] = 256;
276 info->time[0] = 9999;
277 info->time[1] = 9999;
278 VectorSet(info->lightcolor, 1, 1, 1);
279 info->lightshadow = true;
280 info->lighttime = 9999;
281 info->stretchfactor = 1;
283 else if (info == NULL)
285 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
288 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
289 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
290 else if (!strcmp(argv[0], "type"))
293 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
294 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
295 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
296 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
297 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
298 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
299 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
300 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
301 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
302 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
303 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
304 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
305 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
307 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
308 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
309 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
310 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
311 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
312 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
313 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
314 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
315 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
316 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
317 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
318 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
319 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
320 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
321 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
322 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
323 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
324 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
325 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
326 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
327 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
328 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
329 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
330 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
331 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
333 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
342 int CL_ParticleEffectIndexForName(const char *name)
345 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
346 if (!strcmp(particleeffectname[i], name))
351 const char *CL_ParticleEffectNameForIndex(int i)
353 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
355 return particleeffectname[i];
358 // MUST match effectnameindex_t in client.h
359 static const char *standardeffectnames[EFFECT_TOTAL] =
383 "TE_TEI_BIGEXPLOSION",
399 void CL_Particles_LoadEffectInfo(void)
402 unsigned char *filedata;
403 fs_offset_t filesize;
404 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
405 memset(particleeffectname, 0, sizeof(particleeffectname));
406 for (i = 0;i < EFFECT_TOTAL;i++)
407 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
408 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
411 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
421 void CL_ReadPointFile_f (void);
422 void CL_Particles_Init (void)
424 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)");
425 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
427 Cvar_RegisterVariable (&cl_particles);
428 Cvar_RegisterVariable (&cl_particles_quality);
429 Cvar_RegisterVariable (&cl_particles_alpha);
430 Cvar_RegisterVariable (&cl_particles_size);
431 Cvar_RegisterVariable (&cl_particles_quake);
432 Cvar_RegisterVariable (&cl_particles_blood);
433 Cvar_RegisterVariable (&cl_particles_blood_alpha);
434 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
435 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
436 Cvar_RegisterVariable (&cl_particles_explosions_shell);
437 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
438 Cvar_RegisterVariable (&cl_particles_rain);
439 Cvar_RegisterVariable (&cl_particles_snow);
440 Cvar_RegisterVariable (&cl_particles_smoke);
441 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
442 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
443 Cvar_RegisterVariable (&cl_particles_sparks);
444 Cvar_RegisterVariable (&cl_particles_bubbles);
445 Cvar_RegisterVariable (&cl_particles_novis);
446 Cvar_RegisterVariable (&cl_decals);
447 Cvar_RegisterVariable (&cl_decals_time);
448 Cvar_RegisterVariable (&cl_decals_fadetime);
451 void CL_Particles_Shutdown (void)
455 // list of all 26 parameters:
456 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
457 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
458 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
459 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
460 // palpha - opacity of particle as 0-255 (can be more than 255)
461 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
462 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
463 // pgravity - how much effect gravity has on the particle (0-1)
464 // 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
465 // px,py,pz - starting origin of particle
466 // pvx,pvy,pvz - starting velocity of particle
467 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
468 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)
473 if (!cl_particles.integer)
475 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
476 if (cl.free_particle >= cl.max_particles)
479 lifetime = palpha / min(1, palphafade);
480 part = &cl.particles[cl.free_particle++];
481 if (cl.num_particles < cl.free_particle)
482 cl.num_particles = cl.free_particle;
483 memset(part, 0, sizeof(*part));
484 part->typeindex = ptypeindex;
485 l2 = (int)lhrandom(0.5, 256.5);
487 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
488 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
489 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
492 part->sizeincrease = psizeincrease;
493 part->alpha = palpha;
494 part->alphafade = palphafade;
495 part->gravity = pgravity;
496 part->bounce = pbounce;
497 part->stretch = stretch;
499 part->org[0] = px + originjitter * v[0];
500 part->org[1] = py + originjitter * v[1];
501 part->org[2] = pz + originjitter * v[2];
502 part->vel[0] = pvx + velocityjitter * v[0];
503 part->vel[1] = pvy + velocityjitter * v[1];
504 part->vel[2] = pvz + velocityjitter * v[2];
506 part->airfriction = pairfriction;
507 part->liquidfriction = pliquidfriction;
508 part->die = cl.time + lifetime;
509 part->delayedcollisions = 0;
510 part->qualityreduction = pqualityreduction;
511 if (part->typeindex == pt_blood)
512 part->gravity += 1; // FIXME: this is a legacy hack, effectinfo.txt doesn't have gravity on blood (nor do the particle calls in the engine)
513 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
514 if (part->typeindex == pt_rain)
518 float lifetime = part->die - cl.time;
521 // turn raindrop into simple spark and create delayedspawn splash effect
522 part->typeindex = pt_spark;
524 VectorMA(part->org, lifetime, part->vel, endvec);
525 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
526 part->die = cl.time + lifetime * trace.fraction;
527 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);
530 part2->delayedspawn = part->die;
531 part2->die += part->die - cl.time;
532 for (i = rand() & 7;i < 10;i++)
534 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);
537 part2->delayedspawn = part->die;
538 part2->die += part->die - cl.time;
543 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
545 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
548 VectorMA(part->org, lifetime, part->vel, endvec);
549 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
550 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
555 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
559 if (!cl_decals.integer)
561 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
562 if (cl.free_decal >= cl.max_decals)
564 decal = &cl.decals[cl.free_decal++];
565 if (cl.num_decals < cl.free_decal)
566 cl.num_decals = cl.free_decal;
567 memset(decal, 0, sizeof(*decal));
568 decal->typeindex = pt_decal;
569 decal->texnum = texnum;
570 VectorAdd(org, normal, decal->org);
571 VectorCopy(normal, decal->normal);
573 decal->alpha = alpha;
574 decal->time2 = cl.time;
575 l2 = (int)lhrandom(0.5, 256.5);
577 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
578 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
579 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
580 decal->owner = hitent;
583 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
584 decal->ownermodel = cl.entities[decal->owner].render.model;
585 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
586 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
590 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
593 float bestfrac, bestorg[3], bestnormal[3];
595 int besthitent = 0, hitent;
598 for (i = 0;i < 32;i++)
601 VectorMA(org, maxdist, org2, org2);
602 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
603 // take the closest trace result that doesn't end up hitting a NOMARKS
604 // surface (sky for example)
605 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
607 bestfrac = trace.fraction;
609 VectorCopy(trace.endpos, bestorg);
610 VectorCopy(trace.plane.normal, bestnormal);
614 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
617 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
618 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
619 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)
622 matrix4x4_t tempmatrix;
623 VectorLerp(originmins, 0.5, originmaxs, center);
624 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
625 if (effectnameindex == EFFECT_SVC_PARTICLE)
627 if (cl_particles.integer)
629 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
631 CL_ParticleExplosion(center);
632 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
633 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
636 count *= cl_particles_quality.value;
637 for (;count > 0;count--)
639 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
640 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);
645 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
646 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
647 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
648 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
649 else if (effectnameindex == EFFECT_TE_SPIKE)
651 if (cl_particles_bulletimpacts.integer)
653 if (cl_particles_quake.integer)
655 if (cl_particles_smoke.integer)
656 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
660 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
661 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
662 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);
666 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
667 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
669 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
671 if (cl_particles_bulletimpacts.integer)
673 if (cl_particles_quake.integer)
675 if (cl_particles_smoke.integer)
676 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
680 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
681 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
682 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);
686 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
687 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
688 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);
690 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
692 if (cl_particles_bulletimpacts.integer)
694 if (cl_particles_quake.integer)
696 if (cl_particles_smoke.integer)
697 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
701 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
702 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
703 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);
707 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
708 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
710 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
712 if (cl_particles_bulletimpacts.integer)
714 if (cl_particles_quake.integer)
716 if (cl_particles_smoke.integer)
717 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
721 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
722 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
723 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);
727 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
728 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
729 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);
731 else if (effectnameindex == EFFECT_TE_BLOOD)
733 if (!cl_particles_blood.integer)
735 if (cl_particles_quake.integer)
736 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
739 static double bloodaccumulator = 0;
740 //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);
741 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
742 for (;bloodaccumulator > 0;bloodaccumulator--)
743 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -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);
746 else if (effectnameindex == EFFECT_TE_SPARK)
747 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
748 else if (effectnameindex == EFFECT_TE_PLASMABURN)
750 // plasma scorch mark
751 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
752 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
753 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
755 else if (effectnameindex == EFFECT_TE_GUNSHOT)
757 if (cl_particles_bulletimpacts.integer)
759 if (cl_particles_quake.integer)
760 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
763 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
764 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
765 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);
769 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
770 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
772 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
774 if (cl_particles_bulletimpacts.integer)
776 if (cl_particles_quake.integer)
777 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
780 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
781 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
782 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);
786 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
787 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
788 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);
790 else if (effectnameindex == EFFECT_TE_EXPLOSION)
792 CL_ParticleExplosion(center);
793 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);
795 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
797 CL_ParticleExplosion(center);
798 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);
800 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
802 if (cl_particles_quake.integer)
805 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
808 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);
810 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);
814 CL_ParticleExplosion(center);
815 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);
817 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
818 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);
819 else if (effectnameindex == EFFECT_TE_FLAMEJET)
821 count *= cl_particles_quality.value;
823 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);
825 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
827 float i, j, inc, vel;
830 inc = 8 / cl_particles_quality.value;
831 for (i = -128;i < 128;i += inc)
833 for (j = -128;j < 128;j += inc)
835 dir[0] = j + lhrandom(0, inc);
836 dir[1] = i + lhrandom(0, inc);
838 org[0] = center[0] + dir[0];
839 org[1] = center[1] + dir[1];
840 org[2] = center[2] + lhrandom(0, 64);
841 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
842 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);
846 else if (effectnameindex == EFFECT_TE_TELEPORT)
848 float i, j, k, inc, vel;
851 if (cl_particles_quake.integer)
852 inc = 4 / cl_particles_quality.value;
854 inc = 8 / cl_particles_quality.value;
855 for (i = -16;i < 16;i += inc)
857 for (j = -16;j < 16;j += inc)
859 for (k = -24;k < 32;k += inc)
861 VectorSet(dir, i*8, j*8, k*8);
862 VectorNormalize(dir);
863 vel = lhrandom(50, 113);
864 if (cl_particles_quake.integer)
865 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);
867 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);
871 if (!cl_particles_quake.integer)
872 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);
873 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);
875 else if (effectnameindex == EFFECT_TE_TEI_G3)
876 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);
877 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
879 if (cl_particles_smoke.integer)
881 count *= 0.25f * cl_particles_quality.value;
883 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);
886 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
888 CL_ParticleExplosion(center);
889 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);
891 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
894 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
895 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
896 if (cl_particles_smoke.integer)
897 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
898 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);
899 if (cl_particles_sparks.integer)
900 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
901 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);
902 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);
904 else if (effectnameindex == EFFECT_EF_FLAME)
906 count *= 300 * cl_particles_quality.value;
908 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);
909 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);
911 else if (effectnameindex == EFFECT_EF_STARDUST)
913 count *= 200 * cl_particles_quality.value;
915 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);
916 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);
918 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
922 int smoke, blood, bubbles, r, color;
924 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
927 Vector4Set(light, 0, 0, 0, 0);
929 if (effectnameindex == EFFECT_TR_ROCKET)
930 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
931 else if (effectnameindex == EFFECT_TR_VORESPIKE)
933 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
934 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
936 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
938 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
939 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
943 matrix4x4_t tempmatrix;
944 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
945 R_RTLight_Update(&r_refdef.scene.lights[r_refdef.scene.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
952 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
955 VectorSubtract(originmaxs, originmins, dir);
956 len = VectorNormalizeLength(dir);
959 dec = -ent->persistent.trail_time;
960 ent->persistent.trail_time += len;
961 if (ent->persistent.trail_time < 0.01f)
964 // if we skip out, leave it reset
965 ent->persistent.trail_time = 0.0f;
970 // advance into this frame to reach the first puff location
971 VectorMA(originmins, dec, dir, pos);
974 smoke = cl_particles.integer && cl_particles_smoke.integer;
975 blood = cl_particles.integer && cl_particles_blood.integer;
976 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
977 qd = 1.0f / cl_particles_quality.value;
984 if (effectnameindex == EFFECT_TR_BLOOD)
986 if (cl_particles_quake.integer)
988 color = particlepalette[67 + (rand()&3)];
989 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);
994 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, 0, -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);
997 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
999 if (cl_particles_quake.integer)
1002 color = particlepalette[67 + (rand()&3)];
1003 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);
1008 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, 0, -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);
1014 if (effectnameindex == EFFECT_TR_ROCKET)
1016 if (cl_particles_quake.integer)
1019 color = particlepalette[ramp3[r]];
1020 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);
1024 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);
1025 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);
1028 else if (effectnameindex == EFFECT_TR_GRENADE)
1030 if (cl_particles_quake.integer)
1033 color = particlepalette[ramp3[r]];
1034 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);
1038 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);
1041 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1043 if (cl_particles_quake.integer)
1046 color = particlepalette[52 + (rand()&7)];
1047 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);
1048 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);
1050 else if (gamemode == GAME_GOODVSBAD2)
1053 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);
1057 color = particlepalette[20 + (rand()&7)];
1058 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);
1061 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1063 if (cl_particles_quake.integer)
1066 color = particlepalette[230 + (rand()&7)];
1067 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);
1068 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);
1072 color = particlepalette[226 + (rand()&7)];
1073 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);
1076 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1078 if (cl_particles_quake.integer)
1080 color = particlepalette[152 + (rand()&3)];
1081 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);
1083 else if (gamemode == GAME_GOODVSBAD2)
1086 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);
1088 else if (gamemode == GAME_PRYDON)
1091 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);
1094 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);
1096 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1099 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);
1101 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1104 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);
1106 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1107 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);
1111 if (effectnameindex == EFFECT_TR_ROCKET)
1112 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);
1113 else if (effectnameindex == EFFECT_TR_GRENADE)
1114 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);
1116 // advance to next time and position
1119 VectorMA (pos, dec, dir, pos);
1122 ent->persistent.trail_time = len;
1124 else if (developer.integer >= 1)
1125 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1128 // this is also called on point effects with spawndlight = true and
1129 // spawnparticles = true
1130 // it is called CL_ParticleTrail because most code does not want to supply
1131 // these parameters, only trail handling does
1132 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)
1135 qboolean found = false;
1136 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1138 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1139 return; // no such effect
1141 VectorLerp(originmins, 0.5, originmaxs, center);
1142 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1144 int effectinfoindex;
1147 particleeffectinfo_t *info;
1149 vec3_t centervelocity;
1155 qboolean underwater;
1156 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1157 VectorLerp(originmins, 0.5, originmaxs, center);
1158 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1159 supercontents = CL_PointSuperContents(center);
1160 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1161 VectorSubtract(originmaxs, originmins, traildir);
1162 traillen = VectorLength(traildir);
1163 VectorNormalize(traildir);
1164 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1166 if (info->effectnameindex == effectnameindex)
1169 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1171 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1174 // spawn a dlight if requested
1175 if (info->lightradiusstart > 0 && spawndlight)
1177 matrix4x4_t tempmatrix;
1178 if (info->trailspacing > 0)
1179 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1181 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1182 if (info->lighttime > 0 && info->lightradiusfade > 0)
1184 // light flash (explosion, etc)
1185 // called when effect starts
1186 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);
1191 // called by CL_LinkNetworkEntity
1192 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1193 R_RTLight_Update(&r_refdef.scene.lights[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);
1197 if (!spawnparticles)
1202 if (info->tex[1] > info->tex[0])
1204 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1205 tex = min(tex, info->tex[1] - 1);
1207 if (info->particletype == pt_decal)
1208 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]);
1209 else if (info->particletype == pt_beam)
1210 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);
1213 if (!cl_particles.integer)
1215 switch (info->particletype)
1217 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1218 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1219 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1220 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1221 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1222 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1225 VectorCopy(originmins, trailpos);
1226 if (info->trailspacing > 0)
1228 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1229 trailstep = info->trailspacing / cl_particles_quality.value;
1233 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1236 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1237 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1239 if (info->tex[1] > info->tex[0])
1241 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1242 tex = min(tex, info->tex[1] - 1);
1246 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1247 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1248 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1251 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);
1253 VectorMA(trailpos, trailstep, traildir, trailpos);
1260 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1263 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)
1265 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1273 void CL_EntityParticles (const entity_t *ent)
1276 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1277 static vec3_t avelocities[NUMVERTEXNORMALS];
1278 if (!cl_particles.integer) return;
1279 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1281 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1283 if (!avelocities[0][0])
1284 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1285 avelocities[0][i] = lhrandom(0, 2.55);
1287 for (i = 0;i < NUMVERTEXNORMALS;i++)
1289 yaw = cl.time * avelocities[i][0];
1290 pitch = cl.time * avelocities[i][1];
1291 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1292 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1293 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1294 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);
1299 void CL_ReadPointFile_f (void)
1301 vec3_t org, leakorg;
1303 char *pointfile = NULL, *pointfilepos, *t, tchar;
1304 char name[MAX_OSPATH];
1309 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1310 strlcat (name, ".pts", sizeof (name));
1311 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1314 Con_Printf("Could not open %s\n", name);
1318 Con_Printf("Reading %s...\n", name);
1319 VectorClear(leakorg);
1322 pointfilepos = pointfile;
1323 while (*pointfilepos)
1325 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1330 while (*t && *t != '\n' && *t != '\r')
1334 #if _MSC_VER >= 1400
1335 #define sscanf sscanf_s
1337 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1343 VectorCopy(org, leakorg);
1346 if (cl.num_particles < cl.max_particles - 3)
1349 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);
1352 Mem_Free(pointfile);
1353 VectorCopy(leakorg, org);
1354 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1356 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);
1357 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);
1358 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);
1363 CL_ParseParticleEffect
1365 Parse an effect out of the server message
1368 void CL_ParseParticleEffect (void)
1371 int i, count, msgcount, color;
1373 MSG_ReadVector(org, cls.protocol);
1374 for (i=0 ; i<3 ; i++)
1375 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1376 msgcount = MSG_ReadByte ();
1377 color = MSG_ReadByte ();
1379 if (msgcount == 255)
1384 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1389 CL_ParticleExplosion
1393 void CL_ParticleExplosion (const vec3_t org)
1399 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1400 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1402 if (cl_particles_quake.integer)
1404 for (i = 0;i < 1024;i++)
1410 color = particlepalette[ramp1[r]];
1411 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);
1415 color = particlepalette[ramp2[r]];
1416 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);
1422 i = CL_PointSuperContents(org);
1423 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1425 if (cl_particles.integer && cl_particles_bubbles.integer)
1426 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1427 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);
1431 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1433 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1437 for (k = 0;k < 16;k++)
1440 VectorMA(org, 128, v2, v);
1441 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1442 if (trace.fraction >= 0.1)
1445 VectorSubtract(trace.endpos, org, v2);
1446 VectorScale(v2, 2.0f, v2);
1447 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);
1453 if (cl_particles_explosions_shell.integer)
1454 R_NewExplosion(org);
1459 CL_ParticleExplosion2
1463 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1466 if (!cl_particles.integer) return;
1468 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1470 k = particlepalette[colorStart + (i % colorLength)];
1471 if (cl_particles_quake.integer)
1472 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);
1474 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);
1478 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1480 if (cl_particles_sparks.integer)
1482 sparkcount *= cl_particles_quality.value;
1483 while(sparkcount-- > 0)
1484 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);
1488 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1490 if (cl_particles_smoke.integer)
1492 smokecount *= cl_particles_quality.value;
1493 while(smokecount-- > 0)
1494 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);
1498 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)
1501 if (!cl_particles.integer) return;
1503 count = (int)(count * cl_particles_quality.value);
1506 k = particlepalette[colorbase + (rand()&3)];
1507 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);
1511 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1514 float minz, maxz, lifetime = 30;
1515 if (!cl_particles.integer) return;
1516 if (dir[2] < 0) // falling
1518 minz = maxs[2] + dir[2] * 0.1;
1521 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1526 maxz = maxs[2] + dir[2] * 0.1;
1528 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1531 count = (int)(count * cl_particles_quality.value);
1536 if (!cl_particles_rain.integer) break;
1537 count *= 4; // ick, this should be in the mod or maps?
1541 k = particlepalette[colorbase + (rand()&3)];
1542 if (gamemode == GAME_GOODVSBAD2)
1543 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);
1545 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);
1549 if (!cl_particles_snow.integer) break;
1552 k = particlepalette[colorbase + (rand()&3)];
1553 if (gamemode == GAME_GOODVSBAD2)
1554 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);
1556 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);
1560 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1564 #define MAX_PARTICLETEXTURES 64
1565 // particletexture_t is a rectangle in the particlefonttexture
1566 typedef struct particletexture_s
1568 rtexture_t *texture;
1569 float s1, t1, s2, t2;
1573 static rtexturepool_t *particletexturepool;
1574 static rtexture_t *particlefonttexture;
1575 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1577 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1578 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1579 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1580 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1582 #define PARTICLETEXTURESIZE 64
1583 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1585 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1589 dz = 1 - (dx*dx+dy*dy);
1590 if (dz > 0) // it does hit the sphere
1594 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1595 VectorNormalize(normal);
1596 dot = DotProduct(normal, light);
1597 if (dot > 0.5) // interior reflection
1598 f += ((dot * 2) - 1);
1599 else if (dot < -0.5) // exterior reflection
1600 f += ((dot * -2) - 1);
1602 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1603 VectorNormalize(normal);
1604 dot = DotProduct(normal, light);
1605 if (dot > 0.5) // interior reflection
1606 f += ((dot * 2) - 1);
1607 else if (dot < -0.5) // exterior reflection
1608 f += ((dot * -2) - 1);
1610 f += 16; // just to give it a haze so you can see the outline
1611 f = bound(0, f, 255);
1612 return (unsigned char) f;
1618 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1620 int basex, basey, y;
1621 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1622 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1623 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1624 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1627 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1630 float cx, cy, dx, dy, f, iradius;
1632 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1633 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1634 iradius = 1.0f / radius;
1635 alpha *= (1.0f / 255.0f);
1636 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1638 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1642 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1647 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1648 d[0] += (int)(f * (blue - d[0]));
1649 d[1] += (int)(f * (green - d[1]));
1650 d[2] += (int)(f * (red - d[2]));
1656 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1659 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1661 data[0] = bound(minb, data[0], maxb);
1662 data[1] = bound(ming, data[1], maxg);
1663 data[2] = bound(minr, data[2], maxr);
1667 void particletextureinvert(unsigned char *data)
1670 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1672 data[0] = 255 - data[0];
1673 data[1] = 255 - data[1];
1674 data[2] = 255 - data[2];
1678 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1679 static void R_InitBloodTextures (unsigned char *particletexturedata)
1682 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1685 for (i = 0;i < 8;i++)
1687 memset(&data[0][0][0], 255, sizeof(data));
1688 for (k = 0;k < 24;k++)
1689 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1690 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1691 particletextureinvert(&data[0][0][0]);
1692 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1696 for (i = 0;i < 8;i++)
1698 memset(&data[0][0][0], 255, sizeof(data));
1700 for (j = 1;j < 10;j++)
1701 for (k = min(j, m - 1);k < m;k++)
1702 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1703 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1704 particletextureinvert(&data[0][0][0]);
1705 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1710 //uncomment this to make engine save out particle font to a tga file when run
1711 //#define DUMPPARTICLEFONT
1713 static void R_InitParticleTexture (void)
1715 int x, y, d, i, k, m;
1719 // a note: decals need to modulate (multiply) the background color to
1720 // properly darken it (stain), and they need to be able to alpha fade,
1721 // this is a very difficult challenge because it means fading to white
1722 // (no change to background) rather than black (darkening everything
1723 // behind the whole decal polygon), and to accomplish this the texture is
1724 // inverted (dark red blood on white background becomes brilliant cyan
1725 // and white on black background) so we can alpha fade it to black, then
1726 // we invert it again during the blendfunc to make it work...
1728 #ifndef DUMPPARTICLEFONT
1729 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1730 if (!particlefonttexture)
1733 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1734 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1735 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1738 for (i = 0;i < 8;i++)
1740 memset(&data[0][0][0], 255, sizeof(data));
1743 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1745 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1746 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1748 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1750 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1751 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1753 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1754 d = (noise2[y][x] - 128) * 3 + 192;
1756 d = (int)(d * (1-(dx*dx+dy*dy)));
1757 d = (d * noise1[y][x]) >> 7;
1758 d = bound(0, d, 255);
1759 data[y][x][3] = (unsigned char) d;
1766 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1770 memset(&data[0][0][0], 255, sizeof(data));
1771 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1773 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1774 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1776 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1777 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1778 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1781 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1784 memset(&data[0][0][0], 255, sizeof(data));
1785 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1787 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1788 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1790 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1791 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1792 d = bound(0, d, 255);
1793 data[y][x][3] = (unsigned char) d;
1796 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1799 memset(&data[0][0][0], 255, sizeof(data));
1800 light[0] = 1;light[1] = 1;light[2] = 1;
1801 VectorNormalize(light);
1802 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1804 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1805 // stretch upper half of bubble by +50% and shrink lower half by -50%
1806 // (this gives an elongated teardrop shape)
1808 dy = (dy - 0.5f) * 2.0f;
1810 dy = (dy - 0.5f) / 1.5f;
1811 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1813 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1814 // shrink bubble width to half
1816 data[y][x][3] = shadebubble(dx, dy, light);
1819 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1822 memset(&data[0][0][0], 255, sizeof(data));
1823 light[0] = 1;light[1] = 1;light[2] = 1;
1824 VectorNormalize(light);
1825 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1827 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1828 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1830 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1831 data[y][x][3] = shadebubble(dx, dy, light);
1834 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1836 // Blood particles and blood decals
1837 R_InitBloodTextures (particletexturedata);
1840 for (i = 0;i < 8;i++)
1842 memset(&data[0][0][0], 255, sizeof(data));
1843 for (k = 0;k < 12;k++)
1844 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1845 for (k = 0;k < 3;k++)
1846 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1847 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1848 particletextureinvert(&data[0][0][0]);
1849 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1852 #ifdef DUMPPARTICLEFONT
1853 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1856 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1858 Mem_Free(particletexturedata);
1860 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1862 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1863 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1864 particletexture[i].texture = particlefonttexture;
1865 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1866 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1867 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1868 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1871 #ifndef DUMPPARTICLEFONT
1872 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1873 if (!particletexture[tex_beam].texture)
1876 unsigned char noise3[64][64], data2[64][16][4];
1878 fractalnoise(&noise3[0][0], 64, 4);
1880 for (y = 0;y < 64;y++)
1882 dy = (y - 0.5f*64) / (64*0.5f-1);
1883 for (x = 0;x < 16;x++)
1885 dx = (x - 0.5f*16) / (16*0.5f-2);
1886 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1887 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1888 data2[y][x][3] = 255;
1892 #ifdef DUMPPARTICLEFONT
1893 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1895 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1897 particletexture[tex_beam].s1 = 0;
1898 particletexture[tex_beam].t1 = 0;
1899 particletexture[tex_beam].s2 = 1;
1900 particletexture[tex_beam].t2 = 1;
1903 static void r_part_start(void)
1906 // generate particlepalette for convenience from the main one
1907 for (i = 0;i < 256;i++)
1908 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1909 particletexturepool = R_AllocTexturePool();
1910 R_InitParticleTexture ();
1911 CL_Particles_LoadEffectInfo();
1914 static void r_part_shutdown(void)
1916 R_FreeTexturePool(&particletexturepool);
1919 static void r_part_newmap(void)
1921 CL_Particles_LoadEffectInfo();
1924 #define BATCHSIZE 256
1925 unsigned short particle_elements[BATCHSIZE*6];
1927 void R_Particles_Init (void)
1930 for (i = 0;i < BATCHSIZE;i++)
1932 particle_elements[i*6+0] = i*4+0;
1933 particle_elements[i*6+1] = i*4+1;
1934 particle_elements[i*6+2] = i*4+2;
1935 particle_elements[i*6+3] = i*4+0;
1936 particle_elements[i*6+4] = i*4+2;
1937 particle_elements[i*6+5] = i*4+3;
1940 Cvar_RegisterVariable(&r_drawparticles);
1941 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1942 Cvar_RegisterVariable(&r_drawdecals);
1943 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1944 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1947 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1949 int surfacelistindex;
1951 float *v3f, *t2f, *c4f;
1952 particletexture_t *tex;
1953 float right[3], up[3], size, ca;
1954 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1955 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1957 r_refdef.stats.decals += numsurfaces;
1958 R_Mesh_Matrix(&identitymatrix);
1959 R_Mesh_ResetTextureState();
1960 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1961 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1962 R_Mesh_ColorPointer(particle_color4f, 0, 0);
1963 R_SetupGenericShader(true);
1964 GL_DepthMask(false);
1965 GL_DepthRange(0, 1);
1966 GL_PolygonOffset(0, 0);
1968 GL_CullFace(GL_NONE);
1970 // generate all the vertices at once
1971 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1973 d = cl.decals + surfacelist[surfacelistindex];
1976 c4f = particle_color4f + 16*surfacelistindex;
1977 ca = d->alpha * alphascale;
1978 if (r_refdef.fogenabled)
1979 ca *= FogPoint_World(d->org);
1980 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1981 Vector4Copy(c4f, c4f + 4);
1982 Vector4Copy(c4f, c4f + 8);
1983 Vector4Copy(c4f, c4f + 12);
1985 // calculate vertex positions
1986 size = d->size * cl_particles_size.value;
1987 VectorVectors(d->normal, right, up);
1988 VectorScale(right, size, right);
1989 VectorScale(up, size, up);
1990 v3f = particle_vertex3f + 12*surfacelistindex;
1991 v3f[ 0] = d->org[0] - right[0] - up[0];
1992 v3f[ 1] = d->org[1] - right[1] - up[1];
1993 v3f[ 2] = d->org[2] - right[2] - up[2];
1994 v3f[ 3] = d->org[0] - right[0] + up[0];
1995 v3f[ 4] = d->org[1] - right[1] + up[1];
1996 v3f[ 5] = d->org[2] - right[2] + up[2];
1997 v3f[ 6] = d->org[0] + right[0] + up[0];
1998 v3f[ 7] = d->org[1] + right[1] + up[1];
1999 v3f[ 8] = d->org[2] + right[2] + up[2];
2000 v3f[ 9] = d->org[0] + right[0] - up[0];
2001 v3f[10] = d->org[1] + right[1] - up[1];
2002 v3f[11] = d->org[2] + right[2] - up[2];
2004 // calculate texcoords
2005 tex = &particletexture[d->texnum];
2006 t2f = particle_texcoord2f + 8*surfacelistindex;
2007 t2f[0] = tex->s1;t2f[1] = tex->t2;
2008 t2f[2] = tex->s1;t2f[3] = tex->t1;
2009 t2f[4] = tex->s2;t2f[5] = tex->t1;
2010 t2f[6] = tex->s2;t2f[7] = tex->t2;
2013 // now render the decals all at once
2014 // (this assumes they all use one particle font texture!)
2015 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2016 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2017 GL_LockArrays(0, numsurfaces*4);
2018 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2019 GL_LockArrays(0, 0);
2022 void R_DrawDecals (void)
2030 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2031 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2033 // LordHavoc: early out conditions
2034 if ((!cl.num_decals) || (!r_drawdecals.integer))
2037 decalfade = frametime * 256 / cl_decals_fadetime.value;
2038 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2039 drawdist2 = drawdist2*drawdist2;
2041 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2043 if (!decal->typeindex)
2046 if (cl.time > decal->time2 + cl_decals_time.value)
2048 decal->alpha -= decalfade;
2049 if (decal->alpha <= 0)
2055 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2057 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2058 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2064 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))
2066 if(!cl_particles_novis.integer)
2067 if (!r_refdef.viewcache.world_novis)
2068 if(r_refdef.scene.worldmodel->brush.PointInLeaf)
2070 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
2072 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2075 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2079 decal->typeindex = 0;
2080 if (cl.free_decal > i)
2084 // reduce cl.num_decals if possible
2085 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2088 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2090 decal_t *olddecals = cl.decals;
2091 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2092 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2093 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2094 Mem_Free(olddecals);
2098 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2100 int surfacelistindex;
2101 int batchstart, batchcount;
2102 const particle_t *p;
2104 rtexture_t *texture;
2105 float *v3f, *t2f, *c4f;
2106 particletexture_t *tex;
2107 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2108 float ambient[3], diffuse[3], diffusenormal[3];
2109 vec4_t colormultiplier;
2110 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2112 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));
2114 r_refdef.stats.particles += numsurfaces;
2115 R_Mesh_Matrix(&identitymatrix);
2116 R_Mesh_ResetTextureState();
2117 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2118 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2119 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2120 R_SetupGenericShader(true);
2121 GL_DepthMask(false);
2122 GL_DepthRange(0, 1);
2123 GL_PolygonOffset(0, 0);
2125 GL_CullFace(GL_NONE);
2127 // first generate all the vertices at once
2128 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2130 p = cl.particles + surfacelist[surfacelistindex];
2132 blendmode = particletype[p->typeindex].blendmode;
2134 c4f[0] = p->color[0] * colormultiplier[0];
2135 c4f[1] = p->color[1] * colormultiplier[1];
2136 c4f[2] = p->color[2] * colormultiplier[2];
2137 c4f[3] = p->alpha * colormultiplier[3];
2142 // additive and modulate can just fade out in fog (this is correct)
2143 if (r_refdef.fogenabled)
2144 c4f[3] *= FogPoint_World(p->org);
2145 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2152 // note: lighting is not cheap!
2153 if (particletype[p->typeindex].lighting)
2155 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2156 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2157 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2158 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2160 // mix in the fog color
2161 if (r_refdef.fogenabled)
2163 fog = FogPoint_World(p->org);
2165 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2166 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2167 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2171 // copy the color into the other three vertices
2172 Vector4Copy(c4f, c4f + 4);
2173 Vector4Copy(c4f, c4f + 8);
2174 Vector4Copy(c4f, c4f + 12);
2176 size = p->size * cl_particles_size.value;
2177 tex = &particletexture[p->texnum];
2178 switch(particletype[p->typeindex].orientation)
2180 case PARTICLE_BILLBOARD:
2181 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2182 VectorScale(r_refdef.view.up, size, up);
2183 v3f[ 0] = p->org[0] - right[0] - up[0];
2184 v3f[ 1] = p->org[1] - right[1] - up[1];
2185 v3f[ 2] = p->org[2] - right[2] - up[2];
2186 v3f[ 3] = p->org[0] - right[0] + up[0];
2187 v3f[ 4] = p->org[1] - right[1] + up[1];
2188 v3f[ 5] = p->org[2] - right[2] + up[2];
2189 v3f[ 6] = p->org[0] + right[0] + up[0];
2190 v3f[ 7] = p->org[1] + right[1] + up[1];
2191 v3f[ 8] = p->org[2] + right[2] + up[2];
2192 v3f[ 9] = p->org[0] + right[0] - up[0];
2193 v3f[10] = p->org[1] + right[1] - up[1];
2194 v3f[11] = p->org[2] + right[2] - up[2];
2195 t2f[0] = tex->s1;t2f[1] = tex->t2;
2196 t2f[2] = tex->s1;t2f[3] = tex->t1;
2197 t2f[4] = tex->s2;t2f[5] = tex->t1;
2198 t2f[6] = tex->s2;t2f[7] = tex->t2;
2200 case PARTICLE_ORIENTED_DOUBLESIDED:
2201 VectorVectors(p->vel, right, up);
2202 VectorScale(right, size * p->stretch, right);
2203 VectorScale(up, size, up);
2204 v3f[ 0] = p->org[0] - right[0] - up[0];
2205 v3f[ 1] = p->org[1] - right[1] - up[1];
2206 v3f[ 2] = p->org[2] - right[2] - up[2];
2207 v3f[ 3] = p->org[0] - right[0] + up[0];
2208 v3f[ 4] = p->org[1] - right[1] + up[1];
2209 v3f[ 5] = p->org[2] - right[2] + up[2];
2210 v3f[ 6] = p->org[0] + right[0] + up[0];
2211 v3f[ 7] = p->org[1] + right[1] + up[1];
2212 v3f[ 8] = p->org[2] + right[2] + up[2];
2213 v3f[ 9] = p->org[0] + right[0] - up[0];
2214 v3f[10] = p->org[1] + right[1] - up[1];
2215 v3f[11] = p->org[2] + right[2] - up[2];
2216 t2f[0] = tex->s1;t2f[1] = tex->t2;
2217 t2f[2] = tex->s1;t2f[3] = tex->t1;
2218 t2f[4] = tex->s2;t2f[5] = tex->t1;
2219 t2f[6] = tex->s2;t2f[7] = tex->t2;
2221 case PARTICLE_SPARK:
2222 len = VectorLength(p->vel);
2223 VectorNormalize2(p->vel, up);
2224 lenfactor = p->stretch * 0.04 * len;
2225 if(lenfactor < size * 0.5)
2226 lenfactor = size * 0.5;
2227 VectorMA(p->org, -lenfactor, up, v);
2228 VectorMA(p->org, lenfactor, up, up2);
2229 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2230 t2f[0] = tex->s1;t2f[1] = tex->t2;
2231 t2f[2] = tex->s1;t2f[3] = tex->t1;
2232 t2f[4] = tex->s2;t2f[5] = tex->t1;
2233 t2f[6] = tex->s2;t2f[7] = tex->t2;
2236 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2237 VectorSubtract(p->vel, p->org, up);
2238 VectorNormalize(up);
2239 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2240 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2241 t2f[0] = 1;t2f[1] = v[0];
2242 t2f[2] = 0;t2f[3] = v[0];
2243 t2f[4] = 0;t2f[5] = v[1];
2244 t2f[6] = 1;t2f[7] = v[1];
2249 // now render batches of particles based on blendmode and texture
2252 GL_LockArrays(0, numsurfaces*4);
2255 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2257 p = cl.particles + surfacelist[surfacelistindex];
2259 if (blendmode != particletype[p->typeindex].blendmode)
2261 blendmode = particletype[p->typeindex].blendmode;
2265 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2268 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2271 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2275 if (texture != particletexture[p->texnum].texture)
2277 texture = particletexture[p->texnum].texture;
2278 R_Mesh_TexBind(0, R_GetTexture(texture));
2281 // iterate until we find a change in settings
2282 batchstart = surfacelistindex++;
2283 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2285 p = cl.particles + surfacelist[surfacelistindex];
2286 if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2290 batchcount = surfacelistindex - batchstart;
2291 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2293 GL_LockArrays(0, 0);
2296 void R_DrawParticles (void)
2299 float minparticledist;
2301 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2307 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2308 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2310 // LordHavoc: early out conditions
2311 if ((!cl.num_particles) || (!r_drawparticles.integer))
2314 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2315 gravity = frametime * cl.movevars_gravity;
2316 dvel = 1+4*frametime;
2317 decalfade = frametime * 255 / cl_decals_fadetime.value;
2318 update = frametime > 0;
2319 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2320 drawdist2 = drawdist2*drawdist2;
2322 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2326 if (cl.free_particle > i)
2327 cl.free_particle = i;
2333 if (p->delayedspawn > cl.time)
2335 p->delayedspawn = 0;
2339 p->size += p->sizeincrease * frametime;
2340 p->alpha -= p->alphafade * frametime;
2342 if (p->alpha <= 0 || p->die <= cl.time)
2345 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2347 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2349 if (p->typeindex == pt_blood)
2350 p->size += frametime * 8;
2352 p->vel[2] -= p->gravity * gravity;
2353 f = 1.0f - min(p->liquidfriction * frametime, 1);
2354 VectorScale(p->vel, f, p->vel);
2358 p->vel[2] -= p->gravity * gravity;
2361 f = 1.0f - min(p->airfriction * frametime, 1);
2362 VectorScale(p->vel, f, p->vel);
2366 VectorCopy(p->org, oldorg);
2367 VectorMA(p->org, frametime, p->vel, p->org);
2368 if (p->bounce && cl.time >= p->delayedcollisions)
2370 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);
2371 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2372 // or if the trace hit something flagged as NOIMPACT
2373 // then remove the particle
2374 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2376 VectorCopy(trace.endpos, p->org);
2377 // react if the particle hit something
2378 if (trace.fraction < 1)
2380 VectorCopy(trace.endpos, p->org);
2381 if (p->typeindex == pt_blood)
2383 // blood - splash on solid
2384 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2386 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)));
2387 if (cl_decals.integer)
2389 // create a decal for the blood splat
2390 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);
2394 else if (p->bounce < 0)
2396 // bounce -1 means remove on impact
2401 // anything else - bounce off solid
2402 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2403 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2404 if (DotProduct(p->vel, p->vel) < 0.03)
2405 VectorClear(p->vel);
2411 if (p->typeindex != pt_static)
2413 switch (p->typeindex)
2415 case pt_entityparticle:
2416 // particle that removes itself after one rendered frame
2423 a = CL_PointSuperContents(p->org);
2424 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2428 a = CL_PointSuperContents(p->org);
2429 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2433 a = CL_PointSuperContents(p->org);
2434 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2438 if (cl.time > p->time2)
2441 p->time2 = cl.time + (rand() & 3) * 0.1;
2442 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2443 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2445 a = CL_PointSuperContents(p->org);
2446 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2454 else if (p->delayedspawn)
2457 // don't render particles too close to the view (they chew fillrate)
2458 // also don't render particles behind the view (useless)
2459 // further checks to cull to the frustum would be too slow here
2460 switch(p->typeindex)
2463 // beams have no culling
2464 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2467 if(!cl_particles_novis.integer)
2468 if (!r_refdef.viewcache.world_novis)
2469 if(r_refdef.scene.worldmodel->brush.PointInLeaf)
2471 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2473 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2476 // anything else just has to be in front of the viewer and visible at this distance
2477 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2478 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2485 if (cl.free_particle > i)
2486 cl.free_particle = i;
2489 // reduce cl.num_particles if possible
2490 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2493 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2495 particle_t *oldparticles = cl.particles;
2496 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2497 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2498 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2499 Mem_Free(oldparticles);