2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
119 particleeffectinfo_t;
121 #define MAX_PARTICLEEFFECTNAME 256
122 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
124 #define MAX_PARTICLEEFFECTINFO 4096
126 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
128 static int particlepalette[256];
130 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
131 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
132 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
133 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
134 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
135 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
136 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
137 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
138 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
139 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
140 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
141 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
142 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
143 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
144 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
145 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
146 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
147 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
148 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
149 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
150 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
151 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
152 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
153 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
154 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
155 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
156 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
157 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
158 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
159 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
160 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
161 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
164 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
165 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
166 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
168 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
170 // texture numbers in particle font
171 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
172 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
173 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
174 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
175 static const int tex_rainsplash = 32;
176 static const int tex_particle = 63;
177 static const int tex_bubble = 62;
178 static const int tex_raindrop = 61;
179 static const int tex_beam = 60;
181 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
182 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
183 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
184 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
185 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
186 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
187 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
188 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
189 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
190 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
191 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
192 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
193 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
194 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
195 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
196 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
197 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
198 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
199 cvar_t cl_particles_novis = {CVAR_SAVE, "cl_particles_novis", "0", "turn off PVS culling of particles"};
200 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
201 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
202 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
205 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
211 particleeffectinfo_t *info = NULL;
212 const char *text = textstart;
214 effectinfoindex = -1;
215 for (linenumber = 1;;linenumber++)
218 for (arrayindex = 0;arrayindex < 16;arrayindex++)
219 argv[arrayindex][0] = 0;
222 if (!COM_ParseToken_Simple(&text, true, false))
224 if (!strcmp(com_token, "\n"))
228 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
234 #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;}
235 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
236 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
237 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
238 #define readfloat(var) checkparms(2);var = atof(argv[1])
239 if (!strcmp(argv[0], "effect"))
244 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
246 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
249 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
251 if (particleeffectname[effectnameindex][0])
253 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
258 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
262 // if we run out of names, abort
263 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
265 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
268 info = particleeffectinfo + effectinfoindex;
269 info->effectnameindex = effectnameindex;
270 info->particletype = pt_alphastatic;
271 info->blendmode = particletype[info->particletype].blendmode;
272 info->orientation = particletype[info->particletype].orientation;
273 info->tex[0] = tex_particle;
274 info->tex[1] = tex_particle;
275 info->color[0] = 0xFFFFFF;
276 info->color[1] = 0xFFFFFF;
280 info->alpha[1] = 256;
281 info->alpha[2] = 256;
282 info->time[0] = 9999;
283 info->time[1] = 9999;
284 VectorSet(info->lightcolor, 1, 1, 1);
285 info->lightshadow = true;
286 info->lighttime = 9999;
287 info->stretchfactor = 1;
289 else if (info == NULL)
291 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
294 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
295 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
296 else if (!strcmp(argv[0], "type"))
299 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
300 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
301 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
302 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
303 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
304 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
305 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
306 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
307 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
308 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
309 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
310 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
311 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
312 info->blendmode = particletype[info->particletype].blendmode;
313 info->orientation = particletype[info->particletype].orientation;
315 else if (!strcmp(argv[0], "blend"))
318 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
319 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
320 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
321 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
323 else if (!strcmp(argv[0], "orientation"))
326 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
327 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
328 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
329 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_BEAM;
330 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
332 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
333 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
334 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
335 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
336 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
337 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
338 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
339 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
340 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
341 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
342 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
343 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
344 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
345 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
346 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
347 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
348 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
349 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
350 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
351 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
352 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
353 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
354 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
355 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
356 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
358 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
367 int CL_ParticleEffectIndexForName(const char *name)
370 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
371 if (!strcmp(particleeffectname[i], name))
376 const char *CL_ParticleEffectNameForIndex(int i)
378 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
380 return particleeffectname[i];
383 // MUST match effectnameindex_t in client.h
384 static const char *standardeffectnames[EFFECT_TOTAL] =
408 "TE_TEI_BIGEXPLOSION",
424 void CL_Particles_LoadEffectInfo(void)
427 unsigned char *filedata;
428 fs_offset_t filesize;
429 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
430 memset(particleeffectname, 0, sizeof(particleeffectname));
431 for (i = 0;i < EFFECT_TOTAL;i++)
432 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
433 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
436 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
446 void CL_ReadPointFile_f (void);
447 void CL_Particles_Init (void)
449 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)");
450 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
452 Cvar_RegisterVariable (&cl_particles);
453 Cvar_RegisterVariable (&cl_particles_quality);
454 Cvar_RegisterVariable (&cl_particles_alpha);
455 Cvar_RegisterVariable (&cl_particles_size);
456 Cvar_RegisterVariable (&cl_particles_quake);
457 Cvar_RegisterVariable (&cl_particles_blood);
458 Cvar_RegisterVariable (&cl_particles_blood_alpha);
459 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
460 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
461 Cvar_RegisterVariable (&cl_particles_explosions_shell);
462 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
463 Cvar_RegisterVariable (&cl_particles_rain);
464 Cvar_RegisterVariable (&cl_particles_snow);
465 Cvar_RegisterVariable (&cl_particles_smoke);
466 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
467 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
468 Cvar_RegisterVariable (&cl_particles_sparks);
469 Cvar_RegisterVariable (&cl_particles_bubbles);
470 Cvar_RegisterVariable (&cl_particles_novis);
471 Cvar_RegisterVariable (&cl_decals);
472 Cvar_RegisterVariable (&cl_decals_time);
473 Cvar_RegisterVariable (&cl_decals_fadetime);
476 void CL_Particles_Shutdown (void)
480 // list of all 26 parameters:
481 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
482 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
483 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
484 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
485 // palpha - opacity of particle as 0-255 (can be more than 255)
486 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
487 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
488 // pgravity - how much effect gravity has on the particle (0-1)
489 // 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
490 // px,py,pz - starting origin of particle
491 // pvx,pvy,pvz - starting velocity of particle
492 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
493 // blendmode - one of the PBLEND_ values
494 // orientation - one of the PARTICLE_ values
495 static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation)
500 if (!cl_particles.integer)
502 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
503 if (cl.free_particle >= cl.max_particles)
506 lifetime = palpha / min(1, palphafade);
507 part = &cl.particles[cl.free_particle++];
508 if (cl.num_particles < cl.free_particle)
509 cl.num_particles = cl.free_particle;
510 memset(part, 0, sizeof(*part));
511 part->typeindex = ptypeindex;
512 part->blendmode = blendmode;
513 part->orientation = orientation;
514 l2 = (int)lhrandom(0.5, 256.5);
516 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
517 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
518 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
521 part->sizeincrease = psizeincrease;
522 part->alpha = palpha;
523 part->alphafade = palphafade;
524 part->gravity = pgravity;
525 part->bounce = pbounce;
526 part->stretch = stretch;
528 part->org[0] = px + originjitter * v[0];
529 part->org[1] = py + originjitter * v[1];
530 part->org[2] = pz + originjitter * v[2];
531 part->vel[0] = pvx + velocityjitter * v[0];
532 part->vel[1] = pvy + velocityjitter * v[1];
533 part->vel[2] = pvz + velocityjitter * v[2];
535 part->airfriction = pairfriction;
536 part->liquidfriction = pliquidfriction;
537 part->die = cl.time + lifetime;
538 part->delayedcollisions = 0;
539 part->qualityreduction = pqualityreduction;
540 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
541 if (part->typeindex == pt_rain)
545 float lifetime = part->die - cl.time;
548 // turn raindrop into simple spark and create delayedspawn splash effect
549 part->typeindex = pt_spark;
551 VectorMA(part->org, lifetime, part->vel, endvec);
552 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
553 part->die = cl.time + lifetime * trace.fraction;
554 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED);
557 part2->delayedspawn = part->die;
558 part2->die += part->die - cl.time;
559 for (i = rand() & 7;i < 10;i++)
561 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK);
564 part2->delayedspawn = part->die;
565 part2->die += part->die - cl.time;
570 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
572 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
575 VectorMA(part->org, lifetime, part->vel, endvec);
576 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
577 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
582 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
586 if (!cl_decals.integer)
588 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
589 if (cl.free_decal >= cl.max_decals)
591 decal = &cl.decals[cl.free_decal++];
592 if (cl.num_decals < cl.free_decal)
593 cl.num_decals = cl.free_decal;
594 memset(decal, 0, sizeof(*decal));
595 decal->typeindex = pt_decal;
596 decal->texnum = texnum;
597 VectorAdd(org, normal, decal->org);
598 VectorCopy(normal, decal->normal);
600 decal->alpha = alpha;
601 decal->time2 = cl.time;
602 l2 = (int)lhrandom(0.5, 256.5);
604 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
605 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
606 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
607 decal->owner = hitent;
610 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
611 decal->ownermodel = cl.entities[decal->owner].render.model;
612 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
613 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
617 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
620 float bestfrac, bestorg[3], bestnormal[3];
622 int besthitent = 0, hitent;
625 for (i = 0;i < 32;i++)
628 VectorMA(org, maxdist, org2, org2);
629 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
630 // take the closest trace result that doesn't end up hitting a NOMARKS
631 // surface (sky for example)
632 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
634 bestfrac = trace.fraction;
636 VectorCopy(trace.endpos, bestorg);
637 VectorCopy(trace.plane.normal, bestnormal);
641 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
644 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
645 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
646 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)
649 matrix4x4_t tempmatrix;
650 VectorLerp(originmins, 0.5, originmaxs, center);
651 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
652 if (effectnameindex == EFFECT_SVC_PARTICLE)
654 if (cl_particles.integer)
656 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
658 CL_ParticleExplosion(center);
659 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
660 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
663 count *= cl_particles_quality.value;
664 for (;count > 0;count--)
666 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
667 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
672 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
673 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
674 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
675 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
676 else if (effectnameindex == EFFECT_TE_SPIKE)
678 if (cl_particles_bulletimpacts.integer)
680 if (cl_particles_quake.integer)
682 if (cl_particles_smoke.integer)
683 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
687 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
688 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
689 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
693 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
694 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
696 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
698 if (cl_particles_bulletimpacts.integer)
700 if (cl_particles_quake.integer)
702 if (cl_particles_smoke.integer)
703 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
707 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
708 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
709 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
713 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
714 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
715 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);
717 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
719 if (cl_particles_bulletimpacts.integer)
721 if (cl_particles_quake.integer)
723 if (cl_particles_smoke.integer)
724 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
728 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
729 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
730 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
734 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
735 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
737 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
739 if (cl_particles_bulletimpacts.integer)
741 if (cl_particles_quake.integer)
743 if (cl_particles_smoke.integer)
744 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
748 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
749 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
750 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
754 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
755 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
756 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);
758 else if (effectnameindex == EFFECT_TE_BLOOD)
760 if (!cl_particles_blood.integer)
762 if (cl_particles_quake.integer)
763 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
766 static double bloodaccumulator = 0;
767 //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
768 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
769 for (;bloodaccumulator > 0;bloodaccumulator--)
770 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD);
773 else if (effectnameindex == EFFECT_TE_SPARK)
774 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
775 else if (effectnameindex == EFFECT_TE_PLASMABURN)
777 // plasma scorch mark
778 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
779 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
780 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
782 else if (effectnameindex == EFFECT_TE_GUNSHOT)
784 if (cl_particles_bulletimpacts.integer)
786 if (cl_particles_quake.integer)
787 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
790 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
791 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
792 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
796 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
797 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
799 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
801 if (cl_particles_bulletimpacts.integer)
803 if (cl_particles_quake.integer)
804 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
807 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
808 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
809 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
813 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
814 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
815 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);
817 else if (effectnameindex == EFFECT_TE_EXPLOSION)
819 CL_ParticleExplosion(center);
820 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);
822 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
824 CL_ParticleExplosion(center);
825 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);
827 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
829 if (cl_particles_quake.integer)
832 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
835 CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
837 CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
841 CL_ParticleExplosion(center);
842 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);
844 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
845 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);
846 else if (effectnameindex == EFFECT_TE_FLAMEJET)
848 count *= cl_particles_quality.value;
850 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
852 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
854 float i, j, inc, vel;
857 inc = 8 / cl_particles_quality.value;
858 for (i = -128;i < 128;i += inc)
860 for (j = -128;j < 128;j += inc)
862 dir[0] = j + lhrandom(0, inc);
863 dir[1] = i + lhrandom(0, inc);
865 org[0] = center[0] + dir[0];
866 org[1] = center[1] + dir[1];
867 org[2] = center[2] + lhrandom(0, 64);
868 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
869 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
873 else if (effectnameindex == EFFECT_TE_TELEPORT)
875 float i, j, k, inc, vel;
878 if (cl_particles_quake.integer)
879 inc = 4 / cl_particles_quality.value;
881 inc = 8 / cl_particles_quality.value;
882 for (i = -16;i < 16;i += inc)
884 for (j = -16;j < 16;j += inc)
886 for (k = -24;k < 32;k += inc)
888 VectorSet(dir, i*8, j*8, k*8);
889 VectorNormalize(dir);
890 vel = lhrandom(50, 113);
891 if (cl_particles_quake.integer)
892 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
894 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
898 if (!cl_particles_quake.integer)
899 CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
900 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);
902 else if (effectnameindex == EFFECT_TE_TEI_G3)
903 CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BEAM);
904 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
906 if (cl_particles_smoke.integer)
908 count *= 0.25f * cl_particles_quality.value;
910 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
913 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
915 CL_ParticleExplosion(center);
916 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);
918 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
921 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
922 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
923 if (cl_particles_smoke.integer)
924 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
925 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
926 if (cl_particles_sparks.integer)
927 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
928 CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK);
929 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);
931 else if (effectnameindex == EFFECT_EF_FLAME)
933 count *= 300 * cl_particles_quality.value;
935 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
936 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);
938 else if (effectnameindex == EFFECT_EF_STARDUST)
940 count *= 200 * cl_particles_quality.value;
942 CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
943 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);
945 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
949 int smoke, blood, bubbles, r, color;
951 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
954 Vector4Set(light, 0, 0, 0, 0);
956 if (effectnameindex == EFFECT_TR_ROCKET)
957 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
958 else if (effectnameindex == EFFECT_TR_VORESPIKE)
960 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
961 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
963 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
965 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
966 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
970 matrix4x4_t tempmatrix;
971 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
972 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
973 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
980 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
983 VectorSubtract(originmaxs, originmins, dir);
984 len = VectorNormalizeLength(dir);
987 dec = -ent->persistent.trail_time;
988 ent->persistent.trail_time += len;
989 if (ent->persistent.trail_time < 0.01f)
992 // if we skip out, leave it reset
993 ent->persistent.trail_time = 0.0f;
998 // advance into this frame to reach the first puff location
999 VectorMA(originmins, dec, dir, pos);
1002 smoke = cl_particles.integer && cl_particles_smoke.integer;
1003 blood = cl_particles.integer && cl_particles_blood.integer;
1004 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1005 qd = 1.0f / cl_particles_quality.value;
1012 if (effectnameindex == EFFECT_TR_BLOOD)
1014 if (cl_particles_quake.integer)
1016 color = particlepalette[67 + (rand()&3)];
1017 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1022 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD);
1025 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1027 if (cl_particles_quake.integer)
1030 color = particlepalette[67 + (rand()&3)];
1031 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1036 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD);
1042 if (effectnameindex == EFFECT_TR_ROCKET)
1044 if (cl_particles_quake.integer)
1047 color = particlepalette[ramp3[r]];
1048 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1052 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1053 CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1056 else if (effectnameindex == EFFECT_TR_GRENADE)
1058 if (cl_particles_quake.integer)
1061 color = particlepalette[ramp3[r]];
1062 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1066 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1069 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1071 if (cl_particles_quake.integer)
1074 color = particlepalette[52 + (rand()&7)];
1075 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1076 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1078 else if (gamemode == GAME_GOODVSBAD2)
1081 CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1085 color = particlepalette[20 + (rand()&7)];
1086 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1089 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1091 if (cl_particles_quake.integer)
1094 color = particlepalette[230 + (rand()&7)];
1095 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1096 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1100 color = particlepalette[226 + (rand()&7)];
1101 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1104 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1106 if (cl_particles_quake.integer)
1108 color = particlepalette[152 + (rand()&3)];
1109 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1111 else if (gamemode == GAME_GOODVSBAD2)
1114 CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1116 else if (gamemode == GAME_PRYDON)
1119 CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1122 CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1124 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1127 CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1129 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1132 CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1134 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1135 CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1139 if (effectnameindex == EFFECT_TR_ROCKET)
1140 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1141 else if (effectnameindex == EFFECT_TR_GRENADE)
1142 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1144 // advance to next time and position
1147 VectorMA (pos, dec, dir, pos);
1150 ent->persistent.trail_time = len;
1152 else if (developer.integer >= 1)
1153 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1156 // this is also called on point effects with spawndlight = true and
1157 // spawnparticles = true
1158 // it is called CL_ParticleTrail because most code does not want to supply
1159 // these parameters, only trail handling does
1160 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)
1163 qboolean found = false;
1164 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1166 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1167 return; // no such effect
1169 VectorLerp(originmins, 0.5, originmaxs, center);
1170 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1172 int effectinfoindex;
1175 particleeffectinfo_t *info;
1177 vec3_t centervelocity;
1183 qboolean underwater;
1184 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1185 VectorLerp(originmins, 0.5, originmaxs, center);
1186 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1187 supercontents = CL_PointSuperContents(center);
1188 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1189 VectorSubtract(originmaxs, originmins, traildir);
1190 traillen = VectorLength(traildir);
1191 VectorNormalize(traildir);
1192 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1194 if (info->effectnameindex == effectnameindex)
1197 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1199 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1202 // spawn a dlight if requested
1203 if (info->lightradiusstart > 0 && spawndlight)
1205 matrix4x4_t tempmatrix;
1206 if (info->trailspacing > 0)
1207 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1209 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1210 if (info->lighttime > 0 && info->lightradiusfade > 0)
1212 // light flash (explosion, etc)
1213 // called when effect starts
1214 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);
1219 // called by CL_LinkNetworkEntity
1220 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1221 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1222 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1226 if (!spawnparticles)
1231 if (info->tex[1] > info->tex[0])
1233 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1234 tex = min(tex, info->tex[1] - 1);
1236 if (info->particletype == pt_decal)
1237 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]);
1238 else if (info->orientation == PARTICLE_BEAM)
1239 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation);
1242 if (!cl_particles.integer)
1244 switch (info->particletype)
1246 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1247 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1248 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1249 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1250 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1251 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1254 VectorCopy(originmins, trailpos);
1255 if (info->trailspacing > 0)
1257 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1258 trailstep = info->trailspacing / cl_particles_quality.value;
1262 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1265 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1266 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1268 if (info->tex[1] > info->tex[0])
1270 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1271 tex = min(tex, info->tex[1] - 1);
1275 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1276 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1277 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1280 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation);
1282 VectorMA(trailpos, trailstep, traildir, trailpos);
1289 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1292 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)
1294 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1302 void CL_EntityParticles (const entity_t *ent)
1305 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1306 static vec3_t avelocities[NUMVERTEXNORMALS];
1307 if (!cl_particles.integer) return;
1308 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1310 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1312 if (!avelocities[0][0])
1313 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1314 avelocities[0][i] = lhrandom(0, 2.55);
1316 for (i = 0;i < NUMVERTEXNORMALS;i++)
1318 yaw = cl.time * avelocities[i][0];
1319 pitch = cl.time * avelocities[i][1];
1320 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1321 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1322 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1323 CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1328 void CL_ReadPointFile_f (void)
1330 vec3_t org, leakorg;
1332 char *pointfile = NULL, *pointfilepos, *t, tchar;
1333 char name[MAX_OSPATH];
1338 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1339 strlcat (name, ".pts", sizeof (name));
1340 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1343 Con_Printf("Could not open %s\n", name);
1347 Con_Printf("Reading %s...\n", name);
1348 VectorClear(leakorg);
1351 pointfilepos = pointfile;
1352 while (*pointfilepos)
1354 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1359 while (*t && *t != '\n' && *t != '\r')
1363 #if _MSC_VER >= 1400
1364 #define sscanf sscanf_s
1366 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1372 VectorCopy(org, leakorg);
1375 if (cl.num_particles < cl.max_particles - 3)
1378 CL_NewParticle(pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1381 Mem_Free(pointfile);
1382 VectorCopy(leakorg, org);
1383 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1385 CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM);
1386 CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM);
1387 CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM);
1392 CL_ParseParticleEffect
1394 Parse an effect out of the server message
1397 void CL_ParseParticleEffect (void)
1400 int i, count, msgcount, color;
1402 MSG_ReadVector(org, cls.protocol);
1403 for (i=0 ; i<3 ; i++)
1404 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1405 msgcount = MSG_ReadByte ();
1406 color = MSG_ReadByte ();
1408 if (msgcount == 255)
1413 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1418 CL_ParticleExplosion
1422 void CL_ParticleExplosion (const vec3_t org)
1428 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1429 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1431 if (cl_particles_quake.integer)
1433 for (i = 0;i < 1024;i++)
1439 color = particlepalette[ramp1[r]];
1440 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1444 color = particlepalette[ramp2[r]];
1445 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1451 i = CL_PointSuperContents(org);
1452 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1454 if (cl_particles.integer && cl_particles_bubbles.integer)
1455 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1456 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1460 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1462 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1466 for (k = 0;k < 16;k++)
1469 VectorMA(org, 128, v2, v);
1470 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1471 if (trace.fraction >= 0.1)
1474 VectorSubtract(trace.endpos, org, v2);
1475 VectorScale(v2, 2.0f, v2);
1476 CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK);
1482 if (cl_particles_explosions_shell.integer)
1483 R_NewExplosion(org);
1488 CL_ParticleExplosion2
1492 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1495 if (!cl_particles.integer) return;
1497 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1499 k = particlepalette[colorStart + (i % colorLength)];
1500 if (cl_particles_quake.integer)
1501 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1503 CL_NewParticle(pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1507 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1509 if (cl_particles_sparks.integer)
1511 sparkcount *= cl_particles_quality.value;
1512 while(sparkcount-- > 0)
1513 CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK);
1517 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1519 if (cl_particles_smoke.integer)
1521 smokecount *= cl_particles_quality.value;
1522 while(smokecount-- > 0)
1523 CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1527 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)
1530 if (!cl_particles.integer) return;
1532 count = (int)(count * cl_particles_quality.value);
1535 k = particlepalette[colorbase + (rand()&3)];
1536 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
1540 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1543 float minz, maxz, lifetime = 30;
1544 if (!cl_particles.integer) return;
1545 if (dir[2] < 0) // falling
1547 minz = maxs[2] + dir[2] * 0.1;
1550 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1555 maxz = maxs[2] + dir[2] * 0.1;
1557 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1560 count = (int)(count * cl_particles_quality.value);
1565 if (!cl_particles_rain.integer) break;
1566 count *= 4; // ick, this should be in the mod or maps?
1570 k = particlepalette[colorbase + (rand()&3)];
1571 if (gamemode == GAME_GOODVSBAD2)
1572 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK);
1574 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK);
1578 if (!cl_particles_snow.integer) break;
1581 k = particlepalette[colorbase + (rand()&3)];
1582 if (gamemode == GAME_GOODVSBAD2)
1583 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1585 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD);
1589 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1593 #define MAX_PARTICLETEXTURES 64
1594 // particletexture_t is a rectangle in the particlefonttexture
1595 typedef struct particletexture_s
1597 rtexture_t *texture;
1598 float s1, t1, s2, t2;
1602 static rtexturepool_t *particletexturepool;
1603 static rtexture_t *particlefonttexture;
1604 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1606 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1607 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1608 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1609 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1611 #define PARTICLETEXTURESIZE 64
1612 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1614 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1618 dz = 1 - (dx*dx+dy*dy);
1619 if (dz > 0) // it does hit the sphere
1623 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1624 VectorNormalize(normal);
1625 dot = DotProduct(normal, light);
1626 if (dot > 0.5) // interior reflection
1627 f += ((dot * 2) - 1);
1628 else if (dot < -0.5) // exterior reflection
1629 f += ((dot * -2) - 1);
1631 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1632 VectorNormalize(normal);
1633 dot = DotProduct(normal, light);
1634 if (dot > 0.5) // interior reflection
1635 f += ((dot * 2) - 1);
1636 else if (dot < -0.5) // exterior reflection
1637 f += ((dot * -2) - 1);
1639 f += 16; // just to give it a haze so you can see the outline
1640 f = bound(0, f, 255);
1641 return (unsigned char) f;
1647 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1649 int basex, basey, y;
1650 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1651 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1652 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1653 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1656 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1659 float cx, cy, dx, dy, f, iradius;
1661 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1662 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1663 iradius = 1.0f / radius;
1664 alpha *= (1.0f / 255.0f);
1665 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1667 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1671 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1676 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1677 d[0] += (int)(f * (blue - d[0]));
1678 d[1] += (int)(f * (green - d[1]));
1679 d[2] += (int)(f * (red - d[2]));
1685 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1688 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1690 data[0] = bound(minb, data[0], maxb);
1691 data[1] = bound(ming, data[1], maxg);
1692 data[2] = bound(minr, data[2], maxr);
1696 void particletextureinvert(unsigned char *data)
1699 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1701 data[0] = 255 - data[0];
1702 data[1] = 255 - data[1];
1703 data[2] = 255 - data[2];
1707 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1708 static void R_InitBloodTextures (unsigned char *particletexturedata)
1711 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1714 for (i = 0;i < 8;i++)
1716 memset(&data[0][0][0], 255, sizeof(data));
1717 for (k = 0;k < 24;k++)
1718 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1719 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1720 particletextureinvert(&data[0][0][0]);
1721 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1725 for (i = 0;i < 8;i++)
1727 memset(&data[0][0][0], 255, sizeof(data));
1729 for (j = 1;j < 10;j++)
1730 for (k = min(j, m - 1);k < m;k++)
1731 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1732 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1733 particletextureinvert(&data[0][0][0]);
1734 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1739 //uncomment this to make engine save out particle font to a tga file when run
1740 //#define DUMPPARTICLEFONT
1742 static void R_InitParticleTexture (void)
1744 int x, y, d, i, k, m;
1748 // a note: decals need to modulate (multiply) the background color to
1749 // properly darken it (stain), and they need to be able to alpha fade,
1750 // this is a very difficult challenge because it means fading to white
1751 // (no change to background) rather than black (darkening everything
1752 // behind the whole decal polygon), and to accomplish this the texture is
1753 // inverted (dark red blood on white background becomes brilliant cyan
1754 // and white on black background) so we can alpha fade it to black, then
1755 // we invert it again during the blendfunc to make it work...
1757 #ifndef DUMPPARTICLEFONT
1758 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1759 if (!particlefonttexture)
1762 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1763 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1764 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1767 for (i = 0;i < 8;i++)
1769 memset(&data[0][0][0], 255, sizeof(data));
1772 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1774 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1775 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1777 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1779 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1780 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1782 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1783 d = (noise2[y][x] - 128) * 3 + 192;
1785 d = (int)(d * (1-(dx*dx+dy*dy)));
1786 d = (d * noise1[y][x]) >> 7;
1787 d = bound(0, d, 255);
1788 data[y][x][3] = (unsigned char) d;
1795 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1799 memset(&data[0][0][0], 255, sizeof(data));
1800 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1802 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1803 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1805 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1806 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1807 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1810 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1813 memset(&data[0][0][0], 255, sizeof(data));
1814 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1816 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1817 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1819 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1820 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1821 d = bound(0, d, 255);
1822 data[y][x][3] = (unsigned char) d;
1825 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1828 memset(&data[0][0][0], 255, sizeof(data));
1829 light[0] = 1;light[1] = 1;light[2] = 1;
1830 VectorNormalize(light);
1831 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1833 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1834 // stretch upper half of bubble by +50% and shrink lower half by -50%
1835 // (this gives an elongated teardrop shape)
1837 dy = (dy - 0.5f) * 2.0f;
1839 dy = (dy - 0.5f) / 1.5f;
1840 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1842 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1843 // shrink bubble width to half
1845 data[y][x][3] = shadebubble(dx, dy, light);
1848 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1851 memset(&data[0][0][0], 255, sizeof(data));
1852 light[0] = 1;light[1] = 1;light[2] = 1;
1853 VectorNormalize(light);
1854 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1856 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1857 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1859 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1860 data[y][x][3] = shadebubble(dx, dy, light);
1863 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1865 // Blood particles and blood decals
1866 R_InitBloodTextures (particletexturedata);
1869 for (i = 0;i < 8;i++)
1871 memset(&data[0][0][0], 255, sizeof(data));
1872 for (k = 0;k < 12;k++)
1873 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1874 for (k = 0;k < 3;k++)
1875 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1876 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1877 particletextureinvert(&data[0][0][0]);
1878 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1881 #ifdef DUMPPARTICLEFONT
1882 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1885 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1887 Mem_Free(particletexturedata);
1889 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1891 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1892 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1893 particletexture[i].texture = particlefonttexture;
1894 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1895 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1896 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1897 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1900 #ifndef DUMPPARTICLEFONT
1901 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1902 if (!particletexture[tex_beam].texture)
1905 unsigned char noise3[64][64], data2[64][16][4];
1907 fractalnoise(&noise3[0][0], 64, 4);
1909 for (y = 0;y < 64;y++)
1911 dy = (y - 0.5f*64) / (64*0.5f-1);
1912 for (x = 0;x < 16;x++)
1914 dx = (x - 0.5f*16) / (16*0.5f-2);
1915 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1916 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1917 data2[y][x][3] = 255;
1921 #ifdef DUMPPARTICLEFONT
1922 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1924 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1926 particletexture[tex_beam].s1 = 0;
1927 particletexture[tex_beam].t1 = 0;
1928 particletexture[tex_beam].s2 = 1;
1929 particletexture[tex_beam].t2 = 1;
1932 static void r_part_start(void)
1935 // generate particlepalette for convenience from the main one
1936 for (i = 0;i < 256;i++)
1937 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1938 particletexturepool = R_AllocTexturePool();
1939 R_InitParticleTexture ();
1940 CL_Particles_LoadEffectInfo();
1943 static void r_part_shutdown(void)
1945 R_FreeTexturePool(&particletexturepool);
1948 static void r_part_newmap(void)
1950 CL_Particles_LoadEffectInfo();
1953 #define BATCHSIZE 256
1954 unsigned short particle_elements[BATCHSIZE*6];
1956 void R_Particles_Init (void)
1959 for (i = 0;i < BATCHSIZE;i++)
1961 particle_elements[i*6+0] = i*4+0;
1962 particle_elements[i*6+1] = i*4+1;
1963 particle_elements[i*6+2] = i*4+2;
1964 particle_elements[i*6+3] = i*4+0;
1965 particle_elements[i*6+4] = i*4+2;
1966 particle_elements[i*6+5] = i*4+3;
1969 Cvar_RegisterVariable(&r_drawparticles);
1970 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1971 Cvar_RegisterVariable(&r_drawdecals);
1972 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1973 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1976 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1978 int surfacelistindex;
1980 float *v3f, *t2f, *c4f;
1981 particletexture_t *tex;
1982 float right[3], up[3], size, ca;
1983 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1984 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1986 r_refdef.stats.decals += numsurfaces;
1987 R_Mesh_Matrix(&identitymatrix);
1988 R_Mesh_ResetTextureState();
1989 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1990 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1991 R_Mesh_ColorPointer(particle_color4f, 0, 0);
1992 R_SetupGenericShader(true);
1993 GL_DepthMask(false);
1994 GL_DepthRange(0, 1);
1995 GL_PolygonOffset(0, 0);
1997 GL_CullFace(GL_NONE);
1999 // generate all the vertices at once
2000 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2002 d = cl.decals + surfacelist[surfacelistindex];
2005 c4f = particle_color4f + 16*surfacelistindex;
2006 ca = d->alpha * alphascale;
2007 if (r_refdef.fogenabled)
2008 ca *= FogPoint_World(d->org);
2009 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2010 Vector4Copy(c4f, c4f + 4);
2011 Vector4Copy(c4f, c4f + 8);
2012 Vector4Copy(c4f, c4f + 12);
2014 // calculate vertex positions
2015 size = d->size * cl_particles_size.value;
2016 VectorVectors(d->normal, right, up);
2017 VectorScale(right, size, right);
2018 VectorScale(up, size, up);
2019 v3f = particle_vertex3f + 12*surfacelistindex;
2020 v3f[ 0] = d->org[0] - right[0] - up[0];
2021 v3f[ 1] = d->org[1] - right[1] - up[1];
2022 v3f[ 2] = d->org[2] - right[2] - up[2];
2023 v3f[ 3] = d->org[0] - right[0] + up[0];
2024 v3f[ 4] = d->org[1] - right[1] + up[1];
2025 v3f[ 5] = d->org[2] - right[2] + up[2];
2026 v3f[ 6] = d->org[0] + right[0] + up[0];
2027 v3f[ 7] = d->org[1] + right[1] + up[1];
2028 v3f[ 8] = d->org[2] + right[2] + up[2];
2029 v3f[ 9] = d->org[0] + right[0] - up[0];
2030 v3f[10] = d->org[1] + right[1] - up[1];
2031 v3f[11] = d->org[2] + right[2] - up[2];
2033 // calculate texcoords
2034 tex = &particletexture[d->texnum];
2035 t2f = particle_texcoord2f + 8*surfacelistindex;
2036 t2f[0] = tex->s1;t2f[1] = tex->t2;
2037 t2f[2] = tex->s1;t2f[3] = tex->t1;
2038 t2f[4] = tex->s2;t2f[5] = tex->t1;
2039 t2f[6] = tex->s2;t2f[7] = tex->t2;
2042 // now render the decals all at once
2043 // (this assumes they all use one particle font texture!)
2044 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2045 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2046 GL_LockArrays(0, numsurfaces*4);
2047 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2048 GL_LockArrays(0, 0);
2051 void R_DrawDecals (void)
2059 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2060 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2062 // LordHavoc: early out conditions
2063 if ((!cl.num_decals) || (!r_drawdecals.integer))
2066 decalfade = frametime * 256 / cl_decals_fadetime.value;
2067 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2068 drawdist2 = drawdist2*drawdist2;
2070 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2072 if (!decal->typeindex)
2075 if (cl.time > decal->time2 + cl_decals_time.value)
2077 decal->alpha -= decalfade;
2078 if (decal->alpha <= 0)
2084 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2086 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2087 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2093 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2095 if(!cl_particles_novis.integer)
2096 if (!r_refdef.viewcache.world_novis)
2097 if(r_refdef.scene.worldmodel->brush.PointInLeaf)
2099 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
2101 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2104 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2108 decal->typeindex = 0;
2109 if (cl.free_decal > i)
2113 // reduce cl.num_decals if possible
2114 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2117 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2119 decal_t *olddecals = cl.decals;
2120 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2121 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2122 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2123 Mem_Free(olddecals);
2127 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2129 int surfacelistindex;
2130 int batchstart, batchcount;
2131 const particle_t *p;
2133 rtexture_t *texture;
2134 float *v3f, *t2f, *c4f;
2135 particletexture_t *tex;
2136 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2137 float ambient[3], diffuse[3], diffusenormal[3];
2138 vec4_t colormultiplier;
2139 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2141 Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2143 r_refdef.stats.particles += numsurfaces;
2144 R_Mesh_Matrix(&identitymatrix);
2145 R_Mesh_ResetTextureState();
2146 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2147 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2148 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2149 R_SetupGenericShader(true);
2150 GL_DepthMask(false);
2151 GL_DepthRange(0, 1);
2152 GL_PolygonOffset(0, 0);
2154 GL_CullFace(GL_NONE);
2156 // first generate all the vertices at once
2157 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2159 p = cl.particles + surfacelist[surfacelistindex];
2161 blendmode = p->blendmode;
2163 c4f[0] = p->color[0] * colormultiplier[0];
2164 c4f[1] = p->color[1] * colormultiplier[1];
2165 c4f[2] = p->color[2] * colormultiplier[2];
2166 c4f[3] = p->alpha * colormultiplier[3];
2169 case PBLEND_INVALID:
2172 // additive and modulate can just fade out in fog (this is correct)
2173 if (r_refdef.fogenabled)
2174 c4f[3] *= FogPoint_World(p->org);
2175 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2182 // note: lighting is not cheap!
2183 if (particletype[p->typeindex].lighting)
2185 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2186 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2187 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2188 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2190 // mix in the fog color
2191 if (r_refdef.fogenabled)
2193 fog = FogPoint_World(p->org);
2195 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2196 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2197 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2201 // copy the color into the other three vertices
2202 Vector4Copy(c4f, c4f + 4);
2203 Vector4Copy(c4f, c4f + 8);
2204 Vector4Copy(c4f, c4f + 12);
2206 size = p->size * cl_particles_size.value;
2207 tex = &particletexture[p->texnum];
2208 switch(p->orientation)
2210 case PARTICLE_INVALID:
2211 case PARTICLE_BILLBOARD:
2212 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2213 VectorScale(r_refdef.view.up, size, up);
2214 v3f[ 0] = p->org[0] - right[0] - up[0];
2215 v3f[ 1] = p->org[1] - right[1] - up[1];
2216 v3f[ 2] = p->org[2] - right[2] - up[2];
2217 v3f[ 3] = p->org[0] - right[0] + up[0];
2218 v3f[ 4] = p->org[1] - right[1] + up[1];
2219 v3f[ 5] = p->org[2] - right[2] + up[2];
2220 v3f[ 6] = p->org[0] + right[0] + up[0];
2221 v3f[ 7] = p->org[1] + right[1] + up[1];
2222 v3f[ 8] = p->org[2] + right[2] + up[2];
2223 v3f[ 9] = p->org[0] + right[0] - up[0];
2224 v3f[10] = p->org[1] + right[1] - up[1];
2225 v3f[11] = p->org[2] + right[2] - up[2];
2226 t2f[0] = tex->s1;t2f[1] = tex->t2;
2227 t2f[2] = tex->s1;t2f[3] = tex->t1;
2228 t2f[4] = tex->s2;t2f[5] = tex->t1;
2229 t2f[6] = tex->s2;t2f[7] = tex->t2;
2231 case PARTICLE_ORIENTED_DOUBLESIDED:
2232 VectorVectors(p->vel, right, up);
2233 VectorScale(right, size * p->stretch, right);
2234 VectorScale(up, size, up);
2235 v3f[ 0] = p->org[0] - right[0] - up[0];
2236 v3f[ 1] = p->org[1] - right[1] - up[1];
2237 v3f[ 2] = p->org[2] - right[2] - up[2];
2238 v3f[ 3] = p->org[0] - right[0] + up[0];
2239 v3f[ 4] = p->org[1] - right[1] + up[1];
2240 v3f[ 5] = p->org[2] - right[2] + up[2];
2241 v3f[ 6] = p->org[0] + right[0] + up[0];
2242 v3f[ 7] = p->org[1] + right[1] + up[1];
2243 v3f[ 8] = p->org[2] + right[2] + up[2];
2244 v3f[ 9] = p->org[0] + right[0] - up[0];
2245 v3f[10] = p->org[1] + right[1] - up[1];
2246 v3f[11] = p->org[2] + right[2] - up[2];
2247 t2f[0] = tex->s1;t2f[1] = tex->t2;
2248 t2f[2] = tex->s1;t2f[3] = tex->t1;
2249 t2f[4] = tex->s2;t2f[5] = tex->t1;
2250 t2f[6] = tex->s2;t2f[7] = tex->t2;
2252 case PARTICLE_SPARK:
2253 len = VectorLength(p->vel);
2254 VectorNormalize2(p->vel, up);
2255 lenfactor = p->stretch * 0.04 * len;
2256 if(lenfactor < size * 0.5)
2257 lenfactor = size * 0.5;
2258 VectorMA(p->org, -lenfactor, up, v);
2259 VectorMA(p->org, lenfactor, up, up2);
2260 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2261 t2f[0] = tex->s1;t2f[1] = tex->t2;
2262 t2f[2] = tex->s1;t2f[3] = tex->t1;
2263 t2f[4] = tex->s2;t2f[5] = tex->t1;
2264 t2f[6] = tex->s2;t2f[7] = tex->t2;
2267 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2268 VectorSubtract(p->vel, p->org, up);
2269 VectorNormalize(up);
2270 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2271 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2272 t2f[0] = 1;t2f[1] = v[0];
2273 t2f[2] = 0;t2f[3] = v[0];
2274 t2f[4] = 0;t2f[5] = v[1];
2275 t2f[6] = 1;t2f[7] = v[1];
2280 // now render batches of particles based on blendmode and texture
2281 blendmode = PBLEND_INVALID;
2283 GL_LockArrays(0, numsurfaces*4);
2286 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2288 p = cl.particles + surfacelist[surfacelistindex];
2290 if (blendmode != p->blendmode)
2292 blendmode = p->blendmode;
2296 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2298 case PBLEND_INVALID:
2300 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2303 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2307 if (texture != particletexture[p->texnum].texture)
2309 texture = particletexture[p->texnum].texture;
2310 R_Mesh_TexBind(0, R_GetTexture(texture));
2313 // iterate until we find a change in settings
2314 batchstart = surfacelistindex++;
2315 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2317 p = cl.particles + surfacelist[surfacelistindex];
2318 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2322 batchcount = surfacelistindex - batchstart;
2323 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2325 GL_LockArrays(0, 0);
2328 void R_DrawParticles (void)
2331 float minparticledist;
2333 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2339 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2340 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2342 // LordHavoc: early out conditions
2343 if ((!cl.num_particles) || (!r_drawparticles.integer))
2346 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2347 gravity = frametime * cl.movevars_gravity;
2348 dvel = 1+4*frametime;
2349 decalfade = frametime * 255 / cl_decals_fadetime.value;
2350 update = frametime > 0;
2351 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2352 drawdist2 = drawdist2*drawdist2;
2354 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2358 if (cl.free_particle > i)
2359 cl.free_particle = i;
2365 if (p->delayedspawn > cl.time)
2367 p->delayedspawn = 0;
2371 p->size += p->sizeincrease * frametime;
2372 p->alpha -= p->alphafade * frametime;
2374 if (p->alpha <= 0 || p->die <= cl.time)
2377 if (p->orientation != PARTICLE_BEAM && frametime > 0)
2379 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2381 if (p->typeindex == pt_blood)
2382 p->size += frametime * 8;
2384 p->vel[2] -= p->gravity * gravity;
2385 f = 1.0f - min(p->liquidfriction * frametime, 1);
2386 VectorScale(p->vel, f, p->vel);
2390 p->vel[2] -= p->gravity * gravity;
2393 f = 1.0f - min(p->airfriction * frametime, 1);
2394 VectorScale(p->vel, f, p->vel);
2398 VectorCopy(p->org, oldorg);
2399 VectorMA(p->org, frametime, p->vel, p->org);
2400 if (p->bounce && cl.time >= p->delayedcollisions)
2402 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);
2403 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2404 // or if the trace hit something flagged as NOIMPACT
2405 // then remove the particle
2406 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2408 VectorCopy(trace.endpos, p->org);
2409 // react if the particle hit something
2410 if (trace.fraction < 1)
2412 VectorCopy(trace.endpos, p->org);
2413 if (p->typeindex == pt_blood)
2415 // blood - splash on solid
2416 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2418 R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2419 if (cl_decals.integer)
2421 // create a decal for the blood splat
2422 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);
2426 else if (p->bounce < 0)
2428 // bounce -1 means remove on impact
2433 // anything else - bounce off solid
2434 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2435 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2436 if (DotProduct(p->vel, p->vel) < 0.03)
2437 VectorClear(p->vel);
2443 if (p->typeindex != pt_static)
2445 switch (p->typeindex)
2447 case pt_entityparticle:
2448 // particle that removes itself after one rendered frame
2455 a = CL_PointSuperContents(p->org);
2456 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2460 a = CL_PointSuperContents(p->org);
2461 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2465 a = CL_PointSuperContents(p->org);
2466 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2470 if (cl.time > p->time2)
2473 p->time2 = cl.time + (rand() & 3) * 0.1;
2474 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2475 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2477 a = CL_PointSuperContents(p->org);
2478 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2486 else if (p->delayedspawn)
2489 // don't render particles too close to the view (they chew fillrate)
2490 // also don't render particles behind the view (useless)
2491 // further checks to cull to the frustum would be too slow here
2492 switch(p->typeindex)
2495 // beams have no culling
2496 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2499 if(!cl_particles_novis.integer)
2500 if (!r_refdef.viewcache.world_novis)
2501 if(r_refdef.scene.worldmodel->brush.PointInLeaf)
2503 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2505 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2508 // anything else just has to be in front of the viewer and visible at this distance
2509 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2510 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2517 if (cl.free_particle > i)
2518 cl.free_particle = i;
2521 // reduce cl.num_particles if possible
2522 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2525 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2527 particle_t *oldparticles = cl.particles;
2528 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2529 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2530 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2531 Mem_Free(oldparticles);