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_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
196 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
197 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
200 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
206 particleeffectinfo_t *info = NULL;
207 const char *text = textstart;
209 effectinfoindex = -1;
210 for (linenumber = 1;;linenumber++)
213 for (arrayindex = 0;arrayindex < 16;arrayindex++)
214 argv[arrayindex][0] = 0;
217 if (!COM_ParseToken_Simple(&text, true, false))
219 if (!strcmp(com_token, "\n"))
223 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
229 #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;}
230 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
231 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
232 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
233 #define readfloat(var) checkparms(2);var = atof(argv[1])
234 if (!strcmp(argv[0], "effect"))
239 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
241 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
244 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
246 if (particleeffectname[effectnameindex][0])
248 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
253 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
257 // if we run out of names, abort
258 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
260 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
263 info = particleeffectinfo + effectinfoindex;
264 info->effectnameindex = effectnameindex;
265 info->particletype = pt_alphastatic;
266 info->tex[0] = tex_particle;
267 info->tex[1] = tex_particle;
268 info->color[0] = 0xFFFFFF;
269 info->color[1] = 0xFFFFFF;
273 info->alpha[1] = 256;
274 info->alpha[2] = 256;
275 info->time[0] = 9999;
276 info->time[1] = 9999;
277 VectorSet(info->lightcolor, 1, 1, 1);
278 info->lightshadow = true;
279 info->lighttime = 9999;
280 info->stretchfactor = 1;
282 else if (info == NULL)
284 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
287 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
288 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
289 else if (!strcmp(argv[0], "type"))
292 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
293 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
294 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
295 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
296 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
297 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
298 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
299 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
300 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
301 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
302 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
303 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
304 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
306 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
307 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
308 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
309 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
310 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
311 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
312 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
313 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
314 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
315 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
316 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
317 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
318 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
319 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
320 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
321 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
322 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
323 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
324 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
325 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
326 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
327 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
328 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
329 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
330 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
332 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
341 int CL_ParticleEffectIndexForName(const char *name)
344 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
345 if (!strcmp(particleeffectname[i], name))
350 const char *CL_ParticleEffectNameForIndex(int i)
352 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
354 return particleeffectname[i];
357 // MUST match effectnameindex_t in client.h
358 static const char *standardeffectnames[EFFECT_TOTAL] =
382 "TE_TEI_BIGEXPLOSION",
398 void CL_Particles_LoadEffectInfo(void)
401 unsigned char *filedata;
402 fs_offset_t filesize;
403 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
404 memset(particleeffectname, 0, sizeof(particleeffectname));
405 for (i = 0;i < EFFECT_TOTAL;i++)
406 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
407 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
410 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
420 void CL_ReadPointFile_f (void);
421 void CL_Particles_Init (void)
423 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)");
424 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
426 Cvar_RegisterVariable (&cl_particles);
427 Cvar_RegisterVariable (&cl_particles_quality);
428 Cvar_RegisterVariable (&cl_particles_alpha);
429 Cvar_RegisterVariable (&cl_particles_size);
430 Cvar_RegisterVariable (&cl_particles_quake);
431 Cvar_RegisterVariable (&cl_particles_blood);
432 Cvar_RegisterVariable (&cl_particles_blood_alpha);
433 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
434 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
435 Cvar_RegisterVariable (&cl_particles_explosions_shell);
436 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
437 Cvar_RegisterVariable (&cl_particles_rain);
438 Cvar_RegisterVariable (&cl_particles_snow);
439 Cvar_RegisterVariable (&cl_particles_smoke);
440 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
441 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
442 Cvar_RegisterVariable (&cl_particles_sparks);
443 Cvar_RegisterVariable (&cl_particles_bubbles);
444 Cvar_RegisterVariable (&cl_decals);
445 Cvar_RegisterVariable (&cl_decals_time);
446 Cvar_RegisterVariable (&cl_decals_fadetime);
449 void CL_Particles_Shutdown (void)
453 // list of all 26 parameters:
454 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
455 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
456 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
457 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
458 // palpha - opacity of particle as 0-255 (can be more than 255)
459 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
460 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
461 // pgravity - how much effect gravity has on the particle (0-1)
462 // 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
463 // px,py,pz - starting origin of particle
464 // pvx,pvy,pvz - starting velocity of particle
465 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
466 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)
471 if (!cl_particles.integer)
473 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
474 if (cl.free_particle >= cl.max_particles)
477 lifetime = palpha / min(1, palphafade);
478 part = &cl.particles[cl.free_particle++];
479 if (cl.num_particles < cl.free_particle)
480 cl.num_particles = cl.free_particle;
481 memset(part, 0, sizeof(*part));
482 part->typeindex = ptypeindex;
483 l2 = (int)lhrandom(0.5, 256.5);
485 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
486 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
487 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
490 part->sizeincrease = psizeincrease;
491 part->alpha = palpha;
492 part->alphafade = palphafade;
493 part->gravity = pgravity;
494 part->bounce = pbounce;
495 part->stretch = stretch;
497 part->org[0] = px + originjitter * v[0];
498 part->org[1] = py + originjitter * v[1];
499 part->org[2] = pz + originjitter * v[2];
500 part->vel[0] = pvx + velocityjitter * v[0];
501 part->vel[1] = pvy + velocityjitter * v[1];
502 part->vel[2] = pvz + velocityjitter * v[2];
504 part->airfriction = pairfriction;
505 part->liquidfriction = pliquidfriction;
506 part->die = cl.time + lifetime;
507 part->delayedcollisions = 0;
508 part->qualityreduction = pqualityreduction;
509 if (part->typeindex == pt_blood)
510 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)
511 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
512 if (part->typeindex == pt_rain)
516 float lifetime = part->die - cl.time;
519 // turn raindrop into simple spark and create delayedspawn splash effect
520 part->typeindex = pt_spark;
522 VectorMA(part->org, lifetime, part->vel, endvec);
523 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
524 part->die = cl.time + lifetime * trace.fraction;
525 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);
528 part2->delayedspawn = part->die;
529 part2->die += part->die - cl.time;
530 for (i = rand() & 7;i < 10;i++)
532 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);
535 part2->delayedspawn = part->die;
536 part2->die += part->die - cl.time;
541 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
543 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
546 VectorMA(part->org, lifetime, part->vel, endvec);
547 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
548 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
553 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
557 if (!cl_decals.integer)
559 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
560 if (cl.free_decal >= cl.max_decals)
562 decal = &cl.decals[cl.free_decal++];
563 if (cl.num_decals < cl.free_decal)
564 cl.num_decals = cl.free_decal;
565 memset(decal, 0, sizeof(*decal));
566 decal->typeindex = pt_decal;
567 decal->texnum = texnum;
568 VectorAdd(org, normal, decal->org);
569 VectorCopy(normal, decal->normal);
571 decal->alpha = alpha;
572 decal->time2 = cl.time;
573 l2 = (int)lhrandom(0.5, 256.5);
575 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
576 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
577 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
578 decal->owner = hitent;
581 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
582 decal->ownermodel = cl.entities[decal->owner].render.model;
583 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
584 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
588 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
591 float bestfrac, bestorg[3], bestnormal[3];
593 int besthitent = 0, hitent;
596 for (i = 0;i < 32;i++)
599 VectorMA(org, maxdist, org2, org2);
600 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
601 // take the closest trace result that doesn't end up hitting a NOMARKS
602 // surface (sky for example)
603 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
605 bestfrac = trace.fraction;
607 VectorCopy(trace.endpos, bestorg);
608 VectorCopy(trace.plane.normal, bestnormal);
612 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
615 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
616 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
617 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)
620 matrix4x4_t tempmatrix;
621 VectorLerp(originmins, 0.5, originmaxs, center);
622 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
623 if (effectnameindex == EFFECT_SVC_PARTICLE)
625 if (cl_particles.integer)
627 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
629 CL_ParticleExplosion(center);
630 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
631 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
634 count *= cl_particles_quality.value;
635 for (;count > 0;count--)
637 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
638 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);
643 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
644 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
645 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
646 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
647 else if (effectnameindex == EFFECT_TE_SPIKE)
649 if (cl_particles_bulletimpacts.integer)
651 if (cl_particles_quake.integer)
653 if (cl_particles_smoke.integer)
654 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
658 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
659 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
660 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);
664 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
665 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
667 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
669 if (cl_particles_bulletimpacts.integer)
671 if (cl_particles_quake.integer)
673 if (cl_particles_smoke.integer)
674 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
678 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
679 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
680 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);
684 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
685 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
686 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);
688 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
690 if (cl_particles_bulletimpacts.integer)
692 if (cl_particles_quake.integer)
694 if (cl_particles_smoke.integer)
695 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
699 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
700 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
701 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);
705 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
706 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
708 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
710 if (cl_particles_bulletimpacts.integer)
712 if (cl_particles_quake.integer)
714 if (cl_particles_smoke.integer)
715 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
719 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
720 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
721 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);
725 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
726 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
727 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);
729 else if (effectnameindex == EFFECT_TE_BLOOD)
731 if (!cl_particles_blood.integer)
733 if (cl_particles_quake.integer)
734 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
737 static double bloodaccumulator = 0;
738 //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);
739 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
740 for (;bloodaccumulator > 0;bloodaccumulator--)
741 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);
744 else if (effectnameindex == EFFECT_TE_SPARK)
745 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
746 else if (effectnameindex == EFFECT_TE_PLASMABURN)
748 // plasma scorch mark
749 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
750 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
751 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
753 else if (effectnameindex == EFFECT_TE_GUNSHOT)
755 if (cl_particles_bulletimpacts.integer)
757 if (cl_particles_quake.integer)
758 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
761 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
762 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
763 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1);
767 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
768 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
770 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
772 if (cl_particles_bulletimpacts.integer)
774 if (cl_particles_quake.integer)
775 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
778 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
779 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
780 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);
784 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
785 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
786 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);
788 else if (effectnameindex == EFFECT_TE_EXPLOSION)
790 CL_ParticleExplosion(center);
791 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);
793 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
795 CL_ParticleExplosion(center);
796 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);
798 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
800 if (cl_particles_quake.integer)
803 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
806 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);
808 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);
812 CL_ParticleExplosion(center);
813 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);
815 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
816 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);
817 else if (effectnameindex == EFFECT_TE_FLAMEJET)
819 count *= cl_particles_quality.value;
821 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);
823 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
825 float i, j, inc, vel;
828 inc = 8 / cl_particles_quality.value;
829 for (i = -128;i < 128;i += inc)
831 for (j = -128;j < 128;j += inc)
833 dir[0] = j + lhrandom(0, inc);
834 dir[1] = i + lhrandom(0, inc);
836 org[0] = center[0] + dir[0];
837 org[1] = center[1] + dir[1];
838 org[2] = center[2] + lhrandom(0, 64);
839 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
840 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);
844 else if (effectnameindex == EFFECT_TE_TELEPORT)
846 float i, j, k, inc, vel;
849 if (cl_particles_quake.integer)
850 inc = 4 / cl_particles_quality.value;
852 inc = 8 / cl_particles_quality.value;
853 for (i = -16;i < 16;i += inc)
855 for (j = -16;j < 16;j += inc)
857 for (k = -24;k < 32;k += inc)
859 VectorSet(dir, i*8, j*8, k*8);
860 VectorNormalize(dir);
861 vel = lhrandom(50, 113);
862 if (cl_particles_quake.integer)
863 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);
865 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);
869 if (!cl_particles_quake.integer)
870 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);
871 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);
873 else if (effectnameindex == EFFECT_TE_TEI_G3)
874 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);
875 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
877 if (cl_particles_smoke.integer)
879 count *= 0.25f * cl_particles_quality.value;
881 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);
884 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
886 CL_ParticleExplosion(center);
887 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);
889 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
892 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
893 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
894 if (cl_particles_smoke.integer)
895 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
896 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);
897 if (cl_particles_sparks.integer)
898 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
899 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);
900 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);
902 else if (effectnameindex == EFFECT_EF_FLAME)
904 count *= 300 * cl_particles_quality.value;
906 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);
907 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);
909 else if (effectnameindex == EFFECT_EF_STARDUST)
911 count *= 200 * cl_particles_quality.value;
913 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);
914 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);
916 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
920 int smoke, blood, bubbles, r, color;
922 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
925 Vector4Set(light, 0, 0, 0, 0);
927 if (effectnameindex == EFFECT_TR_ROCKET)
928 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
929 else if (effectnameindex == EFFECT_TR_VORESPIKE)
931 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
932 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
934 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
936 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
937 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
941 matrix4x4_t tempmatrix;
942 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
943 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);
950 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
953 VectorSubtract(originmaxs, originmins, dir);
954 len = VectorNormalizeLength(dir);
957 dec = -ent->persistent.trail_time;
958 ent->persistent.trail_time += len;
959 if (ent->persistent.trail_time < 0.01f)
962 // if we skip out, leave it reset
963 ent->persistent.trail_time = 0.0f;
968 // advance into this frame to reach the first puff location
969 VectorMA(originmins, dec, dir, pos);
972 smoke = cl_particles.integer && cl_particles_smoke.integer;
973 blood = cl_particles.integer && cl_particles_blood.integer;
974 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
975 qd = 1.0f / cl_particles_quality.value;
982 if (effectnameindex == EFFECT_TR_BLOOD)
984 if (cl_particles_quake.integer)
986 color = particlepalette[67 + (rand()&3)];
987 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);
992 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);
995 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
997 if (cl_particles_quake.integer)
1000 color = particlepalette[67 + (rand()&3)];
1001 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);
1006 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);
1012 if (effectnameindex == EFFECT_TR_ROCKET)
1014 if (cl_particles_quake.integer)
1017 color = particlepalette[ramp3[r]];
1018 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);
1022 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);
1023 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);
1026 else if (effectnameindex == EFFECT_TR_GRENADE)
1028 if (cl_particles_quake.integer)
1031 color = particlepalette[ramp3[r]];
1032 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);
1036 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);
1039 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1041 if (cl_particles_quake.integer)
1044 color = particlepalette[52 + (rand()&7)];
1045 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);
1046 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 else if (gamemode == GAME_GOODVSBAD2)
1051 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);
1055 color = particlepalette[20 + (rand()&7)];
1056 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);
1059 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1061 if (cl_particles_quake.integer)
1064 color = particlepalette[230 + (rand()&7)];
1065 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);
1066 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);
1070 color = particlepalette[226 + (rand()&7)];
1071 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);
1074 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1076 if (cl_particles_quake.integer)
1078 color = particlepalette[152 + (rand()&3)];
1079 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);
1081 else if (gamemode == GAME_GOODVSBAD2)
1084 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);
1086 else if (gamemode == GAME_PRYDON)
1089 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);
1092 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);
1094 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1097 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);
1099 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1102 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);
1104 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1105 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);
1109 if (effectnameindex == EFFECT_TR_ROCKET)
1110 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);
1111 else if (effectnameindex == EFFECT_TR_GRENADE)
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);
1114 // advance to next time and position
1117 VectorMA (pos, dec, dir, pos);
1120 ent->persistent.trail_time = len;
1122 else if (developer.integer >= 1)
1123 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1126 // this is also called on point effects with spawndlight = true and
1127 // spawnparticles = true
1128 // it is called CL_ParticleTrail because most code does not want to supply
1129 // these parameters, only trail handling does
1130 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)
1133 qboolean found = false;
1134 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1136 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1137 return; // no such effect
1139 VectorLerp(originmins, 0.5, originmaxs, center);
1140 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1142 int effectinfoindex;
1145 particleeffectinfo_t *info;
1147 vec3_t centervelocity;
1153 qboolean underwater;
1154 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1155 VectorLerp(originmins, 0.5, originmaxs, center);
1156 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1157 supercontents = CL_PointSuperContents(center);
1158 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1159 VectorSubtract(originmaxs, originmins, traildir);
1160 traillen = VectorLength(traildir);
1161 VectorNormalize(traildir);
1162 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1164 if (info->effectnameindex == effectnameindex)
1167 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1169 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1172 // spawn a dlight if requested
1173 if (info->lightradiusstart > 0 && spawndlight)
1175 matrix4x4_t tempmatrix;
1176 if (info->trailspacing > 0)
1177 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1179 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1180 if (info->lighttime > 0 && info->lightradiusfade > 0)
1182 // light flash (explosion, etc)
1183 // called when effect starts
1184 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);
1189 // called by CL_LinkNetworkEntity
1190 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1191 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);
1195 if (!spawnparticles)
1200 if (info->tex[1] > info->tex[0])
1202 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1203 tex = min(tex, info->tex[1] - 1);
1205 if (info->particletype == pt_decal)
1206 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]);
1207 else if (info->particletype == pt_beam)
1208 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);
1211 if (!cl_particles.integer)
1213 switch (info->particletype)
1215 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1216 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1217 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1218 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1219 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1220 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1223 VectorCopy(originmins, trailpos);
1224 if (info->trailspacing > 0)
1226 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1227 trailstep = info->trailspacing / cl_particles_quality.value;
1231 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1234 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1235 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1237 if (info->tex[1] > info->tex[0])
1239 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1240 tex = min(tex, info->tex[1] - 1);
1244 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1245 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1246 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1249 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);
1251 VectorMA(trailpos, trailstep, traildir, trailpos);
1258 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1261 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)
1263 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1271 void CL_EntityParticles (const entity_t *ent)
1274 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1275 static vec3_t avelocities[NUMVERTEXNORMALS];
1276 if (!cl_particles.integer) return;
1277 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1279 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1281 if (!avelocities[0][0])
1282 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1283 avelocities[0][i] = lhrandom(0, 2.55);
1285 for (i = 0;i < NUMVERTEXNORMALS;i++)
1287 yaw = cl.time * avelocities[i][0];
1288 pitch = cl.time * avelocities[i][1];
1289 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1290 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1291 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1292 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);
1297 void CL_ReadPointFile_f (void)
1299 vec3_t org, leakorg;
1301 char *pointfile = NULL, *pointfilepos, *t, tchar;
1302 char name[MAX_OSPATH];
1307 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1308 strlcat (name, ".pts", sizeof (name));
1309 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1312 Con_Printf("Could not open %s\n", name);
1316 Con_Printf("Reading %s...\n", name);
1317 VectorClear(leakorg);
1320 pointfilepos = pointfile;
1321 while (*pointfilepos)
1323 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1328 while (*t && *t != '\n' && *t != '\r')
1332 #if _MSC_VER >= 1400
1333 #define sscanf sscanf_s
1335 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1341 VectorCopy(org, leakorg);
1344 if (cl.num_particles < cl.max_particles - 3)
1347 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);
1350 Mem_Free(pointfile);
1351 VectorCopy(leakorg, org);
1352 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1354 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);
1355 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);
1356 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);
1361 CL_ParseParticleEffect
1363 Parse an effect out of the server message
1366 void CL_ParseParticleEffect (void)
1369 int i, count, msgcount, color;
1371 MSG_ReadVector(org, cls.protocol);
1372 for (i=0 ; i<3 ; i++)
1373 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1374 msgcount = MSG_ReadByte ();
1375 color = MSG_ReadByte ();
1377 if (msgcount == 255)
1382 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1387 CL_ParticleExplosion
1391 void CL_ParticleExplosion (const vec3_t org)
1397 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1398 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1400 if (cl_particles_quake.integer)
1402 for (i = 0;i < 1024;i++)
1408 color = particlepalette[ramp1[r]];
1409 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);
1413 color = particlepalette[ramp2[r]];
1414 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);
1420 i = CL_PointSuperContents(org);
1421 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1423 if (cl_particles.integer && cl_particles_bubbles.integer)
1424 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1425 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);
1429 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1431 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1435 for (k = 0;k < 16;k++)
1438 VectorMA(org, 128, v2, v);
1439 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1440 if (trace.fraction >= 0.1)
1443 VectorSubtract(trace.endpos, org, v2);
1444 VectorScale(v2, 2.0f, v2);
1445 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);
1451 if (cl_particles_explosions_shell.integer)
1452 R_NewExplosion(org);
1457 CL_ParticleExplosion2
1461 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1464 if (!cl_particles.integer) return;
1466 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1468 k = particlepalette[colorStart + (i % colorLength)];
1469 if (cl_particles_quake.integer)
1470 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);
1472 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);
1476 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1478 if (cl_particles_sparks.integer)
1480 sparkcount *= cl_particles_quality.value;
1481 while(sparkcount-- > 0)
1482 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);
1486 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1488 if (cl_particles_smoke.integer)
1490 smokecount *= cl_particles_quality.value;
1491 while(smokecount-- > 0)
1492 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);
1496 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)
1499 if (!cl_particles.integer) return;
1501 count = (int)(count * cl_particles_quality.value);
1504 k = particlepalette[colorbase + (rand()&3)];
1505 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);
1509 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1512 float minz, maxz, lifetime = 30;
1513 if (!cl_particles.integer) return;
1514 if (dir[2] < 0) // falling
1516 minz = maxs[2] + dir[2] * 0.1;
1519 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1524 maxz = maxs[2] + dir[2] * 0.1;
1526 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1529 count = (int)(count * cl_particles_quality.value);
1534 if (!cl_particles_rain.integer) break;
1535 count *= 4; // ick, this should be in the mod or maps?
1539 k = particlepalette[colorbase + (rand()&3)];
1540 if (gamemode == GAME_GOODVSBAD2)
1541 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);
1543 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);
1547 if (!cl_particles_snow.integer) break;
1550 k = particlepalette[colorbase + (rand()&3)];
1551 if (gamemode == GAME_GOODVSBAD2)
1552 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);
1554 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);
1558 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1562 #define MAX_PARTICLETEXTURES 64
1563 // particletexture_t is a rectangle in the particlefonttexture
1564 typedef struct particletexture_s
1566 rtexture_t *texture;
1567 float s1, t1, s2, t2;
1571 static rtexturepool_t *particletexturepool;
1572 static rtexture_t *particlefonttexture;
1573 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1575 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1576 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1577 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1578 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1580 #define PARTICLETEXTURESIZE 64
1581 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1583 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1587 dz = 1 - (dx*dx+dy*dy);
1588 if (dz > 0) // it does hit the sphere
1592 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1593 VectorNormalize(normal);
1594 dot = DotProduct(normal, light);
1595 if (dot > 0.5) // interior reflection
1596 f += ((dot * 2) - 1);
1597 else if (dot < -0.5) // exterior reflection
1598 f += ((dot * -2) - 1);
1600 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1601 VectorNormalize(normal);
1602 dot = DotProduct(normal, light);
1603 if (dot > 0.5) // interior reflection
1604 f += ((dot * 2) - 1);
1605 else if (dot < -0.5) // exterior reflection
1606 f += ((dot * -2) - 1);
1608 f += 16; // just to give it a haze so you can see the outline
1609 f = bound(0, f, 255);
1610 return (unsigned char) f;
1616 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1618 int basex, basey, y;
1619 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1620 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1621 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1622 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1625 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1628 float cx, cy, dx, dy, f, iradius;
1630 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1631 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1632 iradius = 1.0f / radius;
1633 alpha *= (1.0f / 255.0f);
1634 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1636 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1640 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1645 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1646 d[0] += (int)(f * (blue - d[0]));
1647 d[1] += (int)(f * (green - d[1]));
1648 d[2] += (int)(f * (red - d[2]));
1654 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1657 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1659 data[0] = bound(minb, data[0], maxb);
1660 data[1] = bound(ming, data[1], maxg);
1661 data[2] = bound(minr, data[2], maxr);
1665 void particletextureinvert(unsigned char *data)
1668 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1670 data[0] = 255 - data[0];
1671 data[1] = 255 - data[1];
1672 data[2] = 255 - data[2];
1676 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1677 static void R_InitBloodTextures (unsigned char *particletexturedata)
1680 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1683 for (i = 0;i < 8;i++)
1685 memset(&data[0][0][0], 255, sizeof(data));
1686 for (k = 0;k < 24;k++)
1687 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1688 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1689 particletextureinvert(&data[0][0][0]);
1690 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1694 for (i = 0;i < 8;i++)
1696 memset(&data[0][0][0], 255, sizeof(data));
1698 for (j = 1;j < 10;j++)
1699 for (k = min(j, m - 1);k < m;k++)
1700 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1701 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1702 particletextureinvert(&data[0][0][0]);
1703 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1708 //uncomment this to make engine save out particle font to a tga file when run
1709 //#define DUMPPARTICLEFONT
1711 static void R_InitParticleTexture (void)
1713 int x, y, d, i, k, m;
1717 // a note: decals need to modulate (multiply) the background color to
1718 // properly darken it (stain), and they need to be able to alpha fade,
1719 // this is a very difficult challenge because it means fading to white
1720 // (no change to background) rather than black (darkening everything
1721 // behind the whole decal polygon), and to accomplish this the texture is
1722 // inverted (dark red blood on white background becomes brilliant cyan
1723 // and white on black background) so we can alpha fade it to black, then
1724 // we invert it again during the blendfunc to make it work...
1726 #ifndef DUMPPARTICLEFONT
1727 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1728 if (!particlefonttexture)
1731 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1732 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1733 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1736 for (i = 0;i < 8;i++)
1738 memset(&data[0][0][0], 255, sizeof(data));
1741 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1743 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1744 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1746 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1748 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1749 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1751 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1752 d = (noise2[y][x] - 128) * 3 + 192;
1754 d = (int)(d * (1-(dx*dx+dy*dy)));
1755 d = (d * noise1[y][x]) >> 7;
1756 d = bound(0, d, 255);
1757 data[y][x][3] = (unsigned char) d;
1764 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1768 memset(&data[0][0][0], 255, sizeof(data));
1769 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1771 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1772 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1774 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1775 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1776 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1779 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1782 memset(&data[0][0][0], 255, sizeof(data));
1783 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1785 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1786 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1788 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1789 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1790 d = bound(0, d, 255);
1791 data[y][x][3] = (unsigned char) d;
1794 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1797 memset(&data[0][0][0], 255, sizeof(data));
1798 light[0] = 1;light[1] = 1;light[2] = 1;
1799 VectorNormalize(light);
1800 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1802 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1803 // stretch upper half of bubble by +50% and shrink lower half by -50%
1804 // (this gives an elongated teardrop shape)
1806 dy = (dy - 0.5f) * 2.0f;
1808 dy = (dy - 0.5f) / 1.5f;
1809 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1811 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1812 // shrink bubble width to half
1814 data[y][x][3] = shadebubble(dx, dy, light);
1817 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1820 memset(&data[0][0][0], 255, sizeof(data));
1821 light[0] = 1;light[1] = 1;light[2] = 1;
1822 VectorNormalize(light);
1823 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1825 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1826 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1828 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1829 data[y][x][3] = shadebubble(dx, dy, light);
1832 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1834 // Blood particles and blood decals
1835 R_InitBloodTextures (particletexturedata);
1838 for (i = 0;i < 8;i++)
1840 memset(&data[0][0][0], 255, sizeof(data));
1841 for (k = 0;k < 12;k++)
1842 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1843 for (k = 0;k < 3;k++)
1844 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1845 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1846 particletextureinvert(&data[0][0][0]);
1847 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1850 #ifdef DUMPPARTICLEFONT
1851 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1854 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1856 Mem_Free(particletexturedata);
1858 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1860 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1861 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1862 particletexture[i].texture = particlefonttexture;
1863 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1864 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1865 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1866 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1869 #ifndef DUMPPARTICLEFONT
1870 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1871 if (!particletexture[tex_beam].texture)
1874 unsigned char noise3[64][64], data2[64][16][4];
1876 fractalnoise(&noise3[0][0], 64, 4);
1878 for (y = 0;y < 64;y++)
1880 dy = (y - 0.5f*64) / (64*0.5f-1);
1881 for (x = 0;x < 16;x++)
1883 dx = (x - 0.5f*16) / (16*0.5f-2);
1884 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1885 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1886 data2[y][x][3] = 255;
1890 #ifdef DUMPPARTICLEFONT
1891 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1893 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1895 particletexture[tex_beam].s1 = 0;
1896 particletexture[tex_beam].t1 = 0;
1897 particletexture[tex_beam].s2 = 1;
1898 particletexture[tex_beam].t2 = 1;
1901 static void r_part_start(void)
1904 // generate particlepalette for convenience from the main one
1905 for (i = 0;i < 256;i++)
1906 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1907 particletexturepool = R_AllocTexturePool();
1908 R_InitParticleTexture ();
1909 CL_Particles_LoadEffectInfo();
1912 static void r_part_shutdown(void)
1914 R_FreeTexturePool(&particletexturepool);
1917 static void r_part_newmap(void)
1919 CL_Particles_LoadEffectInfo();
1922 #define BATCHSIZE 256
1923 unsigned short particle_elements[BATCHSIZE*6];
1925 void R_Particles_Init (void)
1928 for (i = 0;i < BATCHSIZE;i++)
1930 particle_elements[i*6+0] = i*4+0;
1931 particle_elements[i*6+1] = i*4+1;
1932 particle_elements[i*6+2] = i*4+2;
1933 particle_elements[i*6+3] = i*4+0;
1934 particle_elements[i*6+4] = i*4+2;
1935 particle_elements[i*6+5] = i*4+3;
1938 Cvar_RegisterVariable(&r_drawparticles);
1939 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1940 Cvar_RegisterVariable(&r_drawdecals);
1941 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1942 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1945 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1947 int surfacelistindex;
1949 float *v3f, *t2f, *c4f;
1950 particletexture_t *tex;
1951 float right[3], up[3], size, ca;
1952 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1953 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1955 r_refdef.stats.decals += numsurfaces;
1956 R_Mesh_Matrix(&identitymatrix);
1957 R_Mesh_ResetTextureState();
1958 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1959 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1960 R_Mesh_ColorPointer(particle_color4f, 0, 0);
1961 R_SetupGenericShader(true);
1962 GL_DepthMask(false);
1963 GL_DepthRange(0, 1);
1964 GL_PolygonOffset(0, 0);
1966 GL_CullFace(GL_NONE);
1968 // generate all the vertices at once
1969 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1971 d = cl.decals + surfacelist[surfacelistindex];
1974 c4f = particle_color4f + 16*surfacelistindex;
1975 ca = d->alpha * alphascale;
1976 if (r_refdef.fogenabled)
1977 ca *= FogPoint_World(d->org);
1978 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1979 Vector4Copy(c4f, c4f + 4);
1980 Vector4Copy(c4f, c4f + 8);
1981 Vector4Copy(c4f, c4f + 12);
1983 // calculate vertex positions
1984 size = d->size * cl_particles_size.value;
1985 VectorVectors(d->normal, right, up);
1986 VectorScale(right, size, right);
1987 VectorScale(up, size, up);
1988 v3f = particle_vertex3f + 12*surfacelistindex;
1989 v3f[ 0] = d->org[0] - right[0] - up[0];
1990 v3f[ 1] = d->org[1] - right[1] - up[1];
1991 v3f[ 2] = d->org[2] - right[2] - up[2];
1992 v3f[ 3] = d->org[0] - right[0] + up[0];
1993 v3f[ 4] = d->org[1] - right[1] + up[1];
1994 v3f[ 5] = d->org[2] - right[2] + up[2];
1995 v3f[ 6] = d->org[0] + right[0] + up[0];
1996 v3f[ 7] = d->org[1] + right[1] + up[1];
1997 v3f[ 8] = d->org[2] + right[2] + up[2];
1998 v3f[ 9] = d->org[0] + right[0] - up[0];
1999 v3f[10] = d->org[1] + right[1] - up[1];
2000 v3f[11] = d->org[2] + right[2] - up[2];
2002 // calculate texcoords
2003 tex = &particletexture[d->texnum];
2004 t2f = particle_texcoord2f + 8*surfacelistindex;
2005 t2f[0] = tex->s1;t2f[1] = tex->t2;
2006 t2f[2] = tex->s1;t2f[3] = tex->t1;
2007 t2f[4] = tex->s2;t2f[5] = tex->t1;
2008 t2f[6] = tex->s2;t2f[7] = tex->t2;
2011 // now render the decals all at once
2012 // (this assumes they all use one particle font texture!)
2013 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2014 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2015 GL_LockArrays(0, numsurfaces*4);
2016 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2017 GL_LockArrays(0, 0);
2020 void R_DrawDecals (void)
2028 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2029 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2031 // LordHavoc: early out conditions
2032 if ((!cl.num_decals) || (!r_drawdecals.integer))
2035 decalfade = frametime * 256 / cl_decals_fadetime.value;
2036 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2037 drawdist2 = drawdist2*drawdist2;
2039 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2041 if (!decal->typeindex)
2044 if (cl.time > decal->time2 + cl_decals_time.value)
2046 decal->alpha -= decalfade;
2047 if (decal->alpha <= 0)
2053 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2055 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2056 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2062 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))
2063 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2066 decal->typeindex = 0;
2067 if (cl.free_decal > i)
2071 // reduce cl.num_decals if possible
2072 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2075 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2077 decal_t *olddecals = cl.decals;
2078 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2079 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2080 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2081 Mem_Free(olddecals);
2085 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2087 int surfacelistindex;
2088 int batchstart, batchcount;
2089 const particle_t *p;
2091 rtexture_t *texture;
2092 float *v3f, *t2f, *c4f;
2093 particletexture_t *tex;
2094 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2095 float ambient[3], diffuse[3], diffusenormal[3];
2096 vec4_t colormultiplier;
2097 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2099 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));
2101 r_refdef.stats.particles += numsurfaces;
2102 R_Mesh_Matrix(&identitymatrix);
2103 R_Mesh_ResetTextureState();
2104 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2105 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2106 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2107 R_SetupGenericShader(true);
2108 GL_DepthMask(false);
2109 GL_DepthRange(0, 1);
2110 GL_PolygonOffset(0, 0);
2112 GL_CullFace(GL_NONE);
2114 // first generate all the vertices at once
2115 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2117 p = cl.particles + surfacelist[surfacelistindex];
2119 blendmode = particletype[p->typeindex].blendmode;
2121 c4f[0] = p->color[0] * colormultiplier[0];
2122 c4f[1] = p->color[1] * colormultiplier[1];
2123 c4f[2] = p->color[2] * colormultiplier[2];
2124 c4f[3] = p->alpha * colormultiplier[3];
2129 // additive and modulate can just fade out in fog (this is correct)
2130 if (r_refdef.fogenabled)
2131 c4f[3] *= FogPoint_World(p->org);
2132 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2139 // note: lighting is not cheap!
2140 if (particletype[p->typeindex].lighting)
2142 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2143 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2144 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2145 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2147 // mix in the fog color
2148 if (r_refdef.fogenabled)
2150 fog = FogPoint_World(p->org);
2152 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2153 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2154 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2158 // copy the color into the other three vertices
2159 Vector4Copy(c4f, c4f + 4);
2160 Vector4Copy(c4f, c4f + 8);
2161 Vector4Copy(c4f, c4f + 12);
2163 size = p->size * cl_particles_size.value;
2164 tex = &particletexture[p->texnum];
2165 switch(particletype[p->typeindex].orientation)
2167 case PARTICLE_BILLBOARD:
2168 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2169 VectorScale(r_refdef.view.up, size, up);
2170 v3f[ 0] = p->org[0] - right[0] - up[0];
2171 v3f[ 1] = p->org[1] - right[1] - up[1];
2172 v3f[ 2] = p->org[2] - right[2] - up[2];
2173 v3f[ 3] = p->org[0] - right[0] + up[0];
2174 v3f[ 4] = p->org[1] - right[1] + up[1];
2175 v3f[ 5] = p->org[2] - right[2] + up[2];
2176 v3f[ 6] = p->org[0] + right[0] + up[0];
2177 v3f[ 7] = p->org[1] + right[1] + up[1];
2178 v3f[ 8] = p->org[2] + right[2] + up[2];
2179 v3f[ 9] = p->org[0] + right[0] - up[0];
2180 v3f[10] = p->org[1] + right[1] - up[1];
2181 v3f[11] = p->org[2] + right[2] - up[2];
2182 t2f[0] = tex->s1;t2f[1] = tex->t2;
2183 t2f[2] = tex->s1;t2f[3] = tex->t1;
2184 t2f[4] = tex->s2;t2f[5] = tex->t1;
2185 t2f[6] = tex->s2;t2f[7] = tex->t2;
2187 case PARTICLE_ORIENTED_DOUBLESIDED:
2188 VectorVectors(p->vel, right, up);
2189 VectorScale(right, size * p->stretch, right);
2190 VectorScale(up, size, up);
2191 v3f[ 0] = p->org[0] - right[0] - up[0];
2192 v3f[ 1] = p->org[1] - right[1] - up[1];
2193 v3f[ 2] = p->org[2] - right[2] - up[2];
2194 v3f[ 3] = p->org[0] - right[0] + up[0];
2195 v3f[ 4] = p->org[1] - right[1] + up[1];
2196 v3f[ 5] = p->org[2] - right[2] + up[2];
2197 v3f[ 6] = p->org[0] + right[0] + up[0];
2198 v3f[ 7] = p->org[1] + right[1] + up[1];
2199 v3f[ 8] = p->org[2] + right[2] + up[2];
2200 v3f[ 9] = p->org[0] + right[0] - up[0];
2201 v3f[10] = p->org[1] + right[1] - up[1];
2202 v3f[11] = p->org[2] + right[2] - up[2];
2203 t2f[0] = tex->s1;t2f[1] = tex->t2;
2204 t2f[2] = tex->s1;t2f[3] = tex->t1;
2205 t2f[4] = tex->s2;t2f[5] = tex->t1;
2206 t2f[6] = tex->s2;t2f[7] = tex->t2;
2208 case PARTICLE_SPARK:
2209 len = VectorLength(p->vel);
2210 VectorNormalize2(p->vel, up);
2211 lenfactor = p->stretch * 0.04 * len;
2212 if(lenfactor < size * 0.5)
2213 lenfactor = size * 0.5;
2214 VectorMA(p->org, -lenfactor, up, v);
2215 VectorMA(p->org, lenfactor, up, up2);
2216 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2217 t2f[0] = tex->s1;t2f[1] = tex->t2;
2218 t2f[2] = tex->s1;t2f[3] = tex->t1;
2219 t2f[4] = tex->s2;t2f[5] = tex->t1;
2220 t2f[6] = tex->s2;t2f[7] = tex->t2;
2223 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2224 VectorSubtract(p->vel, p->org, up);
2225 VectorNormalize(up);
2226 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2227 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2228 t2f[0] = 1;t2f[1] = v[0];
2229 t2f[2] = 0;t2f[3] = v[0];
2230 t2f[4] = 0;t2f[5] = v[1];
2231 t2f[6] = 1;t2f[7] = v[1];
2236 // now render batches of particles based on blendmode and texture
2239 GL_LockArrays(0, numsurfaces*4);
2242 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2244 p = cl.particles + surfacelist[surfacelistindex];
2246 if (blendmode != particletype[p->typeindex].blendmode)
2248 blendmode = particletype[p->typeindex].blendmode;
2252 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2255 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2258 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2262 if (texture != particletexture[p->texnum].texture)
2264 texture = particletexture[p->texnum].texture;
2265 R_Mesh_TexBind(0, R_GetTexture(texture));
2268 // iterate until we find a change in settings
2269 batchstart = surfacelistindex++;
2270 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2272 p = cl.particles + surfacelist[surfacelistindex];
2273 if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2277 batchcount = surfacelistindex - batchstart;
2278 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2280 GL_LockArrays(0, 0);
2283 void R_DrawParticles (void)
2286 float minparticledist;
2288 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2294 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2295 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2297 // LordHavoc: early out conditions
2298 if ((!cl.num_particles) || (!r_drawparticles.integer))
2301 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2302 gravity = frametime * cl.movevars_gravity;
2303 dvel = 1+4*frametime;
2304 decalfade = frametime * 255 / cl_decals_fadetime.value;
2305 update = frametime > 0;
2306 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2307 drawdist2 = drawdist2*drawdist2;
2309 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2313 if (cl.free_particle > i)
2314 cl.free_particle = i;
2320 if (p->delayedspawn > cl.time)
2322 p->delayedspawn = 0;
2326 p->size += p->sizeincrease * frametime;
2327 p->alpha -= p->alphafade * frametime;
2329 if (p->alpha <= 0 || p->die <= cl.time)
2332 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2334 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2336 if (p->typeindex == pt_blood)
2337 p->size += frametime * 8;
2339 p->vel[2] -= p->gravity * gravity;
2340 f = 1.0f - min(p->liquidfriction * frametime, 1);
2341 VectorScale(p->vel, f, p->vel);
2345 p->vel[2] -= p->gravity * gravity;
2348 f = 1.0f - min(p->airfriction * frametime, 1);
2349 VectorScale(p->vel, f, p->vel);
2353 VectorCopy(p->org, oldorg);
2354 VectorMA(p->org, frametime, p->vel, p->org);
2355 if (p->bounce && cl.time >= p->delayedcollisions)
2357 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);
2358 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2359 // or if the trace hit something flagged as NOIMPACT
2360 // then remove the particle
2361 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2363 VectorCopy(trace.endpos, p->org);
2364 // react if the particle hit something
2365 if (trace.fraction < 1)
2367 VectorCopy(trace.endpos, p->org);
2368 if (p->typeindex == pt_blood)
2370 // blood - splash on solid
2371 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2373 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)));
2374 if (cl_decals.integer)
2376 // create a decal for the blood splat
2377 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);
2381 else if (p->bounce < 0)
2383 // bounce -1 means remove on impact
2388 // anything else - bounce off solid
2389 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2390 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2391 if (DotProduct(p->vel, p->vel) < 0.03)
2392 VectorClear(p->vel);
2398 if (p->typeindex != pt_static)
2400 switch (p->typeindex)
2402 case pt_entityparticle:
2403 // particle that removes itself after one rendered frame
2410 a = CL_PointSuperContents(p->org);
2411 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2415 a = CL_PointSuperContents(p->org);
2416 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2420 a = CL_PointSuperContents(p->org);
2421 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2425 if (cl.time > p->time2)
2428 p->time2 = cl.time + (rand() & 3) * 0.1;
2429 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2430 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2432 a = CL_PointSuperContents(p->org);
2433 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2441 else if (p->delayedspawn)
2444 // don't render particles too close to the view (they chew fillrate)
2445 // also don't render particles behind the view (useless)
2446 // further checks to cull to the frustum would be too slow here
2447 switch(p->typeindex)
2450 // beams have no culling
2451 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2454 // anything else just has to be in front of the viewer and visible at this distance
2455 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2456 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2463 if (cl.free_particle > i)
2464 cl.free_particle = i;
2467 // reduce cl.num_particles if possible
2468 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2471 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2473 particle_t *oldparticles = cl.particles;
2474 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2475 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2476 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2477 Mem_Free(oldparticles);