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 {0, 0, false}, // pt_dead
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // range of colors to choose from in hex RRGGBB (like HTML color tags),
70 // randomly interpolated at spawn
71 unsigned int color[2];
72 // a random texture is chosen in this range (note the second value is one
73 // past the last choosable, so for example 8,16 chooses any from 8 up and
75 // if start and end of the range are the same, no randomization is done
77 // range of size values randomly chosen when spawning, plus size increase over time
79 // range of alpha values randomly chosen when spawning, plus alpha fade
81 // how long the particle should live (note it is also removed if alpha drops to 0)
83 // how much gravity affects this particle (negative makes it fly up!)
85 // how much bounce the particle has when it hits a surface
86 // if negative the particle is removed on impact
88 // if in air this friction is applied
89 // if negative the particle accelerates
91 // if in liquid (water/slime/lava) this friction is applied
92 // if negative the particle accelerates
94 // these offsets are added to the values given to particleeffect(), and
95 // then an ellipsoid-shaped jitter is added as defined by these
96 // (they are the 3 radii)
97 float originoffset[3];
98 float velocityoffset[3];
99 float originjitter[3];
100 float velocityjitter[3];
101 float velocitymultiplier;
102 // an effect can also spawn a dlight
103 float lightradiusstart;
104 float lightradiusfade;
107 qboolean lightshadow;
110 particleeffectinfo_t;
112 #define MAX_PARTICLEEFFECTNAME 256
113 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
115 #define MAX_PARTICLEEFFECTINFO 4096
117 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
119 static int particlepalette[256] =
121 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
122 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
123 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
124 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
125 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
126 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
127 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
128 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
129 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
130 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
131 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
132 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
133 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
134 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
135 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
136 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
137 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
138 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
139 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
140 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
141 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
142 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
143 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
144 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
145 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
146 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
147 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
148 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
149 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
150 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
151 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
152 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
155 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
156 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
157 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
159 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
161 // texture numbers in particle font
162 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
163 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
164 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
165 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
166 static const int tex_rainsplash = 32;
167 static const int tex_particle = 63;
168 static const int tex_bubble = 62;
169 static const int tex_raindrop = 61;
170 static const int tex_beam = 60;
172 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
173 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
174 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
175 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
176 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
177 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
178 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
179 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
180 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
181 cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0", "enables smoke from explosions"};
182 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
183 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
184 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
185 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
186 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
187 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
188 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
189 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
190 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
191 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
192 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
193 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
196 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
202 particleeffectinfo_t *info = NULL;
203 const char *text = textstart;
205 effectinfoindex = -1;
206 for (linenumber = 1;;linenumber++)
209 for (arrayindex = 0;arrayindex < 16;arrayindex++)
210 argv[arrayindex][0] = 0;
213 if (!COM_ParseToken_Simple(&text, true, false))
215 if (!strcmp(com_token, "\n"))
219 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
225 #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;}
226 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
227 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
228 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
229 #define readfloat(var) checkparms(2);var = atof(argv[1])
230 if (!strcmp(argv[0], "effect"))
235 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
237 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
240 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
242 if (particleeffectname[effectnameindex][0])
244 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
249 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
253 // if we run out of names, abort
254 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
256 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
259 info = particleeffectinfo + effectinfoindex;
260 info->effectnameindex = effectnameindex;
261 info->particletype = pt_alphastatic;
262 info->tex[0] = tex_particle;
263 info->tex[1] = tex_particle;
264 info->color[0] = 0xFFFFFF;
265 info->color[1] = 0xFFFFFF;
269 info->alpha[1] = 256;
270 info->alpha[2] = 256;
271 info->time[0] = 9999;
272 info->time[1] = 9999;
273 VectorSet(info->lightcolor, 1, 1, 1);
274 info->lightshadow = true;
275 info->lighttime = 9999;
277 else if (info == NULL)
279 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
282 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
283 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
284 else if (!strcmp(argv[0], "type"))
287 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
288 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
289 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
290 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
291 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
292 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
293 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
294 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
295 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
296 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
297 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
298 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
299 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
301 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
302 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
303 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
304 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
305 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
306 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
307 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
308 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
309 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
310 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
311 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
312 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
313 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
314 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
315 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
316 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
317 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
318 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
319 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
320 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
321 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
322 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
323 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
324 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
326 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
335 int CL_ParticleEffectIndexForName(const char *name)
338 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
339 if (!strcmp(particleeffectname[i], name))
344 const char *CL_ParticleEffectNameForIndex(int i)
346 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
348 return particleeffectname[i];
351 // MUST match effectnameindex_t in client.h
352 static const char *standardeffectnames[EFFECT_TOTAL] =
376 "TE_TEI_BIGEXPLOSION",
392 void CL_Particles_LoadEffectInfo(void)
395 unsigned char *filedata;
396 fs_offset_t filesize;
397 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
398 memset(particleeffectname, 0, sizeof(particleeffectname));
399 for (i = 0;i < EFFECT_TOTAL;i++)
400 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
401 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
404 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
414 void CL_ReadPointFile_f (void);
415 void CL_Particles_Init (void)
417 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)");
418 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
420 Cvar_RegisterVariable (&cl_particles);
421 Cvar_RegisterVariable (&cl_particles_quality);
422 Cvar_RegisterVariable (&cl_particles_alpha);
423 Cvar_RegisterVariable (&cl_particles_size);
424 Cvar_RegisterVariable (&cl_particles_quake);
425 Cvar_RegisterVariable (&cl_particles_blood);
426 Cvar_RegisterVariable (&cl_particles_blood_alpha);
427 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
428 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
429 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
430 Cvar_RegisterVariable (&cl_particles_explosions_shell);
431 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
432 Cvar_RegisterVariable (&cl_particles_rain);
433 Cvar_RegisterVariable (&cl_particles_snow);
434 Cvar_RegisterVariable (&cl_particles_smoke);
435 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
436 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
437 Cvar_RegisterVariable (&cl_particles_sparks);
438 Cvar_RegisterVariable (&cl_particles_bubbles);
439 Cvar_RegisterVariable (&cl_decals);
440 Cvar_RegisterVariable (&cl_decals_time);
441 Cvar_RegisterVariable (&cl_decals_fadetime);
444 void CL_Particles_Shutdown (void)
448 // list of all 26 parameters:
449 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
450 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
451 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
452 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
453 // palpha - opacity of particle as 0-255 (can be more than 255)
454 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
455 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
456 // pgravity - how much effect gravity has on the particle (0-1)
457 // 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
458 // px,py,pz - starting origin of particle
459 // pvx,pvy,pvz - starting velocity of particle
460 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
461 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)
466 if (!cl_particles.integer)
468 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
469 if (cl.free_particle >= cl.max_particles)
471 part = &cl.particles[cl.free_particle++];
472 if (cl.num_particles < cl.free_particle)
473 cl.num_particles = cl.free_particle;
474 memset(part, 0, sizeof(*part));
475 part->typeindex = ptypeindex;
476 l2 = (int)lhrandom(0.5, 256.5);
478 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
479 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
480 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
481 part->color[3] = 0xFF;
484 part->sizeincrease = psizeincrease;
485 part->alpha = palpha;
486 part->alphafade = palphafade;
487 part->gravity = pgravity;
488 part->bounce = pbounce;
490 part->org[0] = px + originjitter * v[0];
491 part->org[1] = py + originjitter * v[1];
492 part->org[2] = pz + originjitter * v[2];
493 part->vel[0] = pvx + velocityjitter * v[0];
494 part->vel[1] = pvy + velocityjitter * v[1];
495 part->vel[2] = pvz + velocityjitter * v[2];
497 part->airfriction = pairfriction;
498 part->liquidfriction = pliquidfriction;
499 part->die = cl.time + part->alpha / (part->alphafade ? part->alphafade : 1);
500 part->delayedcollisions = 0;
501 if (part->typeindex == pt_blood)
502 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)
503 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
504 if (part->typeindex == pt_rain)
508 float lifetime = part->die - cl.time;
511 // turn raindrop into simple spark and create delayedspawn splash effect
512 part->typeindex = pt_spark;
514 VectorMA(part->org, lifetime, part->vel, endvec);
515 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
516 part->die = cl.time + lifetime * trace.fraction;
517 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);
520 part2->delayedspawn = part->die;
521 part2->die += part->die - cl.time;
522 for (i = rand() & 7;i < 10;i++)
524 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);
527 part2->delayedspawn = part->die;
528 part2->die += part->die - cl.time;
533 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
535 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
538 VectorMA(part->org, lifetime, part->vel, endvec);
539 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
540 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
545 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
549 if (!cl_decals.integer)
551 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
552 if (cl.free_decal >= cl.max_decals)
554 decal = &cl.decals[cl.free_decal++];
555 if (cl.num_decals < cl.free_decal)
556 cl.num_decals = cl.free_decal;
557 memset(decal, 0, sizeof(*decal));
558 decal->typeindex = pt_decal;
559 decal->texnum = texnum;
560 VectorAdd(org, normal, decal->org);
561 VectorCopy(normal, decal->normal);
563 decal->alpha = alpha;
564 decal->time2 = cl.time;
565 l2 = (int)lhrandom(0.5, 256.5);
567 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
568 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
569 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
570 decal->color[3] = 0xFF;
571 decal->owner = hitent;
574 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
575 decal->ownermodel = cl.entities[decal->owner].render.model;
576 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
577 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
581 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
584 float bestfrac, bestorg[3], bestnormal[3];
586 int besthitent = 0, hitent;
589 for (i = 0;i < 32;i++)
592 VectorMA(org, maxdist, org2, org2);
593 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
594 // take the closest trace result that doesn't end up hitting a NOMARKS
595 // surface (sky for example)
596 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
598 bestfrac = trace.fraction;
600 VectorCopy(trace.endpos, bestorg);
601 VectorCopy(trace.plane.normal, bestnormal);
605 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
608 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
609 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
610 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)
613 matrix4x4_t tempmatrix;
614 VectorLerp(originmins, 0.5, originmaxs, center);
615 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
616 if (effectnameindex == EFFECT_SVC_PARTICLE)
618 if (cl_particles.integer)
620 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
622 CL_ParticleExplosion(center);
623 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
624 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
627 count *= cl_particles_quality.value;
628 for (;count > 0;count--)
630 int k = particlepalette[palettecolor + (rand()&7)];
631 if (cl_particles_quake.integer)
632 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 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);
633 else if (gamemode == GAME_GOODVSBAD2)
634 CL_NewParticle(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);
636 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.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, 8, 15);
641 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
642 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
643 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
644 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
645 else if (effectnameindex == EFFECT_TE_SPIKE)
647 if (cl_particles_bulletimpacts.integer)
649 if (cl_particles_quake.integer)
651 if (cl_particles_smoke.integer)
652 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
656 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
657 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
661 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
662 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
664 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
666 if (cl_particles_bulletimpacts.integer)
668 if (cl_particles_quake.integer)
670 if (cl_particles_smoke.integer)
671 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
675 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
676 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
680 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
681 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
682 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);
684 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
686 if (cl_particles_bulletimpacts.integer)
688 if (cl_particles_quake.integer)
690 if (cl_particles_smoke.integer)
691 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
695 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
696 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
700 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
701 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
703 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
705 if (cl_particles_bulletimpacts.integer)
707 if (cl_particles_quake.integer)
709 if (cl_particles_smoke.integer)
710 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
714 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
715 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
719 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
720 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
721 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);
723 else if (effectnameindex == EFFECT_TE_BLOOD)
725 if (!cl_particles_blood.integer)
727 if (cl_particles_quake.integer)
728 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
731 static double bloodaccumulator = 0;
732 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
733 for (;bloodaccumulator > 0;bloodaccumulator--)
734 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);
737 else if (effectnameindex == EFFECT_TE_SPARK)
738 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
739 else if (effectnameindex == EFFECT_TE_PLASMABURN)
741 // plasma scorch mark
742 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
743 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
744 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
746 else if (effectnameindex == EFFECT_TE_GUNSHOT)
748 if (cl_particles_bulletimpacts.integer)
750 if (cl_particles_quake.integer)
751 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
754 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
755 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
759 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
760 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
762 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
764 if (cl_particles_bulletimpacts.integer)
766 if (cl_particles_quake.integer)
767 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
770 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
771 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
775 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
776 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
777 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);
779 else if (effectnameindex == EFFECT_TE_EXPLOSION)
781 CL_ParticleExplosion(center);
782 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);
784 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
786 CL_ParticleExplosion(center);
787 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);
789 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
791 if (cl_particles_quake.integer)
794 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
797 CL_NewParticle(pt_static, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
799 CL_NewParticle(pt_static, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
803 CL_ParticleExplosion(center);
804 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);
806 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
807 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);
808 else if (effectnameindex == EFFECT_TE_FLAMEJET)
810 count *= cl_particles_quality.value;
812 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);
814 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
816 float i, j, inc, vel;
819 inc = 8 / cl_particles_quality.value;
820 for (i = -128;i < 128;i += inc)
822 for (j = -128;j < 128;j += inc)
824 dir[0] = j + lhrandom(0, inc);
825 dir[1] = i + lhrandom(0, inc);
827 org[0] = center[0] + dir[0];
828 org[1] = center[1] + dir[1];
829 org[2] = center[2] + lhrandom(0, 64);
830 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
831 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 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);
835 else if (effectnameindex == EFFECT_TE_TELEPORT)
837 float i, j, k, inc, vel;
840 inc = 8 / cl_particles_quality.value;
841 for (i = -16;i < 16;i += inc)
843 for (j = -16;j < 16;j += inc)
845 for (k = -24;k < 32;k += inc)
847 VectorSet(dir, i*8, j*8, k*8);
848 VectorNormalize(dir);
849 vel = lhrandom(50, 113);
850 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);
854 CL_NewParticle(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);
855 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);
857 else if (effectnameindex == EFFECT_TE_TEI_G3)
858 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);
859 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
861 if (cl_particles_smoke.integer)
863 count *= 0.25f * cl_particles_quality.value;
865 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);
868 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
870 CL_ParticleExplosion(center);
871 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);
873 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
876 if (cl_stainmaps.integer)
877 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
878 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
879 if (cl_particles_smoke.integer)
880 for (f = 0;f < count;f += 4.0f / 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, 20, 155);
882 if (cl_particles_sparks.integer)
883 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
884 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);
885 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);
887 else if (effectnameindex == EFFECT_EF_FLAME)
889 count *= 300 * cl_particles_quality.value;
891 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);
892 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);
894 else if (effectnameindex == EFFECT_EF_STARDUST)
896 count *= 200 * cl_particles_quality.value;
898 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);
899 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);
901 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
905 int smoke, blood, bubbles, r, color;
907 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
910 Vector4Set(light, 0, 0, 0, 0);
912 if (effectnameindex == EFFECT_TR_ROCKET)
913 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
914 else if (effectnameindex == EFFECT_TR_VORESPIKE)
916 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
917 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
919 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
921 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
922 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
926 matrix4x4_t tempmatrix;
927 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
928 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);
935 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
938 VectorSubtract(originmaxs, originmins, dir);
939 len = VectorNormalizeLength(dir);
942 dec = -ent->persistent.trail_time;
943 ent->persistent.trail_time += len;
944 if (ent->persistent.trail_time < 0.01f)
947 // if we skip out, leave it reset
948 ent->persistent.trail_time = 0.0f;
953 // advance into this frame to reach the first puff location
954 VectorMA(originmins, dec, dir, pos);
957 smoke = cl_particles.integer && cl_particles_smoke.integer;
958 blood = cl_particles.integer && cl_particles_blood.integer;
959 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
960 qd = 1.0f / cl_particles_quality.value;
967 if (effectnameindex == EFFECT_TR_BLOOD)
969 if (cl_particles_quake.integer)
971 color = particlepalette[67 + (rand()&3)];
972 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
977 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);
980 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
982 if (cl_particles_quake.integer)
985 color = particlepalette[67 + (rand()&3)];
986 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
991 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);
997 if (effectnameindex == EFFECT_TR_ROCKET)
999 if (cl_particles_quake.integer)
1002 color = particlepalette[ramp3[r]];
1003 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
1007 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);
1008 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);
1011 else if (effectnameindex == EFFECT_TR_GRENADE)
1013 if (cl_particles_quake.integer)
1016 color = particlepalette[ramp3[r]];
1017 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
1021 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);
1024 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1026 if (cl_particles_quake.integer)
1029 color = particlepalette[52 + (rand()&7)];
1030 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
1031 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
1033 else if (gamemode == GAME_GOODVSBAD2)
1036 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);
1040 color = particlepalette[20 + (rand()&7)];
1041 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);
1044 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1046 if (cl_particles_quake.integer)
1049 color = particlepalette[230 + (rand()&7)];
1050 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
1051 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
1055 color = particlepalette[226 + (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);
1059 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1061 if (cl_particles_quake.integer)
1063 color = particlepalette[152 + (rand()&3)];
1064 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
1066 else if (gamemode == GAME_GOODVSBAD2)
1069 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);
1071 else if (gamemode == GAME_PRYDON)
1074 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);
1077 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);
1079 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1082 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);
1084 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1087 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);
1089 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1090 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);
1094 if (effectnameindex == EFFECT_TR_ROCKET)
1095 CL_NewParticle(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);
1096 else if (effectnameindex == EFFECT_TR_GRENADE)
1097 CL_NewParticle(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);
1099 // advance to next time and position
1102 VectorMA (pos, dec, dir, pos);
1105 ent->persistent.trail_time = len;
1107 else if (developer.integer >= 1)
1108 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1111 // this is also called on point effects with spawndlight = true and
1112 // spawnparticles = true
1113 // it is called CL_ParticleTrail because most code does not want to supply
1114 // these parameters, only trail handling does
1115 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)
1118 qboolean found = false;
1119 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1120 return; // invalid effect index
1121 if (!particleeffectname[effectnameindex][0])
1122 return; // no such effect
1123 VectorLerp(originmins, 0.5, originmaxs, center);
1124 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1126 int effectinfoindex;
1129 particleeffectinfo_t *info;
1131 vec3_t centervelocity;
1137 qboolean underwater;
1138 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1139 VectorLerp(originmins, 0.5, originmaxs, center);
1140 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1141 supercontents = CL_PointSuperContents(center);
1142 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1143 VectorSubtract(originmaxs, originmins, traildir);
1144 traillen = VectorLength(traildir);
1145 VectorNormalize(traildir);
1146 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1148 if (info->effectnameindex == effectnameindex)
1151 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1153 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1156 // spawn a dlight if requested
1157 if (info->lightradiusstart > 0 && spawndlight)
1159 matrix4x4_t tempmatrix;
1160 if (info->trailspacing > 0)
1161 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1163 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1164 if (info->lighttime > 0 && info->lightradiusfade > 0)
1166 // light flash (explosion, etc)
1167 // called when effect starts
1168 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);
1173 // called by CL_LinkNetworkEntity
1174 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1175 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);
1179 if (!spawnparticles)
1184 if (info->tex[1] > info->tex[0])
1186 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1187 tex = min(tex, info->tex[1] - 1);
1189 if (info->particletype == pt_decal)
1190 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]);
1191 else if (info->particletype == pt_beam)
1192 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);
1195 if (!cl_particles.integer)
1197 switch (info->particletype)
1199 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1200 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1201 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1202 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1203 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1204 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1207 VectorCopy(originmins, trailpos);
1208 if (info->trailspacing > 0)
1210 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1211 trailstep = info->trailspacing / cl_particles_quality.value;
1215 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1218 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1219 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1221 if (info->tex[1] > info->tex[0])
1223 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1224 tex = min(tex, info->tex[1] - 1);
1228 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1229 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1230 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1233 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);
1235 VectorMA(trailpos, trailstep, traildir, trailpos);
1242 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1245 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)
1247 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1255 void CL_EntityParticles (const entity_t *ent)
1258 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1259 static vec3_t avelocities[NUMVERTEXNORMALS];
1260 if (!cl_particles.integer) return;
1262 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1264 if (!avelocities[0][0])
1265 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1266 avelocities[0][i] = lhrandom(0, 2.55);
1268 for (i = 0;i < NUMVERTEXNORMALS;i++)
1270 yaw = cl.time * avelocities[i][0];
1271 pitch = cl.time * avelocities[i][1];
1272 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1273 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1274 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1275 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);
1280 void CL_ReadPointFile_f (void)
1282 vec3_t org, leakorg;
1284 char *pointfile = NULL, *pointfilepos, *t, tchar;
1285 char name[MAX_OSPATH];
1290 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1291 strlcat (name, ".pts", sizeof (name));
1292 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1295 Con_Printf("Could not open %s\n", name);
1299 Con_Printf("Reading %s...\n", name);
1300 VectorClear(leakorg);
1303 pointfilepos = pointfile;
1304 while (*pointfilepos)
1306 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1311 while (*t && *t != '\n' && *t != '\r')
1315 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1321 VectorCopy(org, leakorg);
1324 if (cl.num_particles < cl.max_particles - 3)
1327 CL_NewParticle(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);
1330 Mem_Free(pointfile);
1331 VectorCopy(leakorg, org);
1332 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1334 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);
1335 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);
1336 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);
1341 CL_ParseParticleEffect
1343 Parse an effect out of the server message
1346 void CL_ParseParticleEffect (void)
1349 int i, count, msgcount, color;
1351 MSG_ReadVector(org, cls.protocol);
1352 for (i=0 ; i<3 ; i++)
1353 dir[i] = MSG_ReadChar ();
1354 msgcount = MSG_ReadByte ();
1355 color = MSG_ReadByte ();
1357 if (msgcount == 255)
1362 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1367 CL_ParticleExplosion
1371 void CL_ParticleExplosion (const vec3_t org)
1377 if (cl_stainmaps.integer)
1378 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1379 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1381 if (cl_particles_quake.integer)
1383 for (i = 0;i < 1024;i++)
1389 color = particlepalette[ramp1[r]];
1390 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
1394 color = particlepalette[ramp2[r]];
1395 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
1401 i = CL_PointSuperContents(org);
1402 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1404 if (cl_particles.integer && cl_particles_bubbles.integer)
1405 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1406 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);
1410 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1412 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1414 for (i = 0;i < 32;i++)
1418 for (k = 0;k < 16;k++)
1420 v[0] = org[0] + lhrandom(-48, 48);
1421 v[1] = org[1] + lhrandom(-48, 48);
1422 v[2] = org[2] + lhrandom(-48, 48);
1423 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1424 if (trace.fraction >= 0.1)
1427 VectorSubtract(trace.endpos, org, v2);
1428 VectorScale(v2, 2.0f, v2);
1429 CL_NewParticle(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);
1433 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1435 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1439 for (k = 0;k < 16;k++)
1442 VectorMA(org, 128, v2, v);
1443 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1444 if (trace.fraction >= 0.1)
1447 VectorSubtract(trace.endpos, org, v2);
1448 VectorScale(v2, 2.0f, v2);
1449 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);
1455 if (cl_particles_explosions_shell.integer)
1456 R_NewExplosion(org);
1461 CL_ParticleExplosion2
1465 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1468 if (!cl_particles.integer) return;
1470 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1472 k = particlepalette[colorStart + (i % colorLength)];
1473 if (cl_particles_quake.integer)
1474 CL_NewParticle(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);
1476 CL_NewParticle(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);
1480 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1482 if (cl_particles_sparks.integer)
1484 sparkcount *= cl_particles_quality.value;
1485 while(sparkcount-- > 0)
1486 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);
1490 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1492 if (cl_particles_smoke.integer)
1494 smokecount *= cl_particles_quality.value;
1495 while(smokecount-- > 0)
1496 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);
1500 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)
1503 if (!cl_particles.integer) return;
1505 count = (int)(count * cl_particles_quality.value);
1508 k = particlepalette[colorbase + (rand()&3)];
1509 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);
1513 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1516 float z, minz, maxz;
1517 if (!cl_particles.integer) return;
1518 if (dir[2] < 0) // falling
1523 minz = z - fabs(dir[2]) * 0.1;
1524 maxz = z + fabs(dir[2]) * 0.1;
1525 minz = bound(mins[2], minz, maxs[2]);
1526 maxz = bound(mins[2], maxz, maxs[2]);
1528 count = (int)(count * cl_particles_quality.value);
1533 if (!cl_particles_rain.integer) break;
1534 count *= 4; // ick, this should be in the mod or maps?
1538 k = particlepalette[colorbase + (rand()&3)];
1539 if (gamemode == GAME_GOODVSBAD2)
1540 CL_NewParticle(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);
1542 CL_NewParticle(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);
1546 if (!cl_particles_snow.integer) break;
1549 k = particlepalette[colorbase + (rand()&3)];
1550 if (gamemode == GAME_GOODVSBAD2)
1551 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);
1553 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);
1557 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1566 void CL_MoveDecals (void)
1572 // LordHavoc: early out condition
1579 decalfade = bound(0, cl.time - cl.oldtime, 0.1) * 255 / cl_decals_fadetime.value;
1581 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
1583 if (!decal->typeindex)
1586 // heavily optimized decal case
1587 // FIXME: this has fairly wacky handling of alpha
1588 if (cl.time > decal->time2 + cl_decals_time.value)
1590 decal->alpha -= decalfade;
1591 if (decal->alpha <= 0)
1593 decal->typeindex = 0;
1594 if (cl.free_decal > i)
1602 if (cl.entities[decal->owner].render.model == decal->ownermodel)
1604 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
1605 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
1609 decal->typeindex = 0;
1610 if (cl.free_decal > i)
1616 // reduce cl.num_decals if possible
1617 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
1626 void CL_MoveParticles (void)
1629 int i, j, a, content;
1630 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
1634 // LordHavoc: early out condition
1635 if (!cl.num_particles)
1637 cl.free_particle = 0;
1641 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1642 gravity = frametime * cl.movevars_gravity;
1643 dvel = 1+4*frametime;
1644 decalfade = frametime * 255 / cl_decals_fadetime.value;
1647 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1651 if (cl.free_particle > i)
1652 cl.free_particle = i;
1656 if (p->delayedspawn)
1658 if (p->delayedspawn > cl.time)
1660 p->delayedspawn = 0;
1665 p->size += p->sizeincrease * frametime;
1666 p->alpha -= p->alphafade * frametime;
1668 if (p->alpha <= 0 || p->die <= cl.time)
1671 if (cl.free_particle > i)
1672 cl.free_particle = i;
1676 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
1678 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
1680 if (p->typeindex == pt_blood)
1681 p->size += frametime * 8;
1683 p->vel[2] -= p->gravity * gravity;
1684 f = 1.0f - min(p->liquidfriction * frametime, 1);
1685 VectorScale(p->vel, f, p->vel);
1689 p->vel[2] -= p->gravity * gravity;
1692 f = 1.0f - min(p->airfriction * frametime, 1);
1693 VectorScale(p->vel, f, p->vel);
1697 VectorCopy(p->org, oldorg);
1698 VectorMA(p->org, frametime, p->vel, p->org);
1699 if (p->bounce && cl.time >= p->delayedcollisions)
1701 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);
1702 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1703 // or if the trace hit something flagged as NOIMPACT
1704 // then remove the particle
1705 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
1708 if (cl.free_particle > i)
1709 cl.free_particle = i;
1712 VectorCopy(trace.endpos, p->org);
1713 // react if the particle hit something
1714 if (trace.fraction < 1)
1716 VectorCopy(trace.endpos, p->org);
1717 if (p->typeindex == pt_rain)
1719 // raindrop - splash on solid/water/slime/lava
1721 // convert from a raindrop particle to a rainsplash decal
1722 VectorCopy(trace.plane.normal, p->vel);
1723 VectorAdd(p->org, p->vel, p->org);
1724 p->typeindex = pt_raindecal;
1725 p->texnum = tex_rainsplash;
1727 p->alphafade = p->alpha / 0.4;
1730 p->liquidfriction = 0;
1733 p->sizeincrease = p->size * 20;
1734 count = (int)lhrandom(1, 10);
1736 CL_NewParticle(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, cl.movevars_gravity * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
1739 else if (p->typeindex == pt_blood)
1741 // blood - splash on solid
1742 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1747 if (cl_stainmaps.integer)
1748 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)));
1749 if (!cl_decals.integer)
1754 // create a decal for the blood splat
1755 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);
1757 if (cl.free_particle > i)
1758 cl.free_particle = i;
1761 else if (p->bounce < 0)
1763 // bounce -1 means remove on impact
1765 if (cl.free_particle > i)
1766 cl.free_particle = i;
1771 // anything else - bounce off solid
1772 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1773 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1774 if (DotProduct(p->vel, p->vel) < 0.03)
1775 VectorClear(p->vel);
1781 if (p->typeindex != pt_static)
1783 switch (p->typeindex)
1785 case pt_entityparticle:
1786 // particle that removes itself after one rendered frame
1790 if (cl.free_particle > i)
1791 cl.free_particle = i;
1797 a = CL_PointSuperContents(p->org);
1798 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1801 if (cl.free_particle > i)
1802 cl.free_particle = i;
1806 a = CL_PointSuperContents(p->org);
1807 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1810 if (cl.free_particle > i)
1811 cl.free_particle = i;
1815 a = CL_PointSuperContents(p->org);
1816 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1819 if (cl.free_particle > i)
1820 cl.free_particle = i;
1824 if (cl.time > p->time2)
1827 p->time2 = cl.time + (rand() & 3) * 0.1;
1828 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
1829 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
1831 a = CL_PointSuperContents(p->org);
1832 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1835 if (cl.free_particle > i)
1836 cl.free_particle = i;
1845 // reduce cl.num_particles if possible
1846 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
1850 #define MAX_PARTICLETEXTURES 64
1851 // particletexture_t is a rectangle in the particlefonttexture
1852 typedef struct particletexture_s
1854 rtexture_t *texture;
1855 float s1, t1, s2, t2;
1859 static rtexturepool_t *particletexturepool;
1860 static rtexture_t *particlefonttexture;
1861 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1863 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1864 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1866 #define PARTICLETEXTURESIZE 64
1867 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1869 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1873 dz = 1 - (dx*dx+dy*dy);
1874 if (dz > 0) // it does hit the sphere
1878 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1879 VectorNormalize(normal);
1880 dot = DotProduct(normal, light);
1881 if (dot > 0.5) // interior reflection
1882 f += ((dot * 2) - 1);
1883 else if (dot < -0.5) // exterior reflection
1884 f += ((dot * -2) - 1);
1886 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1887 VectorNormalize(normal);
1888 dot = DotProduct(normal, light);
1889 if (dot > 0.5) // interior reflection
1890 f += ((dot * 2) - 1);
1891 else if (dot < -0.5) // exterior reflection
1892 f += ((dot * -2) - 1);
1894 f += 16; // just to give it a haze so you can see the outline
1895 f = bound(0, f, 255);
1896 return (unsigned char) f;
1902 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1904 int basex, basey, y;
1905 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1906 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1907 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1908 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1911 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1914 float cx, cy, dx, dy, f, iradius;
1916 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1917 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1918 iradius = 1.0f / radius;
1919 alpha *= (1.0f / 255.0f);
1920 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1922 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1926 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1931 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1932 d[0] += (int)(f * (red - d[0]));
1933 d[1] += (int)(f * (green - d[1]));
1934 d[2] += (int)(f * (blue - d[2]));
1940 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1943 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1945 data[0] = bound(minr, data[0], maxr);
1946 data[1] = bound(ming, data[1], maxg);
1947 data[2] = bound(minb, data[2], maxb);
1951 void particletextureinvert(unsigned char *data)
1954 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1956 data[0] = 255 - data[0];
1957 data[1] = 255 - data[1];
1958 data[2] = 255 - data[2];
1962 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1963 static void R_InitBloodTextures (unsigned char *particletexturedata)
1966 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1969 for (i = 0;i < 8;i++)
1971 memset(&data[0][0][0], 255, sizeof(data));
1972 for (k = 0;k < 24;k++)
1973 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1974 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1975 particletextureinvert(&data[0][0][0]);
1976 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1980 for (i = 0;i < 8;i++)
1982 memset(&data[0][0][0], 255, sizeof(data));
1984 for (j = 1;j < 10;j++)
1985 for (k = min(j, m - 1);k < m;k++)
1986 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1987 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1988 particletextureinvert(&data[0][0][0]);
1989 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1994 //uncomment this to make engine save out particle font to a tga file when run
1995 //#define DUMPPARTICLEFONT
1997 static void R_InitParticleTexture (void)
1999 int x, y, d, i, k, m;
2003 // a note: decals need to modulate (multiply) the background color to
2004 // properly darken it (stain), and they need to be able to alpha fade,
2005 // this is a very difficult challenge because it means fading to white
2006 // (no change to background) rather than black (darkening everything
2007 // behind the whole decal polygon), and to accomplish this the texture is
2008 // inverted (dark red blood on white background becomes brilliant cyan
2009 // and white on black background) so we can alpha fade it to black, then
2010 // we invert it again during the blendfunc to make it work...
2012 #ifndef DUMPPARTICLEFONT
2013 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE, true);
2014 if (!particlefonttexture)
2017 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2018 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
2019 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2022 for (i = 0;i < 8;i++)
2024 memset(&data[0][0][0], 255, sizeof(data));
2027 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
2029 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2030 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2032 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2034 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2035 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2037 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2038 d = (noise2[y][x] - 128) * 3 + 192;
2040 d = (int)(d * (1-(dx*dx+dy*dy)));
2041 d = (d * noise1[y][x]) >> 7;
2042 d = bound(0, d, 255);
2043 data[y][x][3] = (unsigned char) d;
2050 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
2054 memset(&data[0][0][0], 255, sizeof(data));
2055 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2057 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2058 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2060 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2061 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2062 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
2065 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
2068 memset(&data[0][0][0], 255, sizeof(data));
2069 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2071 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2072 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2074 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2075 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2076 d = bound(0, d, 255);
2077 data[y][x][3] = (unsigned char) d;
2080 setuptex(tex_particle, &data[0][0][0], particletexturedata);
2083 memset(&data[0][0][0], 255, sizeof(data));
2084 light[0] = 1;light[1] = 1;light[2] = 1;
2085 VectorNormalize(light);
2086 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2088 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2089 // stretch upper half of bubble by +50% and shrink lower half by -50%
2090 // (this gives an elongated teardrop shape)
2092 dy = (dy - 0.5f) * 2.0f;
2094 dy = (dy - 0.5f) / 1.5f;
2095 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2097 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2098 // shrink bubble width to half
2100 data[y][x][3] = shadebubble(dx, dy, light);
2103 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2106 memset(&data[0][0][0], 255, sizeof(data));
2107 light[0] = 1;light[1] = 1;light[2] = 1;
2108 VectorNormalize(light);
2109 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2111 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2112 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2114 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2115 data[y][x][3] = shadebubble(dx, dy, light);
2118 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2120 // Blood particles and blood decals
2121 R_InitBloodTextures (particletexturedata);
2124 for (i = 0;i < 8;i++)
2126 memset(&data[0][0][0], 255, sizeof(data));
2127 for (k = 0;k < 12;k++)
2128 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2129 for (k = 0;k < 3;k++)
2130 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2131 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2132 particletextureinvert(&data[0][0][0]);
2133 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2136 #ifdef DUMPPARTICLEFONT
2137 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2140 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
2142 Mem_Free(particletexturedata);
2144 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2146 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2147 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2148 particletexture[i].texture = particlefonttexture;
2149 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2150 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2151 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2152 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2155 #ifndef DUMPPARTICLEFONT
2156 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE, true);
2157 if (!particletexture[tex_beam].texture)
2160 unsigned char noise3[64][64], data2[64][16][4];
2162 fractalnoise(&noise3[0][0], 64, 4);
2164 for (y = 0;y < 64;y++)
2166 dy = (y - 0.5f*64) / (64*0.5f-1);
2167 for (x = 0;x < 16;x++)
2169 dx = (x - 0.5f*16) / (16*0.5f-2);
2170 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2171 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2172 data2[y][x][3] = 255;
2176 #ifdef DUMPPARTICLEFONT
2177 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2179 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2181 particletexture[tex_beam].s1 = 0;
2182 particletexture[tex_beam].t1 = 0;
2183 particletexture[tex_beam].s2 = 1;
2184 particletexture[tex_beam].t2 = 1;
2187 static void r_part_start(void)
2189 particletexturepool = R_AllocTexturePool();
2190 R_InitParticleTexture ();
2191 CL_Particles_LoadEffectInfo();
2194 static void r_part_shutdown(void)
2196 R_FreeTexturePool(&particletexturepool);
2199 static void r_part_newmap(void)
2203 #define BATCHSIZE 256
2204 int particle_element3i[BATCHSIZE*6];
2205 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2207 void R_Particles_Init (void)
2210 for (i = 0;i < BATCHSIZE;i++)
2212 particle_element3i[i*6+0] = i*4+0;
2213 particle_element3i[i*6+1] = i*4+1;
2214 particle_element3i[i*6+2] = i*4+2;
2215 particle_element3i[i*6+3] = i*4+0;
2216 particle_element3i[i*6+4] = i*4+2;
2217 particle_element3i[i*6+5] = i*4+3;
2220 Cvar_RegisterVariable(&r_drawparticles);
2221 Cvar_RegisterVariable(&r_drawdecals);
2222 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2225 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2227 int surfacelistindex;
2228 int batchstart, batchcount;
2231 rtexture_t *texture;
2232 float *v3f, *t2f, *c4f;
2234 r_refdef.stats.decals += numsurfaces;
2235 R_Mesh_Matrix(&identitymatrix);
2236 R_Mesh_ResetTextureState();
2237 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2238 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2239 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2240 GL_DepthMask(false);
2241 GL_DepthRange(0, 1);
2242 GL_PolygonOffset(0, 0);
2244 GL_CullFace(GL_NONE);
2246 // first generate all the vertices at once
2247 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2249 particletexture_t *tex;
2251 float right[3], up[3], fog, cr, cg, cb, ca, size;
2253 d = cl.decals + surfacelist[surfacelistindex];
2255 //blendmode = particletype[d->typeindex].blendmode;
2257 cr = d->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2258 cg = d->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2259 cb = d->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2260 ca = d->alpha * (1.0f / 255.0f);
2261 //if (blendmode == PBLEND_MOD)
2271 ca *= cl_particles_alpha.value;
2272 if (r_refdef.fogenabled)
2274 fog = FogPoint_World(d->org);
2278 //if (blendmode == PBLEND_ALPHA)
2281 // cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2282 // cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2283 // cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2286 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2287 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2288 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2289 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2291 size = d->size * cl_particles_size.value;
2293 tex = &particletexture[d->texnum];
2295 // PARTICLE_ORIENTED_DOUBLESIDED
2296 VectorVectors(d->normal, right, up);
2297 VectorScale(right, size, right);
2298 VectorScale(up, size, up);
2299 v3f[ 0] = org[0] - right[0] - up[0];
2300 v3f[ 1] = org[1] - right[1] - up[1];
2301 v3f[ 2] = org[2] - right[2] - up[2];
2302 v3f[ 3] = org[0] - right[0] + up[0];
2303 v3f[ 4] = org[1] - right[1] + up[1];
2304 v3f[ 5] = org[2] - right[2] + up[2];
2305 v3f[ 6] = org[0] + right[0] + up[0];
2306 v3f[ 7] = org[1] + right[1] + up[1];
2307 v3f[ 8] = org[2] + right[2] + up[2];
2308 v3f[ 9] = org[0] + right[0] - up[0];
2309 v3f[10] = org[1] + right[1] - up[1];
2310 v3f[11] = org[2] + right[2] - up[2];
2311 t2f[0] = tex->s1;t2f[1] = tex->t2;
2312 t2f[2] = tex->s1;t2f[3] = tex->t1;
2313 t2f[4] = tex->s2;t2f[5] = tex->t1;
2314 t2f[6] = tex->s2;t2f[7] = tex->t2;
2317 // now render batches of particles based on blendmode and texture
2318 blendmode = PBLEND_ADD;
2319 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2320 texture = particletexture[63].texture;
2321 R_Mesh_TexBind(0, R_GetTexture(texture));
2322 GL_LockArrays(0, numsurfaces*4);
2325 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2327 d = cl.decals + surfacelist[surfacelistindex];
2329 if (blendmode != particletype[d->typeindex].blendmode)
2332 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2334 batchstart = surfacelistindex;
2335 blendmode = particletype[d->typeindex].blendmode;
2336 if (blendmode == PBLEND_ALPHA)
2337 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2338 else if (blendmode == PBLEND_ADD)
2339 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2340 else //if (blendmode == PBLEND_MOD)
2341 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2343 if (texture != particletexture[d->texnum].texture)
2346 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2348 batchstart = surfacelistindex;
2349 texture = particletexture[d->texnum].texture;
2350 R_Mesh_TexBind(0, R_GetTexture(texture));
2356 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2357 GL_LockArrays(0, 0);
2360 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2362 int surfacelistindex;
2363 int batchstart, batchcount;
2364 const particle_t *p;
2366 rtexture_t *texture;
2367 float *v3f, *t2f, *c4f;
2369 r_refdef.stats.particles += numsurfaces;
2370 R_Mesh_Matrix(&identitymatrix);
2371 R_Mesh_ResetTextureState();
2372 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2373 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2374 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2375 GL_DepthMask(false);
2376 GL_DepthRange(0, 1);
2377 GL_PolygonOffset(0, 0);
2379 GL_CullFace(GL_NONE);
2381 // first generate all the vertices at once
2382 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2384 particletexture_t *tex;
2386 float up2[3], v[3], right[3], up[3], fog, cr, cg, cb, ca, size;
2388 p = cl.particles + surfacelist[surfacelistindex];
2390 blendmode = particletype[p->typeindex].blendmode;
2392 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2393 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2394 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2395 ca = p->alpha * (1.0f / 255.0f);
2396 if (blendmode == PBLEND_MOD)
2406 ca *= cl_particles_alpha.value;
2407 if (particletype[p->typeindex].lighting)
2409 float ambient[3], diffuse[3], diffusenormal[3];
2410 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2411 cr *= (ambient[0] + 0.5 * diffuse[0]);
2412 cg *= (ambient[1] + 0.5 * diffuse[1]);
2413 cb *= (ambient[2] + 0.5 * diffuse[2]);
2415 if (r_refdef.fogenabled)
2417 fog = FogPoint_World(p->org);
2421 if (blendmode == PBLEND_ALPHA)
2424 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2425 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2426 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2429 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2430 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2431 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2432 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2434 size = p->size * cl_particles_size.value;
2436 tex = &particletexture[p->texnum];
2437 switch(particletype[p->typeindex].orientation)
2439 case PARTICLE_BILLBOARD:
2440 VectorScale(r_view.left, -size, right);
2441 VectorScale(r_view.up, size, up);
2442 v3f[ 0] = org[0] - right[0] - up[0];
2443 v3f[ 1] = org[1] - right[1] - up[1];
2444 v3f[ 2] = org[2] - right[2] - up[2];
2445 v3f[ 3] = org[0] - right[0] + up[0];
2446 v3f[ 4] = org[1] - right[1] + up[1];
2447 v3f[ 5] = org[2] - right[2] + up[2];
2448 v3f[ 6] = org[0] + right[0] + up[0];
2449 v3f[ 7] = org[1] + right[1] + up[1];
2450 v3f[ 8] = org[2] + right[2] + up[2];
2451 v3f[ 9] = org[0] + right[0] - up[0];
2452 v3f[10] = org[1] + right[1] - up[1];
2453 v3f[11] = org[2] + right[2] - up[2];
2454 t2f[0] = tex->s1;t2f[1] = tex->t2;
2455 t2f[2] = tex->s1;t2f[3] = tex->t1;
2456 t2f[4] = tex->s2;t2f[5] = tex->t1;
2457 t2f[6] = tex->s2;t2f[7] = tex->t2;
2459 case PARTICLE_ORIENTED_DOUBLESIDED:
2460 VectorVectors(p->vel, right, up);
2461 VectorScale(right, size, right);
2462 VectorScale(up, size, up);
2463 v3f[ 0] = org[0] - right[0] - up[0];
2464 v3f[ 1] = org[1] - right[1] - up[1];
2465 v3f[ 2] = org[2] - right[2] - up[2];
2466 v3f[ 3] = org[0] - right[0] + up[0];
2467 v3f[ 4] = org[1] - right[1] + up[1];
2468 v3f[ 5] = org[2] - right[2] + up[2];
2469 v3f[ 6] = org[0] + right[0] + up[0];
2470 v3f[ 7] = org[1] + right[1] + up[1];
2471 v3f[ 8] = org[2] + right[2] + up[2];
2472 v3f[ 9] = org[0] + right[0] - up[0];
2473 v3f[10] = org[1] + right[1] - up[1];
2474 v3f[11] = org[2] + right[2] - up[2];
2475 t2f[0] = tex->s1;t2f[1] = tex->t2;
2476 t2f[2] = tex->s1;t2f[3] = tex->t1;
2477 t2f[4] = tex->s2;t2f[5] = tex->t1;
2478 t2f[6] = tex->s2;t2f[7] = tex->t2;
2480 case PARTICLE_SPARK:
2481 VectorMA(org, -0.02, p->vel, v);
2482 VectorMA(org, 0.02, p->vel, up2);
2483 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2484 t2f[0] = tex->s1;t2f[1] = tex->t2;
2485 t2f[2] = tex->s1;t2f[3] = tex->t1;
2486 t2f[4] = tex->s2;t2f[5] = tex->t1;
2487 t2f[6] = tex->s2;t2f[7] = tex->t2;
2490 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2491 VectorSubtract(p->vel, org, up);
2492 VectorNormalize(up);
2493 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2494 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2495 t2f[0] = 1;t2f[1] = v[0];
2496 t2f[2] = 0;t2f[3] = v[0];
2497 t2f[4] = 0;t2f[5] = v[1];
2498 t2f[6] = 1;t2f[7] = v[1];
2503 // now render batches of particles based on blendmode and texture
2504 blendmode = PBLEND_ADD;
2505 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2506 texture = particletexture[63].texture;
2507 R_Mesh_TexBind(0, R_GetTexture(texture));
2508 GL_LockArrays(0, numsurfaces*4);
2511 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2513 p = cl.particles + surfacelist[surfacelistindex];
2515 if (blendmode != particletype[p->typeindex].blendmode)
2518 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2520 batchstart = surfacelistindex;
2521 blendmode = particletype[p->typeindex].blendmode;
2522 if (blendmode == PBLEND_ALPHA)
2523 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2524 else if (blendmode == PBLEND_ADD)
2525 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2526 else //if (blendmode == PBLEND_MOD)
2527 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2529 if (texture != particletexture[p->texnum].texture)
2532 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2534 batchstart = surfacelistindex;
2535 texture = particletexture[p->texnum].texture;
2536 R_Mesh_TexBind(0, R_GetTexture(texture));
2542 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2543 GL_LockArrays(0, 0);
2546 void R_DrawDecals (void)
2551 // LordHavoc: early out conditions
2552 if ((!cl.num_decals) || (!r_drawdecals.integer))
2555 // LordHavoc: only render if not too close
2556 for (i = 0, d = cl.decals;i < cl.num_decals;i++, d++)
2560 r_refdef.stats.decals++;
2561 R_MeshQueue_AddTransparent(d->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2566 void R_DrawParticles (void)
2569 float minparticledist;
2572 // LordHavoc: early out conditions
2573 if ((!cl.num_particles) || (!r_drawparticles.integer))
2576 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2578 // LordHavoc: only render if not too close
2579 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2580 if (p->typeindex && !p->delayedspawn && (DotProduct(p->org, r_view.forward) >= minparticledist || particletype[p->typeindex].orientation == PARTICLE_BEAM))
2581 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);