2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
31 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
32 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
33 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
34 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
35 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
36 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
38 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
39 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
40 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
41 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
44 #define PARTICLEEFFECT_UNDERWATER 1
45 #define PARTICLEEFFECT_NOTUNDERWATER 2
47 typedef struct particleeffectinfo_s
49 int effectnameindex; // which effect this belongs to
50 // PARTICLEEFFECT_* bits
52 // blood effects may spawn very few particles, so proper fraction-overflow
53 // handling is very important, this variable keeps track of the fraction
54 double particleaccumulator;
55 // the math is: countabsolute + requestedcount * countmultiplier * quality
56 // absolute number of particles to spawn, often used for decals
57 // (unaffected by quality and requestedcount)
59 // multiplier for the number of particles CL_ParticleEffect was told to
60 // spawn, most effects do not really have a count and hence use 1, so
61 // this is often the actual count to spawn, not merely a multiplier
62 float countmultiplier;
63 // if > 0 this causes the particle to spawn in an evenly spaced line from
64 // originmins to originmaxs (causing them to describe a trail, not a box)
66 // type of particle to spawn (defines some aspects of behavior)
68 // range of colors to choose from in hex RRGGBB (like HTML color tags),
69 // randomly interpolated at spawn
70 unsigned int color[2];
71 // a random texture is chosen in this range (note the second value is one
72 // past the last choosable, so for example 8,16 chooses any from 8 up and
74 // if start and end of the range are the same, no randomization is done
76 // range of size values randomly chosen when spawning, plus size increase over time
78 // range of alpha values randomly chosen when spawning, plus alpha fade
80 // how long the particle should live (note it is also removed if alpha drops to 0)
82 // how much gravity affects this particle (negative makes it fly up!)
84 // how much bounce the particle has when it hits a surface
85 // if negative the particle is removed on impact
87 // if in air this friction is applied
88 // if negative the particle accelerates
90 // if in liquid (water/slime/lava) this friction is applied
91 // if negative the particle accelerates
93 // these offsets are added to the values given to particleeffect(), and
94 // then an ellipsoid-shaped jitter is added as defined by these
95 // (they are the 3 radii)
96 float originoffset[3];
97 float velocityoffset[3];
98 float originjitter[3];
99 float velocityjitter[3];
100 float velocitymultiplier;
101 // an effect can also spawn a dlight
102 float lightradiusstart;
103 float lightradiusfade;
106 qboolean lightshadow;
109 particleeffectinfo_t;
111 #define MAX_PARTICLEEFFECTNAME 256
112 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
114 #define MAX_PARTICLEEFFECTINFO 4096
116 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
118 static int particlepalette[256] =
120 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
121 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
122 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
123 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
124 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
125 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
126 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
127 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
128 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
129 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
130 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
131 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
132 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
133 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
134 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
135 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
136 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
137 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
138 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
139 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
140 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
141 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
142 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
143 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
144 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
145 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
146 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
147 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
148 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
149 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
150 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
151 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
154 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
155 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
156 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
158 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
160 // texture numbers in particle font
161 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
162 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
163 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
164 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
165 static const int tex_rainsplash = 32;
166 static const int tex_particle = 63;
167 static const int tex_bubble = 62;
168 static const int tex_raindrop = 61;
169 static const int tex_beam = 60;
171 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
172 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles and reduces their alpha"};
173 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
174 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
175 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
176 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5", "opacity of blood"};
177 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
178 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
179 cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0", "enables smoke from explosions"};
180 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
181 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
182 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
183 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
184 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
185 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
186 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
187 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"};
188 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0", "how long before decals start to fade away"};
189 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20", "how long decals take to fade away"};
192 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
198 particleeffectinfo_t *info = NULL;
199 const char *text = textstart;
201 effectinfoindex = -1;
202 for (linenumber = 1;;linenumber++)
205 for (arrayindex = 0;arrayindex < 16;arrayindex++)
206 argv[arrayindex][0] = 0;
209 if (!COM_ParseToken(&text, true))
211 if (!strcmp(com_token, "\n"))
215 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
221 #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;}
222 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
223 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
224 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
225 #define readfloat(var) checkparms(2);var = atof(argv[1])
226 if (!strcmp(argv[0], "effect"))
231 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
233 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
236 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
238 if (particleeffectname[effectnameindex][0])
240 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
245 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
249 // if we run out of names, abort
250 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
252 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
255 info = particleeffectinfo + effectinfoindex;
256 info->effectnameindex = effectnameindex;
257 info->particletype = pt_alphastatic;
258 info->tex[0] = tex_particle;
259 info->tex[1] = tex_particle;
260 info->color[0] = 0xFFFFFF;
261 info->color[1] = 0xFFFFFF;
265 info->alpha[1] = 256;
266 info->alpha[2] = 256;
267 info->time[0] = 9999;
268 info->time[1] = 9999;
269 VectorSet(info->lightcolor, 1, 1, 1);
270 info->lightshadow = true;
271 info->lighttime = 9999;
273 else if (info == NULL)
275 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
278 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
279 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
280 else if (!strcmp(argv[0], "type"))
283 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
284 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
285 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
286 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
287 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
288 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
289 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
290 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
291 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
292 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
293 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
294 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
295 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
297 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
298 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
299 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
300 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
301 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
302 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
303 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
304 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
305 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
306 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
307 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
308 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
309 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
310 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
311 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
312 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
313 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
314 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
315 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
316 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
317 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
318 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
319 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
320 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
322 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
331 int CL_ParticleEffectIndexForName(const char *name)
334 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
335 if (!strcmp(particleeffectname[i], name))
340 const char *CL_ParticleEffectNameForIndex(int i)
342 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
344 return particleeffectname[i];
347 // MUST match effectnameindex_t in client.h
348 static const char *standardeffectnames[EFFECT_TOTAL] =
372 "TE_TEI_BIGEXPLOSION",
388 void CL_Particles_LoadEffectInfo(void)
391 unsigned char *filedata;
392 fs_offset_t filesize;
393 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
394 memset(particleeffectname, 0, sizeof(particleeffectname));
395 for (i = 0;i < EFFECT_TOTAL;i++)
396 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
397 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
400 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
410 void CL_ReadPointFile_f (void);
411 void CL_Particles_Init (void)
413 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)");
414 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
416 Cvar_RegisterVariable (&cl_particles);
417 Cvar_RegisterVariable (&cl_particles_quality);
418 Cvar_RegisterVariable (&cl_particles_size);
419 Cvar_RegisterVariable (&cl_particles_quake);
420 Cvar_RegisterVariable (&cl_particles_blood);
421 Cvar_RegisterVariable (&cl_particles_blood_alpha);
422 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
423 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
424 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
425 Cvar_RegisterVariable (&cl_particles_explosions_shell);
426 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
427 Cvar_RegisterVariable (&cl_particles_smoke);
428 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
429 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
430 Cvar_RegisterVariable (&cl_particles_sparks);
431 Cvar_RegisterVariable (&cl_particles_bubbles);
432 Cvar_RegisterVariable (&cl_decals);
433 Cvar_RegisterVariable (&cl_decals_time);
434 Cvar_RegisterVariable (&cl_decals_fadetime);
437 void CL_Particles_Shutdown (void)
441 // list of all 26 parameters:
442 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
443 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
444 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
445 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
446 // palpha - opacity of particle as 0-255 (can be more than 255)
447 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
448 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
449 // pgravity - how much effect gravity has on the particle (0-1)
450 // 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
451 // px,py,pz - starting origin of particle
452 // pvx,pvy,pvz - starting velocity of particle
453 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
454 static particle_t *particle(particletype_t *ptype, 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)
459 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
460 if (cl.free_particle >= cl.max_particles)
462 part = &cl.particles[cl.free_particle++];
463 if (cl.num_particles < cl.free_particle)
464 cl.num_particles = cl.free_particle;
465 memset(part, 0, sizeof(*part));
467 l2 = (int)lhrandom(0.5, 256.5);
469 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
470 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
471 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
472 part->color[3] = 0xFF;
475 part->sizeincrease = psizeincrease;
476 part->alpha = palpha;
477 part->alphafade = palphafade;
478 part->gravity = pgravity;
479 part->bounce = pbounce;
481 part->org[0] = px + originjitter * v[0];
482 part->org[1] = py + originjitter * v[1];
483 part->org[2] = pz + originjitter * v[2];
484 part->vel[0] = pvx + velocityjitter * v[0];
485 part->vel[1] = pvy + velocityjitter * v[1];
486 part->vel[2] = pvz + velocityjitter * v[2];
488 part->airfriction = pairfriction;
489 part->liquidfriction = pliquidfriction;
493 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
496 if (!cl_decals.integer)
498 p = particle(particletype + pt_decal, color1, color2, texnum, size, 0, alpha, 0, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], normal[0], normal[1], normal[2], 0, 0, 0, 0);
503 p->ownermodel = cl.entities[p->owner].render.model;
504 VectorAdd(org, normal, p->org);
505 VectorCopy(normal, p->vel);
506 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
507 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
508 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
512 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
515 float bestfrac, bestorg[3], bestnormal[3];
517 int besthitent = 0, hitent;
520 for (i = 0;i < 32;i++)
523 VectorMA(org, maxdist, org2, org2);
524 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
525 // take the closest trace result that doesn't end up hitting a NOMARKS
526 // surface (sky for example)
527 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
529 bestfrac = trace.fraction;
531 VectorCopy(trace.endpos, bestorg);
532 VectorCopy(trace.plane.normal, bestnormal);
536 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
539 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
540 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)
543 matrix4x4_t tempmatrix;
544 VectorLerp(originmins, 0.5, originmaxs, center);
545 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
546 if (effectnameindex == EFFECT_SVC_PARTICLE)
548 if (cl_particles.integer)
550 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
552 CL_ParticleExplosion(center);
553 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
554 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
557 count *= cl_particles_quality.value;
558 for (;count > 0;count--)
560 int k = particlepalette[palettecolor + (rand()&7)];
561 if (cl_particles_quake.integer)
562 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 0, lhrandom(51, 255), 512, 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);
563 else if (gamemode == GAME_GOODVSBAD2)
564 particle(particletype + pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 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, 8, 10);
566 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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, 8, 15);
571 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
572 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
573 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
574 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
575 else if (effectnameindex == EFFECT_TE_SPIKE)
577 if (cl_particles_bulletimpacts.integer)
579 if (cl_particles_quake.integer)
581 if (cl_particles_smoke.integer)
582 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
585 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
588 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
589 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
591 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
593 if (cl_particles_bulletimpacts.integer)
595 if (cl_particles_quake.integer)
597 if (cl_particles_smoke.integer)
598 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
601 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
604 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
605 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
606 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);
608 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
610 if (cl_particles_bulletimpacts.integer)
612 if (cl_particles_quake.integer)
614 if (cl_particles_smoke.integer)
615 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
618 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
621 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
622 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
624 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
626 if (cl_particles_bulletimpacts.integer)
628 if (cl_particles_quake.integer)
630 if (cl_particles_smoke.integer)
631 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
634 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
637 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
638 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
639 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);
641 else if (effectnameindex == EFFECT_TE_BLOOD)
643 if (!cl_particles_blood.integer)
645 if (cl_particles_quake.integer)
646 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
649 static double bloodaccumulator = 0;
650 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
651 for (;bloodaccumulator > 0;bloodaccumulator--)
652 particle(particletype + 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);
655 else if (effectnameindex == EFFECT_TE_SPARK)
656 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
657 else if (effectnameindex == EFFECT_TE_PLASMABURN)
659 // plasma scorch mark
660 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
661 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
662 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
664 else if (effectnameindex == EFFECT_TE_GUNSHOT)
666 if (cl_particles_bulletimpacts.integer)
668 if (cl_particles_quake.integer)
669 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
671 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
674 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
675 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
677 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
679 if (cl_particles_bulletimpacts.integer)
681 if (cl_particles_quake.integer)
682 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
684 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
687 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
688 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
689 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);
691 else if (effectnameindex == EFFECT_TE_EXPLOSION)
693 CL_ParticleExplosion(center);
694 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);
696 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
698 CL_ParticleExplosion(center);
699 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);
701 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
703 if (cl_particles_quake.integer)
706 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
709 particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
711 particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
715 CL_ParticleExplosion(center);
716 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);
718 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
719 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);
720 else if (effectnameindex == EFFECT_TE_FLAMEJET)
722 count *= cl_particles_quality.value;
724 particle(particletype + 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);
726 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
728 float i, j, inc, vel;
731 inc = 8 / cl_particles_quality.value;
732 for (i = -128;i < 128;i += inc)
734 for (j = -128;j < 128;j += inc)
736 dir[0] = j + lhrandom(0, inc);
737 dir[1] = i + lhrandom(0, inc);
739 org[0] = center[0] + dir[0];
740 org[1] = center[1] + dir[1];
741 org[2] = center[2] + lhrandom(0, 64);
742 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
743 particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, 0, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
747 else if (effectnameindex == EFFECT_TE_TELEPORT)
749 float i, j, k, inc, vel;
752 inc = 8 / cl_particles_quality.value;
753 for (i = -16;i < 16;i += inc)
755 for (j = -16;j < 16;j += inc)
757 for (k = -24;k < 32;k += inc)
759 VectorSet(dir, i*8, j*8, k*8);
760 VectorNormalize(dir);
761 vel = lhrandom(50, 113);
762 particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, 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);
766 particle(particletype + pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
767 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);
769 else if (effectnameindex == EFFECT_TE_TEI_G3)
770 particle(particletype + 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);
771 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
773 if (cl_particles_smoke.integer)
775 count *= 0.25f * cl_particles_quality.value;
777 particle(particletype + 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);
780 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
782 CL_ParticleExplosion(center);
783 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);
785 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
788 if (cl_stainmaps.integer)
789 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
790 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
791 if (cl_particles_smoke.integer)
792 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
793 particle(particletype + 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);
794 if (cl_particles_sparks.integer)
795 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
796 particle(particletype + 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);
797 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);
799 else if (effectnameindex == EFFECT_EF_FLAME)
801 count *= 300 * cl_particles_quality.value;
803 particle(particletype + 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);
804 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);
806 else if (effectnameindex == EFFECT_EF_STARDUST)
808 count *= 200 * cl_particles_quality.value;
810 particle(particletype + 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);
811 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);
813 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
817 int smoke, blood, bubbles, r, color;
819 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
822 Vector4Set(light, 0, 0, 0, 0);
824 if (effectnameindex == EFFECT_TR_ROCKET)
825 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
826 else if (effectnameindex == EFFECT_TR_VORESPIKE)
828 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
829 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
831 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
833 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
834 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
838 matrix4x4_t tempmatrix;
839 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
840 R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
847 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
850 VectorSubtract(originmaxs, originmins, dir);
851 len = VectorNormalizeLength(dir);
852 dec = -ent->persistent.trail_time;
853 ent->persistent.trail_time += len;
854 if (ent->persistent.trail_time < 0.01f)
857 // if we skip out, leave it reset
858 ent->persistent.trail_time = 0.0f;
860 // advance into this frame to reach the first puff location
861 VectorMA(originmins, dec, dir, pos);
864 smoke = cl_particles.integer && cl_particles_smoke.integer;
865 blood = cl_particles.integer && cl_particles_blood.integer;
866 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
867 qd = 1.0f / cl_particles_quality.value;
874 if (effectnameindex == EFFECT_TR_BLOOD)
876 if (cl_particles_quake.integer)
878 color = particlepalette[67 + (rand()&3)];
879 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
884 particle(particletype + 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);
887 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
889 if (cl_particles_quake.integer)
892 color = particlepalette[67 + (rand()&3)];
893 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
898 particle(particletype + 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);
904 if (effectnameindex == EFFECT_TR_ROCKET)
906 if (cl_particles_quake.integer)
909 color = particlepalette[ramp3[r]];
910 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
914 particle(particletype + 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);
915 particle(particletype + 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);
918 else if (effectnameindex == EFFECT_TR_GRENADE)
920 if (cl_particles_quake.integer)
923 color = particlepalette[ramp3[r]];
924 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
928 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*50, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
931 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
933 if (cl_particles_quake.integer)
936 color = particlepalette[52 + (rand()&7)];
937 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
938 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
940 else if (gamemode == GAME_GOODVSBAD2)
943 particle(particletype + 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);
947 color = particlepalette[20 + (rand()&7)];
948 particle(particletype + 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);
951 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
953 if (cl_particles_quake.integer)
956 color = particlepalette[230 + (rand()&7)];
957 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
958 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
962 color = particlepalette[226 + (rand()&7)];
963 particle(particletype + 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);
966 else if (effectnameindex == EFFECT_TR_VORESPIKE)
968 if (cl_particles_quake.integer)
970 color = particlepalette[152 + (rand()&3)];
971 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
973 else if (gamemode == GAME_GOODVSBAD2)
976 particle(particletype + 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);
978 else if (gamemode == GAME_PRYDON)
981 particle(particletype + 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);
984 particle(particletype + 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);
986 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
989 particle(particletype + 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);
991 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
994 particle(particletype + 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);
996 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
997 particle(particletype + 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);
1001 if (effectnameindex == EFFECT_TR_ROCKET)
1002 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
1003 else if (effectnameindex == EFFECT_TR_GRENADE)
1004 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
1006 // advance to next time and position
1009 VectorMA (pos, dec, dir, pos);
1011 ent->persistent.trail_time = len;
1013 else if (developer.integer >= 1)
1014 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1017 // this is also called on point effects with spawndlight = true and
1018 // spawnparticles = true
1019 // it is called CL_ParticleTrail because most code does not want to supply
1020 // these parameters, only trail handling does
1021 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)
1024 qboolean found = false;
1025 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1026 return; // invalid effect index
1027 if (!particleeffectname[effectnameindex][0])
1028 return; // no such effect
1029 VectorLerp(originmins, 0.5, originmaxs, center);
1030 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1032 int effectinfoindex;
1035 particleeffectinfo_t *info;
1037 vec3_t centervelocity;
1043 qboolean underwater;
1044 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1045 VectorLerp(originmins, 0.5, originmaxs, center);
1046 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1047 supercontents = CL_PointSuperContents(center);
1048 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1049 VectorSubtract(originmaxs, originmins, traildir);
1050 traillen = VectorLength(traildir);
1051 VectorNormalize(traildir);
1052 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1054 if (info->effectnameindex == effectnameindex)
1057 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1059 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1062 // spawn a dlight if requested
1063 if (info->lightradiusstart > 0 && spawndlight)
1065 matrix4x4_t tempmatrix;
1066 if (info->trailspacing > 0)
1067 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1069 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1070 if (info->lighttime > 0 && info->lightradiusfade > 0)
1072 // light flash (explosion, etc)
1073 // called when effect starts
1074 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);
1079 // called by CL_LinkNetworkEntity
1080 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1081 R_RTLight_Update(&r_refdef.lights[r_refdef.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);
1085 if (!spawnparticles)
1090 if (info->tex[1] > info->tex[0])
1092 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1093 tex = min(tex, info->tex[1] - 1);
1095 if (info->particletype == pt_decal)
1096 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]);
1097 else if (info->particletype == pt_beam)
1098 particle(particletype + 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);
1101 if (!cl_particles.integer)
1103 switch (info->particletype)
1105 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1106 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1107 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1108 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1111 VectorCopy(originmins, trailpos);
1112 if (info->trailspacing > 0)
1114 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1115 trailstep = info->trailspacing / cl_particles_quality.value;
1119 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1122 for (;info->particleaccumulator > 0;info->particleaccumulator--)
1124 if (info->tex[1] > info->tex[0])
1126 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1127 tex = min(tex, info->tex[1] - 1);
1131 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1132 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1133 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1136 particle(particletype + 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);
1138 VectorMA(trailpos, trailstep, traildir, trailpos);
1145 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1148 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)
1150 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1158 void CL_EntityParticles (const entity_t *ent)
1161 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1162 static vec3_t avelocities[NUMVERTEXNORMALS];
1163 if (!cl_particles.integer) return;
1165 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1167 if (!avelocities[0][0])
1168 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1169 avelocities[0][i] = lhrandom(0, 2.55);
1171 for (i = 0;i < NUMVERTEXNORMALS;i++)
1173 yaw = cl.time * avelocities[i][0];
1174 pitch = cl.time * avelocities[i][1];
1175 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1176 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1177 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1178 particle(particletype + 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);
1183 void CL_ReadPointFile_f (void)
1185 vec3_t org, leakorg;
1187 char *pointfile = NULL, *pointfilepos, *t, tchar;
1188 char name[MAX_OSPATH];
1193 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1194 strlcat (name, ".pts", sizeof (name));
1195 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1198 Con_Printf("Could not open %s\n", name);
1202 Con_Printf("Reading %s...\n", name);
1203 VectorClear(leakorg);
1206 pointfilepos = pointfile;
1207 while (*pointfilepos)
1209 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1214 while (*t && *t != '\n' && *t != '\r')
1218 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1224 VectorCopy(org, leakorg);
1227 if (cl.num_particles < cl.max_particles - 3)
1230 particle(particletype + pt_static, 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);
1233 Mem_Free(pointfile);
1234 VectorCopy(leakorg, org);
1235 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1237 particle(particletype + 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);
1238 particle(particletype + 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);
1239 particle(particletype + 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);
1244 CL_ParseParticleEffect
1246 Parse an effect out of the server message
1249 void CL_ParseParticleEffect (void)
1252 int i, count, msgcount, color;
1254 MSG_ReadVector(org, cls.protocol);
1255 for (i=0 ; i<3 ; i++)
1256 dir[i] = MSG_ReadChar ();
1257 msgcount = MSG_ReadByte ();
1258 color = MSG_ReadByte ();
1260 if (msgcount == 255)
1265 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1270 CL_ParticleExplosion
1274 void CL_ParticleExplosion (const vec3_t org)
1280 if (cl_stainmaps.integer)
1281 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1282 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1284 if (cl_particles_quake.integer)
1286 for (i = 0;i < 1024;i++)
1292 color = particlepalette[ramp1[r]];
1293 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
1297 color = particlepalette[ramp2[r]];
1298 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
1304 i = CL_PointSuperContents(org);
1305 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1307 if (cl_particles.integer && cl_particles_bubbles.integer)
1308 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1309 particle(particletype + 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);
1313 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1315 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1317 for (i = 0;i < 32;i++)
1321 for (k = 0;k < 16;k++)
1323 v[0] = org[0] + lhrandom(-48, 48);
1324 v[1] = org[1] + lhrandom(-48, 48);
1325 v[2] = org[2] + lhrandom(-48, 48);
1326 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
1327 if (trace.fraction >= 0.1)
1330 VectorSubtract(trace.endpos, org, v2);
1331 VectorScale(v2, 2.0f, v2);
1332 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 0, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
1336 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1337 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1338 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 1, 0, org[0], org[1], org[2], 0, 0, 80, 0.2, 0.8, 0, 256);
1342 if (cl_particles_explosions_shell.integer)
1343 R_NewExplosion(org);
1348 CL_ParticleExplosion2
1352 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1355 if (!cl_particles.integer) return;
1357 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1359 k = particlepalette[colorStart + (i % colorLength)];
1360 if (cl_particles_quake.integer)
1361 particle(particletype + pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256);
1363 particle(particletype + pt_static, 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);
1367 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1369 if (cl_particles_sparks.integer)
1371 sparkcount *= cl_particles_quality.value;
1372 while(sparkcount-- > 0)
1373 particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.4f, 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]) + sv_gravity.value * 0.1, 0, 0, 0, 64);
1375 if (cl_particles_smoke.integer)
1377 smokecount *= cl_particles_quality.value;
1378 while(smokecount-- > 0)
1379 particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, 0, 255, 1024, 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, 8);
1383 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)
1386 if (!cl_particles.integer) return;
1388 count = (int)(count * cl_particles_quality.value);
1391 k = particlepalette[colorbase + (rand()&3)];
1392 particle(particletype + 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);
1396 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1399 float z, minz, maxz;
1401 if (!cl_particles.integer) return;
1402 if (dir[2] < 0) // falling
1407 minz = z - fabs(dir[2]) * 0.1;
1408 maxz = z + fabs(dir[2]) * 0.1;
1409 minz = bound(mins[2], minz, maxs[2]);
1410 maxz = bound(mins[2], maxz, maxs[2]);
1412 count = (int)(count * cl_particles_quality.value);
1417 count *= 4; // ick, this should be in the mod or maps?
1421 k = particlepalette[colorbase + (rand()&3)];
1422 if (gamemode == GAME_GOODVSBAD2)
1423 particle(particletype + pt_rain, k, k, tex_particle, 20, 0, lhrandom(8, 16), 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);
1425 particle(particletype + pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(8, 16), 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);
1431 k = particlepalette[colorbase + (rand()&3)];
1432 if (gamemode == GAME_GOODVSBAD2)
1433 p = particle(particletype + 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);
1435 p = particle(particletype + 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);
1437 VectorCopy(p->vel, p->relativedirection);
1441 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1450 void CL_MoveParticles (void)
1453 int i, maxparticle, j, a, content;
1454 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1455 particletype_t *decaltype, *bloodtype;
1459 // LordHavoc: early out condition
1460 if (!cl.num_particles)
1462 cl.free_particle = 0;
1466 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1467 gravity = frametime * sv_gravity.value;
1468 dvel = 1+4*frametime;
1469 decalfade = frametime * 255 / cl_decals_fadetime.value;
1470 decaltype = particletype + pt_decal;
1471 bloodtype = particletype + pt_blood;
1475 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1479 if (cl.free_particle > i)
1480 cl.free_particle = i;
1485 // heavily optimized decal case
1486 if (p->type == decaltype)
1488 // FIXME: this has fairly wacky handling of alpha
1489 if (cl.time > p->time2 + cl_decals_time.value)
1491 p->alpha -= decalfade;
1495 if (cl.free_particle > i)
1496 cl.free_particle = i;
1502 if (cl.entities[p->owner].render.model == p->ownermodel)
1504 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1505 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1510 if (cl.free_particle > i)
1511 cl.free_particle = i;
1519 p->alpha -= p->alphafade * frametime;
1524 if (cl.free_particle > i)
1525 cl.free_particle = i;
1529 if (p->type->orientation != PARTICLE_BEAM)
1531 VectorCopy(p->org, oldorg);
1532 VectorMA(p->org, frametime, p->vel, p->org);
1533 VectorCopy(p->org, org);
1536 trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | (p->type == particletype + pt_rain ? SUPERCONTENTS_LIQUIDSMASK : 0), false);
1537 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1538 // or if the trace hit something flagged as NOIMPACT
1539 // then remove the particle
1540 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1545 // react if the particle hit something
1546 if (trace.fraction < 1)
1548 VectorCopy(trace.endpos, p->org);
1549 if (p->type == particletype + pt_rain)
1551 // raindrop - splash on solid/water/slime/lava
1553 // convert from a raindrop particle to a rainsplash decal
1554 VectorCopy(trace.plane.normal, p->vel);
1555 VectorAdd(p->org, p->vel, p->org);
1556 p->type = particletype + pt_raindecal;
1557 p->texnum = tex_rainsplash;
1559 p->alphafade = p->alpha / 0.4;
1562 p->liquidfriction = 0;
1565 p->sizeincrease = p->size * 16;
1568 particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, 32 + p->vel[2]*16, 0, 0, 0, 32);
1570 else if (p->type == bloodtype)
1572 // blood - splash on solid
1573 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1578 if (!cl_decals.integer)
1583 // convert from a blood particle to a blood decal
1584 VectorCopy(trace.plane.normal, p->vel);
1585 VectorAdd(p->org, p->vel, p->org);
1586 if (cl_stainmaps.integer)
1587 R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
1589 p->type = particletype + pt_decal;
1590 p->texnum = tex_blooddecal[rand()&7];
1592 p->ownermodel = cl.entities[hitent].render.model;
1593 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1594 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1595 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1600 p->liquidfriction = 0;
1604 else if (p->bounce < 0)
1606 // bounce -1 means remove on impact
1612 // anything else - bounce off solid
1613 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1614 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1615 if (DotProduct(p->vel, p->vel) < 0.03)
1616 VectorClear(p->vel);
1620 p->vel[2] -= p->gravity * gravity;
1622 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1624 f = 1.0f - min(p->liquidfriction * frametime, 1);
1625 VectorScale(p->vel, f, p->vel);
1627 else if (p->airfriction)
1629 f = 1.0f - min(p->airfriction * frametime, 1);
1630 VectorScale(p->vel, f, p->vel);
1634 if (p->type != particletype + pt_static)
1636 switch (p->type - particletype)
1638 case pt_entityparticle:
1639 // particle that removes itself after one rendered frame
1646 a = CL_PointSuperContents(p->org);
1647 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1649 p->size += frametime * 8;
1650 //p->alpha -= bloodwaterfade;
1653 p->vel[2] -= gravity;
1654 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1658 a = CL_PointSuperContents(p->org);
1659 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1666 a = CL_PointSuperContents(p->org);
1667 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1671 if (cl.time > p->time2)
1674 p->time2 = cl.time + (rand() & 3) * 0.1;
1675 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1676 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1677 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1679 a = CL_PointSuperContents(p->org);
1680 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1688 cl.num_particles = maxparticle + 1;
1691 #define MAX_PARTICLETEXTURES 64
1692 // particletexture_t is a rectangle in the particlefonttexture
1693 typedef struct particletexture_s
1695 rtexture_t *texture;
1696 float s1, t1, s2, t2;
1700 static rtexturepool_t *particletexturepool;
1701 static rtexture_t *particlefonttexture;
1702 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1704 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1706 #define PARTICLETEXTURESIZE 64
1707 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1709 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1713 dz = 1 - (dx*dx+dy*dy);
1714 if (dz > 0) // it does hit the sphere
1718 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1719 VectorNormalize(normal);
1720 dot = DotProduct(normal, light);
1721 if (dot > 0.5) // interior reflection
1722 f += ((dot * 2) - 1);
1723 else if (dot < -0.5) // exterior reflection
1724 f += ((dot * -2) - 1);
1726 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1727 VectorNormalize(normal);
1728 dot = DotProduct(normal, light);
1729 if (dot > 0.5) // interior reflection
1730 f += ((dot * 2) - 1);
1731 else if (dot < -0.5) // exterior reflection
1732 f += ((dot * -2) - 1);
1734 f += 16; // just to give it a haze so you can see the outline
1735 f = bound(0, f, 255);
1736 return (unsigned char) f;
1742 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1744 int basex, basey, y;
1745 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1746 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1747 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1748 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1751 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1754 float cx, cy, dx, dy, f, iradius;
1756 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1757 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1758 iradius = 1.0f / radius;
1759 alpha *= (1.0f / 255.0f);
1760 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1762 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1766 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1769 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1770 d[0] += (int)(f * (red - d[0]));
1771 d[1] += (int)(f * (green - d[1]));
1772 d[2] += (int)(f * (blue - d[2]));
1778 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1781 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1783 data[0] = bound(minr, data[0], maxr);
1784 data[1] = bound(ming, data[1], maxg);
1785 data[2] = bound(minb, data[2], maxb);
1789 void particletextureinvert(unsigned char *data)
1792 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1794 data[0] = 255 - data[0];
1795 data[1] = 255 - data[1];
1796 data[2] = 255 - data[2];
1800 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1801 static void R_InitBloodTextures (unsigned char *particletexturedata)
1804 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1807 for (i = 0;i < 8;i++)
1809 memset(&data[0][0][0], 255, sizeof(data));
1810 for (k = 0;k < 24;k++)
1811 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1812 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1813 particletextureinvert(&data[0][0][0]);
1814 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1818 for (i = 0;i < 8;i++)
1820 memset(&data[0][0][0], 255, sizeof(data));
1822 for (j = 1;j < 10;j++)
1823 for (k = min(j, m - 1);k < m;k++)
1824 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1825 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1826 particletextureinvert(&data[0][0][0]);
1827 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1832 //uncomment this to make engine save out particle font to a tga file when run
1833 //#define DUMPPARTICLEFONT
1835 static void R_InitParticleTexture (void)
1837 int x, y, d, i, k, m;
1841 // a note: decals need to modulate (multiply) the background color to
1842 // properly darken it (stain), and they need to be able to alpha fade,
1843 // this is a very difficult challenge because it means fading to white
1844 // (no change to background) rather than black (darkening everything
1845 // behind the whole decal polygon), and to accomplish this the texture is
1846 // inverted (dark red blood on white background becomes brilliant cyan
1847 // and white on black background) so we can alpha fade it to black, then
1848 // we invert it again during the blendfunc to make it work...
1850 #ifndef DUMPPARTICLEFONT
1851 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1852 if (!particlefonttexture)
1855 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1856 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1857 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1860 for (i = 0;i < 8;i++)
1862 memset(&data[0][0][0], 255, sizeof(data));
1865 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1867 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1868 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1870 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1872 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1873 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1875 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1876 d = (noise2[y][x] - 128) * 3 + 192;
1878 d = (int)(d * (1-(dx*dx+dy*dy)));
1879 d = (d * noise1[y][x]) >> 7;
1880 d = bound(0, d, 255);
1881 data[y][x][3] = (unsigned char) d;
1888 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1892 memset(&data[0][0][0], 255, sizeof(data));
1893 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1895 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1896 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1898 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1899 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1900 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1903 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1906 memset(&data[0][0][0], 255, sizeof(data));
1907 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1909 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1910 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1912 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1913 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1914 d = bound(0, d, 255);
1915 data[y][x][3] = (unsigned char) d;
1918 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1921 memset(&data[0][0][0], 255, sizeof(data));
1922 light[0] = 1;light[1] = 1;light[2] = 1;
1923 VectorNormalize(light);
1924 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1926 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1927 // stretch upper half of bubble by +50% and shrink lower half by -50%
1928 // (this gives an elongated teardrop shape)
1930 dy = (dy - 0.5f) * 2.0f;
1932 dy = (dy - 0.5f) / 1.5f;
1933 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1935 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1936 // shrink bubble width to half
1938 data[y][x][3] = shadebubble(dx, dy, light);
1941 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1944 memset(&data[0][0][0], 255, sizeof(data));
1945 light[0] = 1;light[1] = 1;light[2] = 1;
1946 VectorNormalize(light);
1947 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1949 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1950 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1952 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1953 data[y][x][3] = shadebubble(dx, dy, light);
1956 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1958 // Blood particles and blood decals
1959 R_InitBloodTextures (particletexturedata);
1962 for (i = 0;i < 8;i++)
1964 memset(&data[0][0][0], 255, sizeof(data));
1965 for (k = 0;k < 12;k++)
1966 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1967 for (k = 0;k < 3;k++)
1968 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1969 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1970 particletextureinvert(&data[0][0][0]);
1971 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1974 #ifdef DUMPPARTICLEFONT
1975 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1978 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1980 Mem_Free(particletexturedata);
1982 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1984 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1985 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1986 particletexture[i].texture = particlefonttexture;
1987 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1988 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1989 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1990 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1993 #ifndef DUMPPARTICLEFONT
1994 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1995 if (!particletexture[tex_beam].texture)
1998 unsigned char noise3[64][64], data2[64][16][4];
2000 fractalnoise(&noise3[0][0], 64, 4);
2002 for (y = 0;y < 64;y++)
2004 dy = (y - 0.5f*64) / (64*0.5f-1);
2005 for (x = 0;x < 16;x++)
2007 dx = (x - 0.5f*16) / (16*0.5f-2);
2008 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2009 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2010 data2[y][x][3] = 255;
2014 #ifdef DUMPPARTICLEFONT
2015 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2017 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2019 particletexture[tex_beam].s1 = 0;
2020 particletexture[tex_beam].t1 = 0;
2021 particletexture[tex_beam].s2 = 1;
2022 particletexture[tex_beam].t2 = 1;
2025 static void r_part_start(void)
2027 particletexturepool = R_AllocTexturePool();
2028 R_InitParticleTexture ();
2029 CL_Particles_LoadEffectInfo();
2032 static void r_part_shutdown(void)
2034 R_FreeTexturePool(&particletexturepool);
2037 static void r_part_newmap(void)
2041 #define BATCHSIZE 256
2042 int particle_element3i[BATCHSIZE*6];
2043 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2045 void R_Particles_Init (void)
2048 for (i = 0;i < BATCHSIZE;i++)
2050 particle_element3i[i*6+0] = i*4+0;
2051 particle_element3i[i*6+1] = i*4+1;
2052 particle_element3i[i*6+2] = i*4+2;
2053 particle_element3i[i*6+3] = i*4+0;
2054 particle_element3i[i*6+4] = i*4+2;
2055 particle_element3i[i*6+5] = i*4+3;
2058 Cvar_RegisterVariable(&r_drawparticles);
2059 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2062 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2064 int surfacelistindex;
2065 int batchstart, batchcount;
2066 const particle_t *p;
2068 rtexture_t *texture;
2069 float *v3f, *t2f, *c4f;
2071 R_Mesh_Matrix(&identitymatrix);
2072 R_Mesh_ResetTextureState();
2073 R_Mesh_VertexPointer(particle_vertex3f);
2074 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2075 R_Mesh_ColorPointer(particle_color4f);
2076 GL_DepthMask(false);
2078 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2080 // first generate all the vertices at once
2081 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2083 particletexture_t *tex;
2085 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2087 p = cl.particles + surfacelist[surfacelistindex];
2089 blendmode = p->type->blendmode;
2091 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2092 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2093 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2094 ca = p->alpha * (1.0f / 255.0f);
2095 if (blendmode == PBLEND_MOD)
2105 ca /= cl_particles_quality.value;
2106 if (p->type->lighting)
2108 float ambient[3], diffuse[3], diffusenormal[3];
2109 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2110 cr *= (ambient[0] + 0.5 * diffuse[0]);
2111 cg *= (ambient[1] + 0.5 * diffuse[1]);
2112 cb *= (ambient[2] + 0.5 * diffuse[2]);
2114 if (r_refdef.fogenabled)
2116 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2121 if (blendmode == PBLEND_ALPHA)
2123 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2124 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2125 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2128 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2129 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2130 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2131 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2133 size = p->size * cl_particles_size.value;
2135 tex = &particletexture[p->texnum];
2136 if (p->type->orientation == PARTICLE_BILLBOARD)
2138 VectorScale(r_view.left, -size, right);
2139 VectorScale(r_view.up, size, up);
2140 v3f[ 0] = org[0] - right[0] - up[0];
2141 v3f[ 1] = org[1] - right[1] - up[1];
2142 v3f[ 2] = org[2] - right[2] - up[2];
2143 v3f[ 3] = org[0] - right[0] + up[0];
2144 v3f[ 4] = org[1] - right[1] + up[1];
2145 v3f[ 5] = org[2] - right[2] + up[2];
2146 v3f[ 6] = org[0] + right[0] + up[0];
2147 v3f[ 7] = org[1] + right[1] + up[1];
2148 v3f[ 8] = org[2] + right[2] + up[2];
2149 v3f[ 9] = org[0] + right[0] - up[0];
2150 v3f[10] = org[1] + right[1] - up[1];
2151 v3f[11] = org[2] + right[2] - up[2];
2152 t2f[0] = tex->s1;t2f[1] = tex->t2;
2153 t2f[2] = tex->s1;t2f[3] = tex->t1;
2154 t2f[4] = tex->s2;t2f[5] = tex->t1;
2155 t2f[6] = tex->s2;t2f[7] = tex->t2;
2157 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2160 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2162 VectorNegate(p->vel, v);
2163 VectorVectors(v, right, up);
2166 VectorVectors(p->vel, right, up);
2167 VectorScale(right, size, right);
2168 VectorScale(up, size, up);
2169 v3f[ 0] = org[0] - right[0] - up[0];
2170 v3f[ 1] = org[1] - right[1] - up[1];
2171 v3f[ 2] = org[2] - right[2] - up[2];
2172 v3f[ 3] = org[0] - right[0] + up[0];
2173 v3f[ 4] = org[1] - right[1] + up[1];
2174 v3f[ 5] = org[2] - right[2] + up[2];
2175 v3f[ 6] = org[0] + right[0] + up[0];
2176 v3f[ 7] = org[1] + right[1] + up[1];
2177 v3f[ 8] = org[2] + right[2] + up[2];
2178 v3f[ 9] = org[0] + right[0] - up[0];
2179 v3f[10] = org[1] + right[1] - up[1];
2180 v3f[11] = org[2] + right[2] - up[2];
2181 t2f[0] = tex->s1;t2f[1] = tex->t2;
2182 t2f[2] = tex->s1;t2f[3] = tex->t1;
2183 t2f[4] = tex->s2;t2f[5] = tex->t1;
2184 t2f[6] = tex->s2;t2f[7] = tex->t2;
2186 else if (p->type->orientation == PARTICLE_SPARK)
2188 VectorMA(org, -0.02, p->vel, v);
2189 VectorMA(org, 0.02, p->vel, up2);
2190 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2191 t2f[0] = tex->s1;t2f[1] = tex->t2;
2192 t2f[2] = tex->s1;t2f[3] = tex->t1;
2193 t2f[4] = tex->s2;t2f[5] = tex->t1;
2194 t2f[6] = tex->s2;t2f[7] = tex->t2;
2196 else if (p->type->orientation == PARTICLE_BEAM)
2198 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2199 VectorSubtract(p->vel, org, up);
2200 VectorNormalize(up);
2201 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2202 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2203 t2f[0] = 1;t2f[1] = v[0];
2204 t2f[2] = 0;t2f[3] = v[0];
2205 t2f[4] = 0;t2f[5] = v[1];
2206 t2f[6] = 1;t2f[7] = v[1];
2210 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2215 // now render batches of particles based on blendmode and texture
2216 blendmode = PBLEND_ADD;
2217 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2218 texture = particletexture[63].texture;
2219 R_Mesh_TexBind(0, R_GetTexture(texture));
2220 GL_LockArrays(0, numsurfaces*4);
2223 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2225 p = cl.particles + surfacelist[surfacelistindex];
2227 if (blendmode != p->type->blendmode)
2230 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2232 batchstart = surfacelistindex;
2233 blendmode = p->type->blendmode;
2234 if (blendmode == PBLEND_ALPHA)
2235 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2236 else if (blendmode == PBLEND_ADD)
2237 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2238 else //if (blendmode == PBLEND_MOD)
2239 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2241 if (texture != particletexture[p->texnum].texture)
2244 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2246 batchstart = surfacelistindex;
2247 texture = particletexture[p->texnum].texture;
2248 R_Mesh_TexBind(0, R_GetTexture(texture));
2254 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2255 GL_LockArrays(0, 0);
2258 void R_DrawParticles (void)
2261 float minparticledist;
2264 // LordHavoc: early out conditions
2265 if ((!cl.num_particles) || (!r_drawparticles.integer))
2268 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2270 // LordHavoc: only render if not too close
2271 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2275 r_refdef.stats.particles++;
2276 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2277 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);