2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float velocityoffset[3];
105 float originjitter[3];
106 float velocityjitter[3];
107 float velocitymultiplier;
108 // an effect can also spawn a dlight
109 float lightradiusstart;
110 float lightradiusfade;
113 qboolean lightshadow;
115 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
120 float rotate[4]; // min/max base angle, min/max rotation over time
122 particleeffectinfo_t;
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 int numparticleeffectinfo;
127 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
129 static int particlepalette[256];
131 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
132 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
133 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
134 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
135 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
136 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
137 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
138 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
139 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
140 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
141 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
142 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
143 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
144 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
145 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
146 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
147 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
148 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
149 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
150 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
151 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
152 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
153 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
154 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
155 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
156 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
157 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
158 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
159 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
160 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
161 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
162 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
165 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
166 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
167 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
169 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
171 // particletexture_t is a rectangle in the particlefonttexture
172 typedef struct particletexture_s
175 float s1, t1, s2, t2;
179 static rtexturepool_t *particletexturepool;
180 static rtexture_t *particlefonttexture;
181 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
182 skinframe_t *decalskinframe;
184 // texture numbers in particle font
185 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
186 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
187 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
188 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
189 static const int tex_rainsplash = 32;
190 static const int tex_particle = 63;
191 static const int tex_bubble = 62;
192 static const int tex_raindrop = 61;
193 static const int tex_beam = 60;
195 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
196 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
197 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
198 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
199 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
200 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
201 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
202 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
203 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
204 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
205 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
206 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
207 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
208 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
209 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
210 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
211 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
212 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
213 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
214 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
215 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
216 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
217 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
218 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
219 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
220 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
221 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
222 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
223 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
224 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
225 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
226 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
227 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
230 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
235 particleeffectinfo_t *info = NULL;
236 const char *text = textstart;
238 for (linenumber = 1;;linenumber++)
241 for (arrayindex = 0;arrayindex < 16;arrayindex++)
242 argv[arrayindex][0] = 0;
245 if (!COM_ParseToken_Simple(&text, true, false))
247 if (!strcmp(com_token, "\n"))
251 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
257 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
258 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
259 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
260 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
261 #define readfloat(var) checkparms(2);var = atof(argv[1])
262 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
263 if (!strcmp(argv[0], "effect"))
267 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
269 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
272 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
274 if (particleeffectname[effectnameindex][0])
276 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
281 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
285 // if we run out of names, abort
286 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
288 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
291 info = particleeffectinfo + numparticleeffectinfo++;
292 info->effectnameindex = effectnameindex;
293 info->particletype = pt_alphastatic;
294 info->blendmode = particletype[info->particletype].blendmode;
295 info->orientation = particletype[info->particletype].orientation;
296 info->tex[0] = tex_particle;
297 info->tex[1] = tex_particle;
298 info->color[0] = 0xFFFFFF;
299 info->color[1] = 0xFFFFFF;
303 info->alpha[1] = 256;
304 info->alpha[2] = 256;
305 info->time[0] = 9999;
306 info->time[1] = 9999;
307 VectorSet(info->lightcolor, 1, 1, 1);
308 info->lightshadow = true;
309 info->lighttime = 9999;
310 info->stretchfactor = 1;
311 info->staincolor[0] = (unsigned int)-1;
312 info->staincolor[1] = (unsigned int)-1;
313 info->staintex[0] = -1;
314 info->staintex[1] = -1;
315 info->stainalpha[0] = 1;
316 info->stainalpha[1] = 1;
317 info->stainsize[0] = 2;
318 info->stainsize[1] = 2;
320 info->rotate[1] = 360;
324 else if (info == NULL)
326 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
329 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
330 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
331 else if (!strcmp(argv[0], "type"))
334 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
335 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
336 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
337 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
338 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
339 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
340 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
341 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
342 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
343 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
344 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
345 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
346 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
347 info->blendmode = particletype[info->particletype].blendmode;
348 info->orientation = particletype[info->particletype].orientation;
350 else if (!strcmp(argv[0], "blend"))
353 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
354 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
355 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
356 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
358 else if (!strcmp(argv[0], "orientation"))
361 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
362 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
363 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
364 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
365 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
367 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
368 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
369 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
370 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
371 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
372 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
373 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
374 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
375 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
376 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
377 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
378 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
379 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
380 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
381 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
382 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
383 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
384 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
385 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
386 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
387 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
388 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
389 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
390 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
391 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
392 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
393 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
394 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
395 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
396 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
397 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
399 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
408 int CL_ParticleEffectIndexForName(const char *name)
411 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
412 if (!strcmp(particleeffectname[i], name))
417 const char *CL_ParticleEffectNameForIndex(int i)
419 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
421 return particleeffectname[i];
424 // MUST match effectnameindex_t in client.h
425 static const char *standardeffectnames[EFFECT_TOTAL] =
449 "TE_TEI_BIGEXPLOSION",
465 void CL_Particles_LoadEffectInfo(void)
469 unsigned char *filedata;
470 fs_offset_t filesize;
471 char filename[MAX_QPATH];
472 numparticleeffectinfo = 0;
473 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
474 memset(particleeffectname, 0, sizeof(particleeffectname));
475 for (i = 0;i < EFFECT_TOTAL;i++)
476 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
477 for (filepass = 0;;filepass++)
480 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
481 else if (filepass == 1)
483 if (!cl.worldbasename[0])
485 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
489 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
492 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
502 void CL_ReadPointFile_f (void);
503 void CL_Particles_Init (void)
505 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)");
506 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
508 Cvar_RegisterVariable (&cl_particles);
509 Cvar_RegisterVariable (&cl_particles_quality);
510 Cvar_RegisterVariable (&cl_particles_alpha);
511 Cvar_RegisterVariable (&cl_particles_size);
512 Cvar_RegisterVariable (&cl_particles_quake);
513 Cvar_RegisterVariable (&cl_particles_blood);
514 Cvar_RegisterVariable (&cl_particles_blood_alpha);
515 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
516 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
517 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
518 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
519 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
520 Cvar_RegisterVariable (&cl_particles_explosions_shell);
521 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
522 Cvar_RegisterVariable (&cl_particles_rain);
523 Cvar_RegisterVariable (&cl_particles_snow);
524 Cvar_RegisterVariable (&cl_particles_smoke);
525 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
526 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
527 Cvar_RegisterVariable (&cl_particles_sparks);
528 Cvar_RegisterVariable (&cl_particles_bubbles);
529 Cvar_RegisterVariable (&cl_particles_visculling);
530 Cvar_RegisterVariable (&cl_particles_collisions);
531 Cvar_RegisterVariable (&cl_decals);
532 Cvar_RegisterVariable (&cl_decals_visculling);
533 Cvar_RegisterVariable (&cl_decals_time);
534 Cvar_RegisterVariable (&cl_decals_fadetime);
535 Cvar_RegisterVariable (&cl_decals_newsystem);
536 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
537 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
538 Cvar_RegisterVariable (&cl_decals_models);
539 Cvar_RegisterVariable (&cl_decals_bias);
540 Cvar_RegisterVariable (&cl_decals_max);
543 void CL_Particles_Shutdown (void)
547 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
548 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
550 // list of all 26 parameters:
551 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
552 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
553 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
554 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
555 // palpha - opacity of particle as 0-255 (can be more than 255)
556 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
557 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
558 // pgravity - how much effect gravity has on the particle (0-1)
559 // 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
560 // px,py,pz - starting origin of particle
561 // pvx,pvy,pvz - starting velocity of particle
562 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
563 // blendmode - one of the PBLEND_ values
564 // orientation - one of the PARTICLE_ values
565 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
566 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
567 // stainalpha: opacity of the stain as factor for alpha
568 // stainsize: size of the stain as factor for palpha
569 // angle: base rotation of the particle geometry around its center normal
570 // spin: rotation speed of the particle geometry around its center normal
571 particle_t *CL_NewParticle(const vec3_t sortorigin, 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, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4])
576 if (!cl_particles.integer)
578 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
579 if (cl.free_particle >= cl.max_particles)
582 lifetime = palpha / min(1, palphafade);
583 part = &cl.particles[cl.free_particle++];
584 if (cl.num_particles < cl.free_particle)
585 cl.num_particles = cl.free_particle;
586 memset(part, 0, sizeof(*part));
587 VectorCopy(sortorigin, part->sortorigin);
588 part->typeindex = ptypeindex;
589 part->blendmode = blendmode;
590 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
592 particletexture_t *tex = &particletexture[ptex];
593 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
594 part->orientation = PARTICLE_VBEAM;
596 part->orientation = PARTICLE_HBEAM;
599 part->orientation = orientation;
600 l2 = (int)lhrandom(0.5, 256.5);
602 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
603 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
604 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
605 part->alpha = palpha;
606 part->alphafade = palphafade;
607 part->staintexnum = staintex;
608 if(staincolor1 >= 0 && staincolor2 >= 0)
610 l2 = (int)lhrandom(0.5, 256.5);
612 if(blendmode == PBLEND_INVMOD)
614 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
615 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
616 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
620 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
621 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
622 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
624 if(r > 0xFF) r = 0xFF;
625 if(g > 0xFF) g = 0xFF;
626 if(b > 0xFF) b = 0xFF;
630 r = part->color[0]; // -1 is shorthand for stain = particle color
634 part->staincolor[0] = r;
635 part->staincolor[1] = g;
636 part->staincolor[2] = b;
637 part->stainalpha = palpha * stainalpha;
638 part->stainsize = psize * stainsize;
641 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
643 part->color[0] *= tint[0];
644 part->color[1] *= tint[1];
645 part->color[2] *= tint[2];
647 part->alpha *= tint[3];
648 part->alphafade *= tint[3];
649 part->stainalpha *= tint[3];
653 part->sizeincrease = psizeincrease;
654 part->gravity = pgravity;
655 part->bounce = pbounce;
656 part->stretch = stretch;
658 part->org[0] = px + originjitter * v[0];
659 part->org[1] = py + originjitter * v[1];
660 part->org[2] = pz + originjitter * v[2];
661 part->vel[0] = pvx + velocityjitter * v[0];
662 part->vel[1] = pvy + velocityjitter * v[1];
663 part->vel[2] = pvz + velocityjitter * v[2];
665 part->airfriction = pairfriction;
666 part->liquidfriction = pliquidfriction;
667 part->die = cl.time + lifetime;
668 part->delayedspawn = cl.time;
669 // part->delayedcollisions = 0;
670 part->qualityreduction = pqualityreduction;
673 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
674 if (part->typeindex == pt_rain)
678 float lifetime = part->die - cl.time;
681 // turn raindrop into simple spark and create delayedspawn splash effect
682 part->typeindex = pt_spark;
684 VectorMA(part->org, lifetime, part->vel, endvec);
685 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
686 part->die = cl.time + lifetime * trace.fraction;
687 part2 = CL_NewParticle(endvec, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
690 part2->delayedspawn = part->die;
691 part2->die += part->die - cl.time;
692 for (i = rand() & 7;i < 10;i++)
694 part2 = CL_NewParticle(endvec, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
697 part2->delayedspawn = part->die;
698 part2->die += part->die - cl.time;
704 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
706 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
709 VectorMA(part->org, lifetime, part->vel, endvec);
710 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
711 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
718 static void CL_ImmediateBloodStain(particle_t *part)
723 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
724 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
726 VectorCopy(part->vel, v);
728 staintex = part->staintexnum;
729 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
732 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
733 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
735 VectorCopy(part->vel, v);
737 staintex = tex_blooddecal[rand()&7];
738 R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
742 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
746 entity_render_t *ent = &cl.entities[hitent].render;
747 unsigned char color[3];
748 if (!cl_decals.integer)
750 if (!ent->allowdecals)
753 l2 = (int)lhrandom(0.5, 256.5);
755 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
756 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
757 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
759 if (cl_decals_newsystem.integer)
761 R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
765 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
766 if (cl.free_decal >= cl.max_decals)
768 decal = &cl.decals[cl.free_decal++];
769 if (cl.num_decals < cl.free_decal)
770 cl.num_decals = cl.free_decal;
771 memset(decal, 0, sizeof(*decal));
772 decal->decalsequence = cl.decalsequence++;
773 decal->typeindex = pt_decal;
774 decal->texnum = texnum;
775 VectorMA(org, cl_decals_bias.value, normal, decal->org);
776 VectorCopy(normal, decal->normal);
778 decal->alpha = alpha;
779 decal->time2 = cl.time;
780 decal->color[0] = color[0];
781 decal->color[1] = color[1];
782 decal->color[2] = color[2];
783 decal->owner = hitent;
784 decal->clusterindex = -1000; // no vis culling unless we're sure
787 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
788 decal->ownermodel = cl.entities[decal->owner].render.model;
789 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
790 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
794 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
796 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
798 decal->clusterindex = leaf->clusterindex;
803 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
806 float bestfrac, bestorg[3], bestnormal[3];
808 int besthitent = 0, hitent;
811 for (i = 0;i < 32;i++)
814 VectorMA(org, maxdist, org2, org2);
815 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
816 // take the closest trace result that doesn't end up hitting a NOMARKS
817 // surface (sky for example)
818 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
820 bestfrac = trace.fraction;
822 VectorCopy(trace.endpos, bestorg);
823 VectorCopy(trace.plane.normal, bestnormal);
827 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
830 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
831 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
832 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)
835 matrix4x4_t tempmatrix;
837 VectorLerp(originmins, 0.5, originmaxs, center);
838 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
839 if (effectnameindex == EFFECT_SVC_PARTICLE)
841 if (cl_particles.integer)
843 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
845 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
846 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
847 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
850 count *= cl_particles_quality.value;
851 for (;count > 0;count--)
853 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
854 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
859 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
860 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
861 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
862 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
863 else if (effectnameindex == EFFECT_TE_SPIKE)
865 if (cl_particles_bulletimpacts.integer)
867 if (cl_particles_quake.integer)
869 if (cl_particles_smoke.integer)
870 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
874 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
875 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
876 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
880 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
881 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
883 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
885 if (cl_particles_bulletimpacts.integer)
887 if (cl_particles_quake.integer)
889 if (cl_particles_smoke.integer)
890 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
894 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
895 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
896 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
900 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
901 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
902 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);
904 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
906 if (cl_particles_bulletimpacts.integer)
908 if (cl_particles_quake.integer)
910 if (cl_particles_smoke.integer)
911 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
915 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
916 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
917 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
921 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
922 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
924 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
926 if (cl_particles_bulletimpacts.integer)
928 if (cl_particles_quake.integer)
930 if (cl_particles_smoke.integer)
931 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
935 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
936 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
937 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
941 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
942 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
943 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);
945 else if (effectnameindex == EFFECT_TE_BLOOD)
947 if (!cl_particles_blood.integer)
949 if (cl_particles_quake.integer)
950 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
953 static double bloodaccumulator = 0;
954 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
955 //CL_NewParticle(center, 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, NULL);
956 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
957 for (;bloodaccumulator > 0;bloodaccumulator--)
959 part = CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
960 if (immediatebloodstain && part)
962 immediatebloodstain = false;
963 CL_ImmediateBloodStain(part);
968 else if (effectnameindex == EFFECT_TE_SPARK)
969 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
970 else if (effectnameindex == EFFECT_TE_PLASMABURN)
972 // plasma scorch mark
973 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
974 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
975 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
977 else if (effectnameindex == EFFECT_TE_GUNSHOT)
979 if (cl_particles_bulletimpacts.integer)
981 if (cl_particles_quake.integer)
982 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
985 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
986 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
987 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
991 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
992 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
994 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
996 if (cl_particles_bulletimpacts.integer)
998 if (cl_particles_quake.integer)
999 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1002 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1003 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1004 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1008 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1009 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1010 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);
1012 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1014 CL_ParticleExplosion(center);
1015 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);
1017 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1019 CL_ParticleExplosion(center);
1020 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);
1022 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1024 if (cl_particles_quake.integer)
1027 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1030 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1032 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1036 CL_ParticleExplosion(center);
1037 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);
1039 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1040 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);
1041 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1043 count *= cl_particles_quality.value;
1045 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1047 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1049 float i, j, inc, vel;
1052 inc = 8 / cl_particles_quality.value;
1053 for (i = -128;i < 128;i += inc)
1055 for (j = -128;j < 128;j += inc)
1057 dir[0] = j + lhrandom(0, inc);
1058 dir[1] = i + lhrandom(0, inc);
1060 org[0] = center[0] + dir[0];
1061 org[1] = center[1] + dir[1];
1062 org[2] = center[2] + lhrandom(0, 64);
1063 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1064 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1068 else if (effectnameindex == EFFECT_TE_TELEPORT)
1070 float i, j, k, inc, vel;
1073 if (cl_particles_quake.integer)
1074 inc = 4 / cl_particles_quality.value;
1076 inc = 8 / cl_particles_quality.value;
1077 for (i = -16;i < 16;i += inc)
1079 for (j = -16;j < 16;j += inc)
1081 for (k = -24;k < 32;k += inc)
1083 VectorSet(dir, i*8, j*8, k*8);
1084 VectorNormalize(dir);
1085 vel = lhrandom(50, 113);
1086 if (cl_particles_quake.integer)
1087 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1089 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1093 if (!cl_particles_quake.integer)
1094 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1095 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);
1097 else if (effectnameindex == EFFECT_TE_TEI_G3)
1098 CL_NewParticle(center, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1099 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1101 if (cl_particles_smoke.integer)
1103 count *= 0.25f * cl_particles_quality.value;
1105 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1108 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1110 CL_ParticleExplosion(center);
1111 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);
1113 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1116 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1117 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1118 if (cl_particles_smoke.integer)
1119 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1120 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1121 if (cl_particles_sparks.integer)
1122 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1123 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1124 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);
1126 else if (effectnameindex == EFFECT_EF_FLAME)
1128 count *= 300 * cl_particles_quality.value;
1130 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1131 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);
1133 else if (effectnameindex == EFFECT_EF_STARDUST)
1135 count *= 200 * cl_particles_quality.value;
1137 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1138 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);
1140 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1144 int smoke, blood, bubbles, r, color;
1146 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1149 Vector4Set(light, 0, 0, 0, 0);
1151 if (effectnameindex == EFFECT_TR_ROCKET)
1152 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1153 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1155 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1156 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1158 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1160 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1161 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1165 matrix4x4_t tempmatrix;
1166 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1167 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);
1168 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1172 if (!spawnparticles)
1175 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1178 VectorSubtract(originmaxs, originmins, dir);
1179 len = VectorNormalizeLength(dir);
1182 dec = -ent->persistent.trail_time;
1183 ent->persistent.trail_time += len;
1184 if (ent->persistent.trail_time < 0.01f)
1187 // if we skip out, leave it reset
1188 ent->persistent.trail_time = 0.0f;
1193 // advance into this frame to reach the first puff location
1194 VectorMA(originmins, dec, dir, pos);
1197 smoke = cl_particles.integer && cl_particles_smoke.integer;
1198 blood = cl_particles.integer && cl_particles_blood.integer;
1199 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1200 qd = 1.0f / cl_particles_quality.value;
1207 if (effectnameindex == EFFECT_TR_BLOOD)
1209 if (cl_particles_quake.integer)
1211 color = particlepalette[67 + (rand()&3)];
1212 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1217 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1220 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1222 if (cl_particles_quake.integer)
1225 color = particlepalette[67 + (rand()&3)];
1226 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1231 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1237 if (effectnameindex == EFFECT_TR_ROCKET)
1239 if (cl_particles_quake.integer)
1242 color = particlepalette[ramp3[r]];
1243 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1247 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1248 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1251 else if (effectnameindex == EFFECT_TR_GRENADE)
1253 if (cl_particles_quake.integer)
1256 color = particlepalette[ramp3[r]];
1257 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1261 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1264 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1266 if (cl_particles_quake.integer)
1269 color = particlepalette[52 + (rand()&7)];
1270 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1271 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1273 else if (gamemode == GAME_GOODVSBAD2)
1276 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1280 color = particlepalette[20 + (rand()&7)];
1281 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1284 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1286 if (cl_particles_quake.integer)
1289 color = particlepalette[230 + (rand()&7)];
1290 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1291 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1295 color = particlepalette[226 + (rand()&7)];
1296 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1299 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1301 if (cl_particles_quake.integer)
1303 color = particlepalette[152 + (rand()&3)];
1304 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1306 else if (gamemode == GAME_GOODVSBAD2)
1309 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1311 else if (gamemode == GAME_PRYDON)
1314 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1317 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1319 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1322 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1324 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1327 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1329 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1330 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1334 if (effectnameindex == EFFECT_TR_ROCKET)
1335 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1336 else if (effectnameindex == EFFECT_TR_GRENADE)
1337 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1339 // advance to next time and position
1342 VectorMA (pos, dec, dir, pos);
1345 ent->persistent.trail_time = len;
1348 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1351 // this is also called on point effects with spawndlight = true and
1352 // spawnparticles = true
1353 // it is called CL_ParticleTrail because most code does not want to supply
1354 // these parameters, only trail handling does
1355 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, float tintmins[4], float tintmaxs[4])
1357 qboolean found = false;
1358 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1360 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1361 return; // no such effect
1363 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1365 int effectinfoindex;
1368 particleeffectinfo_t *info;
1375 qboolean underwater;
1376 qboolean immediatebloodstain;
1378 float avgtint[4], tint[4], tintlerp;
1379 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1380 VectorLerp(originmins, 0.5, originmaxs, center);
1381 supercontents = CL_PointSuperContents(center);
1382 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1383 VectorSubtract(originmaxs, originmins, traildir);
1384 traillen = VectorLength(traildir);
1385 VectorNormalize(traildir);
1388 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1392 Vector4Set(avgtint, 1, 1, 1, 1);
1394 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1396 if (info->effectnameindex == effectnameindex)
1399 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1401 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1404 // spawn a dlight if requested
1405 if (info->lightradiusstart > 0 && spawndlight)
1407 matrix4x4_t tempmatrix;
1408 if (info->trailspacing > 0)
1409 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1411 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1412 if (info->lighttime > 0 && info->lightradiusfade > 0)
1414 // light flash (explosion, etc)
1415 // called when effect starts
1416 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1418 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1421 // called by CL_LinkNetworkEntity
1422 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1423 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1424 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1425 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1426 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1427 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1431 if (!spawnparticles)
1436 if (info->tex[1] > info->tex[0])
1438 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1439 tex = min(tex, info->tex[1] - 1);
1441 if(info->staintex[0] < 0)
1442 staintex = info->staintex[0];
1445 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1446 staintex = min(staintex, info->staintex[1] - 1);
1448 if (info->particletype == pt_decal)
1449 CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]);
1450 else if (info->orientation == PARTICLE_HBEAM)
1451 CL_NewParticle(center, 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, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL);
1454 if (!cl_particles.integer)
1456 switch (info->particletype)
1458 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1459 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1460 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1461 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1462 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1463 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1466 VectorCopy(originmins, trailpos);
1467 if (info->trailspacing > 0)
1469 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1470 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1471 immediatebloodstain = false;
1475 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1477 immediatebloodstain =
1478 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1480 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1482 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1483 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1485 if (info->tex[1] > info->tex[0])
1487 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1488 tex = min(tex, info->tex[1] - 1);
1492 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1493 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1494 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1498 tintlerp = lhrandom(0, 1);
1499 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1502 part = CL_NewParticle(center, 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, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL);
1503 if (immediatebloodstain && part)
1505 immediatebloodstain = false;
1506 CL_ImmediateBloodStain(part);
1509 VectorMA(trailpos, trailstep, traildir, trailpos);
1516 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1519 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)
1521 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1529 void CL_EntityParticles (const entity_t *ent)
1532 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1533 static vec3_t avelocities[NUMVERTEXNORMALS];
1534 if (!cl_particles.integer) return;
1535 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1537 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1539 if (!avelocities[0][0])
1540 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1541 avelocities[0][i] = lhrandom(0, 2.55);
1543 for (i = 0;i < NUMVERTEXNORMALS;i++)
1545 yaw = cl.time * avelocities[i][0];
1546 pitch = cl.time * avelocities[i][1];
1547 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1548 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1549 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1550 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1555 void CL_ReadPointFile_f (void)
1557 vec3_t org, leakorg;
1559 char *pointfile = NULL, *pointfilepos, *t, tchar;
1560 char name[MAX_QPATH];
1565 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1566 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1569 Con_Printf("Could not open %s\n", name);
1573 Con_Printf("Reading %s...\n", name);
1574 VectorClear(leakorg);
1577 pointfilepos = pointfile;
1578 while (*pointfilepos)
1580 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1585 while (*t && *t != '\n' && *t != '\r')
1589 #if _MSC_VER >= 1400
1590 #define sscanf sscanf_s
1592 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1598 VectorCopy(org, leakorg);
1601 if (cl.num_particles < cl.max_particles - 3)
1604 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1607 Mem_Free(pointfile);
1608 VectorCopy(leakorg, org);
1609 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1611 CL_NewParticle(org, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1612 CL_NewParticle(org, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1613 CL_NewParticle(org, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1618 CL_ParseParticleEffect
1620 Parse an effect out of the server message
1623 void CL_ParseParticleEffect (void)
1626 int i, count, msgcount, color;
1628 MSG_ReadVector(org, cls.protocol);
1629 for (i=0 ; i<3 ; i++)
1630 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1631 msgcount = MSG_ReadByte ();
1632 color = MSG_ReadByte ();
1634 if (msgcount == 255)
1639 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1644 CL_ParticleExplosion
1648 void CL_ParticleExplosion (const vec3_t org)
1654 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1655 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1657 if (cl_particles_quake.integer)
1659 for (i = 0;i < 1024;i++)
1665 color = particlepalette[ramp1[r]];
1666 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1670 color = particlepalette[ramp2[r]];
1671 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1677 i = CL_PointSuperContents(org);
1678 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1680 if (cl_particles.integer && cl_particles_bubbles.integer)
1681 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1682 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1686 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1688 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1695 VectorMA(org, 128, v2, v);
1696 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1698 while (k < 16 && trace.fraction < 0.1f);
1699 VectorSubtract(trace.endpos, org, v2);
1700 VectorScale(v2, 2.0f, v2);
1701 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1707 if (cl_particles_explosions_shell.integer)
1708 R_NewExplosion(org);
1713 CL_ParticleExplosion2
1717 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1720 if (!cl_particles.integer) return;
1722 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1724 k = particlepalette[colorStart + (i % colorLength)];
1725 if (cl_particles_quake.integer)
1726 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1728 CL_NewParticle(org, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1732 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1735 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1736 if (cl_particles_sparks.integer)
1738 sparkcount *= cl_particles_quality.value;
1739 while(sparkcount-- > 0)
1740 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1744 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1747 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1748 if (cl_particles_smoke.integer)
1750 smokecount *= cl_particles_quality.value;
1751 while(smokecount-- > 0)
1752 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1756 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)
1760 if (!cl_particles.integer) return;
1761 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1763 count = (int)(count * cl_particles_quality.value);
1766 k = particlepalette[colorbase + (rand()&3)];
1767 CL_NewParticle(center, 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, -1, -1, -1, 1, 1, 0, 0, NULL);
1771 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1774 float minz, maxz, lifetime = 30;
1776 if (!cl_particles.integer) return;
1777 if (dir[2] < 0) // falling
1779 minz = maxs[2] + dir[2] * 0.1;
1782 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1787 maxz = maxs[2] + dir[2] * 0.1;
1789 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1792 count = (int)(count * cl_particles_quality.value);
1797 if (!cl_particles_rain.integer) break;
1798 count *= 4; // ick, this should be in the mod or maps?
1802 k = particlepalette[colorbase + (rand()&3)];
1803 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1804 if (gamemode == GAME_GOODVSBAD2)
1805 CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1807 CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1811 if (!cl_particles_snow.integer) break;
1814 k = particlepalette[colorbase + (rand()&3)];
1815 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1816 if (gamemode == GAME_GOODVSBAD2)
1817 CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1819 CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1823 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1827 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1828 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1829 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1830 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1831 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1832 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1834 #define PARTICLETEXTURESIZE 64
1835 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1837 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1841 dz = 1 - (dx*dx+dy*dy);
1842 if (dz > 0) // it does hit the sphere
1846 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1847 VectorNormalize(normal);
1848 dot = DotProduct(normal, light);
1849 if (dot > 0.5) // interior reflection
1850 f += ((dot * 2) - 1);
1851 else if (dot < -0.5) // exterior reflection
1852 f += ((dot * -2) - 1);
1854 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1855 VectorNormalize(normal);
1856 dot = DotProduct(normal, light);
1857 if (dot > 0.5) // interior reflection
1858 f += ((dot * 2) - 1);
1859 else if (dot < -0.5) // exterior reflection
1860 f += ((dot * -2) - 1);
1862 f += 16; // just to give it a haze so you can see the outline
1863 f = bound(0, f, 255);
1864 return (unsigned char) f;
1870 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1871 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1873 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1874 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1875 *width = particlefontcellwidth;
1876 *height = particlefontcellheight;
1879 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1881 int basex, basey, w, h, y;
1882 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1883 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1884 Sys_Error("invalid particle texture size for autogenerating");
1885 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1886 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1889 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1892 float cx, cy, dx, dy, f, iradius;
1894 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1895 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1896 iradius = 1.0f / radius;
1897 alpha *= (1.0f / 255.0f);
1898 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1900 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1904 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1909 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1910 d[0] += (int)(f * (blue - d[0]));
1911 d[1] += (int)(f * (green - d[1]));
1912 d[2] += (int)(f * (red - d[2]));
1918 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1921 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1923 data[0] = bound(minb, data[0], maxb);
1924 data[1] = bound(ming, data[1], maxg);
1925 data[2] = bound(minr, data[2], maxr);
1929 void particletextureinvert(unsigned char *data)
1932 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1934 data[0] = 255 - data[0];
1935 data[1] = 255 - data[1];
1936 data[2] = 255 - data[2];
1940 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1941 static void R_InitBloodTextures (unsigned char *particletexturedata)
1944 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1945 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1948 for (i = 0;i < 8;i++)
1950 memset(data, 255, datasize);
1951 for (k = 0;k < 24;k++)
1952 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1953 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1954 particletextureinvert(data);
1955 setuptex(tex_bloodparticle[i], data, particletexturedata);
1959 for (i = 0;i < 8;i++)
1961 memset(data, 255, datasize);
1963 for (j = 1;j < 10;j++)
1964 for (k = min(j, m - 1);k < m;k++)
1965 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1966 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1967 particletextureinvert(data);
1968 setuptex(tex_blooddecal[i], data, particletexturedata);
1974 //uncomment this to make engine save out particle font to a tga file when run
1975 //#define DUMPPARTICLEFONT
1977 static void R_InitParticleTexture (void)
1979 int x, y, d, i, k, m;
1980 int basex, basey, w, h;
1981 float dx, dy, f, s1, t1, s2, t2;
1984 fs_offset_t filesize;
1985 char texturename[MAX_QPATH];
1988 // a note: decals need to modulate (multiply) the background color to
1989 // properly darken it (stain), and they need to be able to alpha fade,
1990 // this is a very difficult challenge because it means fading to white
1991 // (no change to background) rather than black (darkening everything
1992 // behind the whole decal polygon), and to accomplish this the texture is
1993 // inverted (dark red blood on white background becomes brilliant cyan
1994 // and white on black background) so we can alpha fade it to black, then
1995 // we invert it again during the blendfunc to make it work...
1997 #ifndef DUMPPARTICLEFONT
1998 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2001 particlefonttexture = decalskinframe->base;
2002 // TODO maybe allow custom grid size?
2003 particlefontwidth = image_width;
2004 particlefontheight = image_height;
2005 particlefontcellwidth = image_width / 8;
2006 particlefontcellheight = image_height / 8;
2007 particlefontcols = 8;
2008 particlefontrows = 8;
2013 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2014 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2015 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2016 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2017 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2019 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2020 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2021 particlefontcols = 8;
2022 particlefontrows = 8;
2024 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2027 for (i = 0;i < 8;i++)
2029 memset(data, 255, datasize);
2032 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2033 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2035 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2037 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2038 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2040 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2041 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2043 d = (int)(d * (1-(dx*dx+dy*dy)));
2044 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2045 d = bound(0, d, 255);
2046 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2053 setuptex(tex_smoke[i], data, particletexturedata);
2057 memset(data, 255, datasize);
2058 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2060 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2061 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2063 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2064 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2065 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2068 setuptex(tex_rainsplash, data, particletexturedata);
2071 memset(data, 255, datasize);
2072 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2074 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2075 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2077 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2078 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2079 d = bound(0, d, 255);
2080 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2083 setuptex(tex_particle, data, particletexturedata);
2086 memset(data, 255, datasize);
2087 light[0] = 1;light[1] = 1;light[2] = 1;
2088 VectorNormalize(light);
2089 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2091 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2092 // stretch upper half of bubble by +50% and shrink lower half by -50%
2093 // (this gives an elongated teardrop shape)
2095 dy = (dy - 0.5f) * 2.0f;
2097 dy = (dy - 0.5f) / 1.5f;
2098 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2100 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2101 // shrink bubble width to half
2103 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2106 setuptex(tex_raindrop, data, particletexturedata);
2109 memset(data, 255, datasize);
2110 light[0] = 1;light[1] = 1;light[2] = 1;
2111 VectorNormalize(light);
2112 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2114 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2115 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2117 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2118 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2121 setuptex(tex_bubble, data, particletexturedata);
2123 // Blood particles and blood decals
2124 R_InitBloodTextures (particletexturedata);
2127 for (i = 0;i < 8;i++)
2129 memset(data, 255, datasize);
2130 for (k = 0;k < 12;k++)
2131 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2132 for (k = 0;k < 3;k++)
2133 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2134 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2135 particletextureinvert(data);
2136 setuptex(tex_bulletdecal[i], data, particletexturedata);
2139 #ifdef DUMPPARTICLEFONT
2140 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2143 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2144 particlefonttexture = decalskinframe->base;
2146 Mem_Free(particletexturedata);
2151 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2153 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2154 particletexture[i].texture = particlefonttexture;
2155 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2156 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2157 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2158 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2161 #ifndef DUMPPARTICLEFONT
2162 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, r_texture_convertsRGB_particles.integer != 0);
2163 if (!particletexture[tex_beam].texture)
2166 unsigned char noise3[64][64], data2[64][16][4];
2168 fractalnoise(&noise3[0][0], 64, 4);
2170 for (y = 0;y < 64;y++)
2172 dy = (y - 0.5f*64) / (64*0.5f-1);
2173 for (x = 0;x < 16;x++)
2175 dx = (x - 0.5f*16) / (16*0.5f-2);
2176 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2177 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2178 data2[y][x][3] = 255;
2182 #ifdef DUMPPARTICLEFONT
2183 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2185 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2187 particletexture[tex_beam].s1 = 0;
2188 particletexture[tex_beam].t1 = 0;
2189 particletexture[tex_beam].s2 = 1;
2190 particletexture[tex_beam].t2 = 1;
2192 // now load an texcoord/texture override file
2193 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2200 if(!COM_ParseToken_Simple(&bufptr, true, false))
2202 if(!strcmp(com_token, "\n"))
2203 continue; // empty line
2204 i = atoi(com_token);
2212 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2214 strlcpy(texturename, com_token, sizeof(texturename));
2215 s1 = atof(com_token);
2216 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2219 t1 = atof(com_token);
2220 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2222 s2 = atof(com_token);
2223 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2225 t2 = atof(com_token);
2226 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2227 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2228 strlcpy(texturename, com_token, sizeof(texturename));
2235 if (!texturename[0])
2237 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2240 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2242 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2245 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true);
2248 // R_SkinFrame_LoadExternal already complained
2251 particletexture[i].texture = sf->base;
2252 particletexture[i].s1 = s1;
2253 particletexture[i].t1 = t1;
2254 particletexture[i].s2 = s2;
2255 particletexture[i].t2 = t2;
2261 static void r_part_start(void)
2264 // generate particlepalette for convenience from the main one
2265 for (i = 0;i < 256;i++)
2266 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2267 particletexturepool = R_AllocTexturePool();
2268 R_InitParticleTexture ();
2269 CL_Particles_LoadEffectInfo();
2272 static void r_part_shutdown(void)
2274 R_FreeTexturePool(&particletexturepool);
2277 static void r_part_newmap(void)
2280 R_SkinFrame_MarkUsed(decalskinframe);
2281 CL_Particles_LoadEffectInfo();
2284 #define BATCHSIZE 256
2285 unsigned short particle_elements[BATCHSIZE*6];
2286 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2288 void R_Particles_Init (void)
2291 for (i = 0;i < BATCHSIZE;i++)
2293 particle_elements[i*6+0] = i*4+0;
2294 particle_elements[i*6+1] = i*4+1;
2295 particle_elements[i*6+2] = i*4+2;
2296 particle_elements[i*6+3] = i*4+0;
2297 particle_elements[i*6+4] = i*4+2;
2298 particle_elements[i*6+5] = i*4+3;
2301 Cvar_RegisterVariable(&r_drawparticles);
2302 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2303 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2304 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2305 Cvar_RegisterVariable(&r_drawdecals);
2306 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2307 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2310 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2312 int surfacelistindex;
2314 float *v3f, *t2f, *c4f;
2315 particletexture_t *tex;
2316 float right[3], up[3], size, ca;
2317 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2319 RSurf_ActiveWorldEntity();
2321 r_refdef.stats.drawndecals += numsurfaces;
2322 // R_Mesh_ResetTextureState();
2323 GL_DepthMask(false);
2324 GL_DepthRange(0, 1);
2325 GL_PolygonOffset(0, 0);
2327 GL_CullFace(GL_NONE);
2329 // generate all the vertices at once
2330 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2332 d = cl.decals + surfacelist[surfacelistindex];
2335 c4f = particle_color4f + 16*surfacelistindex;
2336 ca = d->alpha * alphascale;
2337 // ensure alpha multiplier saturates properly
2338 if (ca > 1.0f / 256.0f)
2340 if (r_refdef.fogenabled)
2341 ca *= RSurf_FogVertex(d->org);
2342 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2343 Vector4Copy(c4f, c4f + 4);
2344 Vector4Copy(c4f, c4f + 8);
2345 Vector4Copy(c4f, c4f + 12);
2347 // calculate vertex positions
2348 size = d->size * cl_particles_size.value;
2349 VectorVectors(d->normal, right, up);
2350 VectorScale(right, size, right);
2351 VectorScale(up, size, up);
2352 v3f = particle_vertex3f + 12*surfacelistindex;
2353 v3f[ 0] = d->org[0] - right[0] - up[0];
2354 v3f[ 1] = d->org[1] - right[1] - up[1];
2355 v3f[ 2] = d->org[2] - right[2] - up[2];
2356 v3f[ 3] = d->org[0] - right[0] + up[0];
2357 v3f[ 4] = d->org[1] - right[1] + up[1];
2358 v3f[ 5] = d->org[2] - right[2] + up[2];
2359 v3f[ 6] = d->org[0] + right[0] + up[0];
2360 v3f[ 7] = d->org[1] + right[1] + up[1];
2361 v3f[ 8] = d->org[2] + right[2] + up[2];
2362 v3f[ 9] = d->org[0] + right[0] - up[0];
2363 v3f[10] = d->org[1] + right[1] - up[1];
2364 v3f[11] = d->org[2] + right[2] - up[2];
2366 // calculate texcoords
2367 tex = &particletexture[d->texnum];
2368 t2f = particle_texcoord2f + 8*surfacelistindex;
2369 t2f[0] = tex->s1;t2f[1] = tex->t2;
2370 t2f[2] = tex->s1;t2f[3] = tex->t1;
2371 t2f[4] = tex->s2;t2f[5] = tex->t1;
2372 t2f[6] = tex->s2;t2f[7] = tex->t2;
2375 // now render the decals all at once
2376 // (this assumes they all use one particle font texture!)
2377 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2378 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2379 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2380 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2383 void R_DrawDecals (void)
2386 int drawdecals = r_drawdecals.integer;
2391 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2393 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2394 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2396 // LordHavoc: early out conditions
2400 decalfade = frametime * 256 / cl_decals_fadetime.value;
2401 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2402 drawdist2 = drawdist2*drawdist2;
2404 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2406 if (!decal->typeindex)
2409 if (killsequence - decal->decalsequence > 0)
2412 if (cl.time > decal->time2 + cl_decals_time.value)
2414 decal->alpha -= decalfade;
2415 if (decal->alpha <= 0)
2421 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2423 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2424 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2430 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2436 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))
2437 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2440 decal->typeindex = 0;
2441 if (cl.free_decal > i)
2445 // reduce cl.num_decals if possible
2446 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2449 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2451 decal_t *olddecals = cl.decals;
2452 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2453 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2454 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2455 Mem_Free(olddecals);
2458 r_refdef.stats.totaldecals = cl.num_decals;
2461 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2463 int surfacelistindex;
2464 int batchstart, batchcount;
2465 const particle_t *p;
2467 rtexture_t *texture;
2468 float *v3f, *t2f, *c4f;
2469 particletexture_t *tex;
2470 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2471 float ambient[3], diffuse[3], diffusenormal[3];
2472 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2473 vec4_t colormultiplier;
2474 float minparticledist_start, minparticledist_end;
2477 RSurf_ActiveWorldEntity();
2479 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));
2481 r_refdef.stats.particles += numsurfaces;
2482 // R_Mesh_ResetTextureState();
2483 GL_DepthMask(false);
2484 GL_DepthRange(0, 1);
2485 GL_PolygonOffset(0, 0);
2487 GL_AlphaTest(false);
2488 GL_CullFace(GL_NONE);
2490 spintime = r_refdef.scene.time;
2492 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2493 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2494 dofade = (minparticledist_start < minparticledist_end);
2496 // first generate all the vertices at once
2497 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2499 p = cl.particles + surfacelist[surfacelistindex];
2501 blendmode = (pblend_t)p->blendmode;
2503 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2504 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2505 alpha = palpha * colormultiplier[3];
2506 // ensure alpha multiplier saturates properly
2512 case PBLEND_INVALID:
2514 // additive and modulate can just fade out in fog (this is correct)
2515 if (r_refdef.fogenabled)
2516 alpha *= RSurf_FogVertex(p->org);
2517 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2518 alpha *= 1.0f / 256.0f;
2519 c4f[0] = p->color[0] * alpha;
2520 c4f[1] = p->color[1] * alpha;
2521 c4f[2] = p->color[2] * alpha;
2525 // additive and modulate can just fade out in fog (this is correct)
2526 if (r_refdef.fogenabled)
2527 alpha *= RSurf_FogVertex(p->org);
2528 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2529 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2530 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2531 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2535 c4f[0] = p->color[0] * colormultiplier[0];
2536 c4f[1] = p->color[1] * colormultiplier[1];
2537 c4f[2] = p->color[2] * colormultiplier[2];
2539 // note: lighting is not cheap!
2540 if (particletype[p->typeindex].lighting)
2542 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2543 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2544 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2545 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2547 // mix in the fog color
2548 if (r_refdef.fogenabled)
2550 fog = RSurf_FogVertex(p->org);
2552 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2553 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2554 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2556 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2557 VectorScale(c4f, alpha, c4f);
2560 // copy the color into the other three vertices
2561 Vector4Copy(c4f, c4f + 4);
2562 Vector4Copy(c4f, c4f + 8);
2563 Vector4Copy(c4f, c4f + 12);
2565 size = p->size * cl_particles_size.value;
2566 tex = &particletexture[p->texnum];
2567 switch(p->orientation)
2569 // case PARTICLE_INVALID:
2570 case PARTICLE_BILLBOARD:
2571 if (p->angle + p->spin)
2573 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2574 spinsin = sin(spinrad) * size;
2575 spincos = cos(spinrad) * size;
2576 spinm1 = -p->stretch * spincos;
2579 spinm4 = -p->stretch * spincos;
2580 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2581 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2585 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2586 VectorScale(r_refdef.view.up, size, up);
2589 v3f[ 0] = p->org[0] - right[0] - up[0];
2590 v3f[ 1] = p->org[1] - right[1] - up[1];
2591 v3f[ 2] = p->org[2] - right[2] - up[2];
2592 v3f[ 3] = p->org[0] - right[0] + up[0];
2593 v3f[ 4] = p->org[1] - right[1] + up[1];
2594 v3f[ 5] = p->org[2] - right[2] + up[2];
2595 v3f[ 6] = p->org[0] + right[0] + up[0];
2596 v3f[ 7] = p->org[1] + right[1] + up[1];
2597 v3f[ 8] = p->org[2] + right[2] + up[2];
2598 v3f[ 9] = p->org[0] + right[0] - up[0];
2599 v3f[10] = p->org[1] + right[1] - up[1];
2600 v3f[11] = p->org[2] + right[2] - up[2];
2601 t2f[0] = tex->s1;t2f[1] = tex->t2;
2602 t2f[2] = tex->s1;t2f[3] = tex->t1;
2603 t2f[4] = tex->s2;t2f[5] = tex->t1;
2604 t2f[6] = tex->s2;t2f[7] = tex->t2;
2606 case PARTICLE_ORIENTED_DOUBLESIDED:
2607 VectorVectors(p->vel, baseright, baseup);
2608 if (p->angle + p->spin)
2610 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2611 spinsin = sin(spinrad) * size;
2612 spincos = cos(spinrad) * size;
2613 spinm1 = p->stretch * spincos;
2616 spinm4 = p->stretch * spincos;
2617 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2618 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2622 VectorScale(baseright, size * p->stretch, right);
2623 VectorScale(baseup, size, up);
2625 v3f[ 0] = p->org[0] - right[0] - up[0];
2626 v3f[ 1] = p->org[1] - right[1] - up[1];
2627 v3f[ 2] = p->org[2] - right[2] - up[2];
2628 v3f[ 3] = p->org[0] - right[0] + up[0];
2629 v3f[ 4] = p->org[1] - right[1] + up[1];
2630 v3f[ 5] = p->org[2] - right[2] + up[2];
2631 v3f[ 6] = p->org[0] + right[0] + up[0];
2632 v3f[ 7] = p->org[1] + right[1] + up[1];
2633 v3f[ 8] = p->org[2] + right[2] + up[2];
2634 v3f[ 9] = p->org[0] + right[0] - up[0];
2635 v3f[10] = p->org[1] + right[1] - up[1];
2636 v3f[11] = p->org[2] + right[2] - up[2];
2637 t2f[0] = tex->s1;t2f[1] = tex->t2;
2638 t2f[2] = tex->s1;t2f[3] = tex->t1;
2639 t2f[4] = tex->s2;t2f[5] = tex->t1;
2640 t2f[6] = tex->s2;t2f[7] = tex->t2;
2642 case PARTICLE_SPARK:
2643 len = VectorLength(p->vel);
2644 VectorNormalize2(p->vel, up);
2645 lenfactor = p->stretch * 0.04 * len;
2646 if(lenfactor < size * 0.5)
2647 lenfactor = size * 0.5;
2648 VectorMA(p->org, -lenfactor, up, v);
2649 VectorMA(p->org, lenfactor, up, up2);
2650 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2651 t2f[0] = tex->s1;t2f[1] = tex->t2;
2652 t2f[2] = tex->s1;t2f[3] = tex->t1;
2653 t2f[4] = tex->s2;t2f[5] = tex->t1;
2654 t2f[6] = tex->s2;t2f[7] = tex->t2;
2656 case PARTICLE_VBEAM:
2657 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2658 VectorSubtract(p->vel, p->org, up);
2659 VectorNormalize(up);
2660 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2661 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2662 t2f[0] = tex->s2;t2f[1] = v[0];
2663 t2f[2] = tex->s1;t2f[3] = v[0];
2664 t2f[4] = tex->s1;t2f[5] = v[1];
2665 t2f[6] = tex->s2;t2f[7] = v[1];
2667 case PARTICLE_HBEAM:
2668 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2669 VectorSubtract(p->vel, p->org, up);
2670 VectorNormalize(up);
2671 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2672 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2673 t2f[0] = v[0];t2f[1] = tex->t1;
2674 t2f[2] = v[0];t2f[3] = tex->t2;
2675 t2f[4] = v[1];t2f[5] = tex->t2;
2676 t2f[6] = v[1];t2f[7] = tex->t1;
2681 // now render batches of particles based on blendmode and texture
2682 blendmode = PBLEND_INVALID;
2686 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2687 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2689 p = cl.particles + surfacelist[surfacelistindex];
2691 if (texture != particletexture[p->texnum].texture)
2693 texture = particletexture[p->texnum].texture;
2694 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2697 if (p->blendmode == PBLEND_INVMOD)
2699 // inverse modulate blend - group these
2700 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2701 // iterate until we find a change in settings
2702 batchstart = surfacelistindex++;
2703 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2705 p = cl.particles + surfacelist[surfacelistindex];
2706 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2712 // additive or alpha blend - group these
2713 // (we can group these because we premultiplied the texture alpha)
2714 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2715 // iterate until we find a change in settings
2716 batchstart = surfacelistindex++;
2717 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2719 p = cl.particles + surfacelist[surfacelistindex];
2720 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2725 batchcount = surfacelistindex - batchstart;
2726 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2730 void R_DrawParticles (void)
2733 int drawparticles = r_drawparticles.integer;
2734 float minparticledist_start;
2736 float gravity, frametime, f, dist, oldorg[3];
2742 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2743 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2745 // LordHavoc: early out conditions
2746 if (!cl.num_particles)
2749 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2750 gravity = frametime * cl.movevars_gravity;
2751 update = frametime > 0;
2752 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2753 drawdist2 = drawdist2*drawdist2;
2755 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2759 if (cl.free_particle > i)
2760 cl.free_particle = i;
2766 if (p->delayedspawn > cl.time)
2769 p->size += p->sizeincrease * frametime;
2770 p->alpha -= p->alphafade * frametime;
2772 if (p->alpha <= 0 || p->die <= cl.time)
2775 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2777 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2779 if (p->typeindex == pt_blood)
2780 p->size += frametime * 8;
2782 p->vel[2] -= p->gravity * gravity;
2783 f = 1.0f - min(p->liquidfriction * frametime, 1);
2784 VectorScale(p->vel, f, p->vel);
2788 p->vel[2] -= p->gravity * gravity;
2791 f = 1.0f - min(p->airfriction * frametime, 1);
2792 VectorScale(p->vel, f, p->vel);
2796 VectorCopy(p->org, oldorg);
2797 VectorMA(p->org, frametime, p->vel, p->org);
2798 // if (p->bounce && cl.time >= p->delayedcollisions)
2799 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2801 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2802 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2803 // or if the trace hit something flagged as NOIMPACT
2804 // then remove the particle
2805 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2807 VectorCopy(trace.endpos, p->org);
2808 // react if the particle hit something
2809 if (trace.fraction < 1)
2811 VectorCopy(trace.endpos, p->org);
2813 if (p->staintexnum >= 0)
2815 // blood - splash on solid
2816 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2819 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2820 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2821 if (cl_decals.integer)
2823 // create a decal for the blood splat
2824 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2825 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2830 if (p->typeindex == pt_blood)
2832 // blood - splash on solid
2833 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2835 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2837 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)));
2838 if (cl_decals.integer)
2840 // create a decal for the blood splat
2841 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 * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
2846 else if (p->bounce < 0)
2848 // bounce -1 means remove on impact
2853 // anything else - bounce off solid
2854 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2855 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2860 if (VectorLength2(p->vel) < 0.03)
2862 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2864 VectorClear(p->vel);
2868 if (p->typeindex != pt_static)
2870 switch (p->typeindex)
2872 case pt_entityparticle:
2873 // particle that removes itself after one rendered frame
2880 a = CL_PointSuperContents(p->org);
2881 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2885 a = CL_PointSuperContents(p->org);
2886 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2890 a = CL_PointSuperContents(p->org);
2891 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2895 if (cl.time > p->time2)
2898 p->time2 = cl.time + (rand() & 3) * 0.1;
2899 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2900 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2902 a = CL_PointSuperContents(p->org);
2903 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2911 else if (p->delayedspawn > cl.time)
2915 // don't render particles too close to the view (they chew fillrate)
2916 // also don't render particles behind the view (useless)
2917 // further checks to cull to the frustum would be too slow here
2918 switch(p->typeindex)
2921 // beams have no culling
2922 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2925 if(cl_particles_visculling.integer)
2926 if (!r_refdef.viewcache.world_novis)
2927 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2929 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2931 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2934 // anything else just has to be in front of the viewer and visible at this distance
2935 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2936 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2943 if (cl.free_particle > i)
2944 cl.free_particle = i;
2947 // reduce cl.num_particles if possible
2948 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2951 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2953 particle_t *oldparticles = cl.particles;
2954 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2955 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2956 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2957 Mem_Free(oldparticles);