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", "0", "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];
1987 // a note: decals need to modulate (multiply) the background color to
1988 // properly darken it (stain), and they need to be able to alpha fade,
1989 // this is a very difficult challenge because it means fading to white
1990 // (no change to background) rather than black (darkening everything
1991 // behind the whole decal polygon), and to accomplish this the texture is
1992 // inverted (dark red blood on white background becomes brilliant cyan
1993 // and white on black background) so we can alpha fade it to black, then
1994 // we invert it again during the blendfunc to make it work...
1996 #ifndef DUMPPARTICLEFONT
1997 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
2000 particlefonttexture = decalskinframe->base;
2001 // TODO maybe allow custom grid size?
2002 particlefontwidth = image_width;
2003 particlefontheight = image_height;
2004 particlefontcellwidth = image_width / 8;
2005 particlefontcellheight = image_height / 8;
2006 particlefontcols = 8;
2007 particlefontrows = 8;
2012 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2013 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2014 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2015 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2016 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2018 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2019 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2020 particlefontcols = 8;
2021 particlefontrows = 8;
2023 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2026 for (i = 0;i < 8;i++)
2028 memset(data, 255, datasize);
2031 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2032 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2034 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2036 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2037 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2039 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2040 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2042 d = (int)(d * (1-(dx*dx+dy*dy)));
2043 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2044 d = bound(0, d, 255);
2045 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2052 setuptex(tex_smoke[i], data, particletexturedata);
2056 memset(data, 255, datasize);
2057 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2059 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2060 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2062 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2063 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2064 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2067 setuptex(tex_rainsplash, data, particletexturedata);
2070 memset(data, 255, datasize);
2071 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2073 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2074 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2076 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2077 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2078 d = bound(0, d, 255);
2079 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2082 setuptex(tex_particle, data, particletexturedata);
2085 memset(data, 255, datasize);
2086 light[0] = 1;light[1] = 1;light[2] = 1;
2087 VectorNormalize(light);
2088 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2090 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2091 // stretch upper half of bubble by +50% and shrink lower half by -50%
2092 // (this gives an elongated teardrop shape)
2094 dy = (dy - 0.5f) * 2.0f;
2096 dy = (dy - 0.5f) / 1.5f;
2097 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2099 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2100 // shrink bubble width to half
2102 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2105 setuptex(tex_raindrop, data, particletexturedata);
2108 memset(data, 255, datasize);
2109 light[0] = 1;light[1] = 1;light[2] = 1;
2110 VectorNormalize(light);
2111 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2113 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2114 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2116 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2117 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2120 setuptex(tex_bubble, data, particletexturedata);
2122 // Blood particles and blood decals
2123 R_InitBloodTextures (particletexturedata);
2126 for (i = 0;i < 8;i++)
2128 memset(data, 255, datasize);
2129 for (k = 0;k < 12;k++)
2130 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2131 for (k = 0;k < 3;k++)
2132 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2133 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2134 particletextureinvert(data);
2135 setuptex(tex_bulletdecal[i], data, particletexturedata);
2138 #ifdef DUMPPARTICLEFONT
2139 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2142 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2143 particlefonttexture = decalskinframe->base;
2145 Mem_Free(particletexturedata);
2150 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2152 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2153 particletexture[i].texture = particlefonttexture;
2154 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2155 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2156 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2157 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2160 #ifndef DUMPPARTICLEFONT
2161 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer != 0);
2162 if (!particletexture[tex_beam].texture)
2165 unsigned char noise3[64][64], data2[64][16][4];
2167 fractalnoise(&noise3[0][0], 64, 4);
2169 for (y = 0;y < 64;y++)
2171 dy = (y - 0.5f*64) / (64*0.5f-1);
2172 for (x = 0;x < 16;x++)
2174 dx = (x - 0.5f*16) / (16*0.5f-2);
2175 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2176 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2177 data2[y][x][3] = 255;
2181 #ifdef DUMPPARTICLEFONT
2182 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2184 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL);
2186 particletexture[tex_beam].s1 = 0;
2187 particletexture[tex_beam].t1 = 0;
2188 particletexture[tex_beam].s2 = 1;
2189 particletexture[tex_beam].t2 = 1;
2191 // now load an texcoord/texture override file
2192 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2199 if(!COM_ParseToken_Simple(&bufptr, true, false))
2201 if(!strcmp(com_token, "\n"))
2202 continue; // empty line
2203 i = atoi(com_token);
2211 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2213 strlcpy(texturename, com_token, sizeof(texturename));
2214 s1 = atof(com_token);
2215 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2218 t1 = atof(com_token);
2219 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2221 s2 = atof(com_token);
2222 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2224 t2 = atof(com_token);
2225 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2226 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2227 strlcpy(texturename, com_token, sizeof(texturename));
2234 if (!texturename[0])
2236 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2239 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2241 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2244 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2245 particletexture[i].s1 = s1;
2246 particletexture[i].t1 = t1;
2247 particletexture[i].s2 = s2;
2248 particletexture[i].t2 = t2;
2254 static void r_part_start(void)
2257 // generate particlepalette for convenience from the main one
2258 for (i = 0;i < 256;i++)
2259 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2260 particletexturepool = R_AllocTexturePool();
2261 R_InitParticleTexture ();
2262 CL_Particles_LoadEffectInfo();
2265 static void r_part_shutdown(void)
2267 R_FreeTexturePool(&particletexturepool);
2270 static void r_part_newmap(void)
2273 R_SkinFrame_MarkUsed(decalskinframe);
2274 CL_Particles_LoadEffectInfo();
2277 #define BATCHSIZE 256
2278 unsigned short particle_elements[BATCHSIZE*6];
2279 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2281 void R_Particles_Init (void)
2284 for (i = 0;i < BATCHSIZE;i++)
2286 particle_elements[i*6+0] = i*4+0;
2287 particle_elements[i*6+1] = i*4+1;
2288 particle_elements[i*6+2] = i*4+2;
2289 particle_elements[i*6+3] = i*4+0;
2290 particle_elements[i*6+4] = i*4+2;
2291 particle_elements[i*6+5] = i*4+3;
2294 Cvar_RegisterVariable(&r_drawparticles);
2295 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2296 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2297 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2298 Cvar_RegisterVariable(&r_drawdecals);
2299 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2300 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2303 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2305 int surfacelistindex;
2307 float *v3f, *t2f, *c4f;
2308 particletexture_t *tex;
2309 float right[3], up[3], size, ca;
2310 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2312 RSurf_ActiveWorldEntity();
2314 r_refdef.stats.drawndecals += numsurfaces;
2315 R_Mesh_ResetTextureState();
2316 GL_DepthMask(false);
2317 GL_DepthRange(0, 1);
2318 GL_PolygonOffset(0, 0);
2320 GL_CullFace(GL_NONE);
2322 // generate all the vertices at once
2323 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2325 d = cl.decals + surfacelist[surfacelistindex];
2328 c4f = particle_color4f + 16*surfacelistindex;
2329 ca = d->alpha * alphascale;
2330 // ensure alpha multiplier saturates properly
2331 if (ca > 1.0f / 256.0f)
2333 if (r_refdef.fogenabled)
2334 ca *= RSurf_FogVertex(d->org);
2335 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2336 Vector4Copy(c4f, c4f + 4);
2337 Vector4Copy(c4f, c4f + 8);
2338 Vector4Copy(c4f, c4f + 12);
2340 // calculate vertex positions
2341 size = d->size * cl_particles_size.value;
2342 VectorVectors(d->normal, right, up);
2343 VectorScale(right, size, right);
2344 VectorScale(up, size, up);
2345 v3f = particle_vertex3f + 12*surfacelistindex;
2346 v3f[ 0] = d->org[0] - right[0] - up[0];
2347 v3f[ 1] = d->org[1] - right[1] - up[1];
2348 v3f[ 2] = d->org[2] - right[2] - up[2];
2349 v3f[ 3] = d->org[0] - right[0] + up[0];
2350 v3f[ 4] = d->org[1] - right[1] + up[1];
2351 v3f[ 5] = d->org[2] - right[2] + up[2];
2352 v3f[ 6] = d->org[0] + right[0] + up[0];
2353 v3f[ 7] = d->org[1] + right[1] + up[1];
2354 v3f[ 8] = d->org[2] + right[2] + up[2];
2355 v3f[ 9] = d->org[0] + right[0] - up[0];
2356 v3f[10] = d->org[1] + right[1] - up[1];
2357 v3f[11] = d->org[2] + right[2] - up[2];
2359 // calculate texcoords
2360 tex = &particletexture[d->texnum];
2361 t2f = particle_texcoord2f + 8*surfacelistindex;
2362 t2f[0] = tex->s1;t2f[1] = tex->t2;
2363 t2f[2] = tex->s1;t2f[3] = tex->t1;
2364 t2f[4] = tex->s2;t2f[5] = tex->t1;
2365 t2f[6] = tex->s2;t2f[7] = tex->t2;
2368 // now render the decals all at once
2369 // (this assumes they all use one particle font texture!)
2370 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2371 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2372 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2373 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2376 void R_DrawDecals (void)
2379 int drawdecals = r_drawdecals.integer;
2384 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2386 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2387 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2389 // LordHavoc: early out conditions
2393 decalfade = frametime * 256 / cl_decals_fadetime.value;
2394 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2395 drawdist2 = drawdist2*drawdist2;
2397 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2399 if (!decal->typeindex)
2402 if (killsequence - decal->decalsequence > 0)
2405 if (cl.time > decal->time2 + cl_decals_time.value)
2407 decal->alpha -= decalfade;
2408 if (decal->alpha <= 0)
2414 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2416 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2417 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2423 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2429 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))
2430 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2433 decal->typeindex = 0;
2434 if (cl.free_decal > i)
2438 // reduce cl.num_decals if possible
2439 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2442 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2444 decal_t *olddecals = cl.decals;
2445 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2446 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2447 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2448 Mem_Free(olddecals);
2451 r_refdef.stats.totaldecals = cl.num_decals;
2454 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2456 int surfacelistindex;
2457 int batchstart, batchcount;
2458 const particle_t *p;
2460 rtexture_t *texture;
2461 float *v3f, *t2f, *c4f;
2462 particletexture_t *tex;
2463 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2464 float ambient[3], diffuse[3], diffusenormal[3];
2465 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2466 vec4_t colormultiplier;
2467 float minparticledist_start, minparticledist_end;
2470 RSurf_ActiveWorldEntity();
2472 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));
2474 r_refdef.stats.particles += numsurfaces;
2475 R_Mesh_ResetTextureState();
2476 GL_DepthMask(false);
2477 GL_DepthRange(0, 1);
2478 GL_PolygonOffset(0, 0);
2480 GL_AlphaTest(false);
2481 GL_CullFace(GL_NONE);
2483 spintime = r_refdef.scene.time;
2485 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2486 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2487 dofade = (minparticledist_start < minparticledist_end);
2489 // first generate all the vertices at once
2490 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2492 p = cl.particles + surfacelist[surfacelistindex];
2494 blendmode = (pblend_t)p->blendmode;
2496 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2497 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2501 case PBLEND_INVALID:
2503 alpha = palpha * colormultiplier[3];
2504 // ensure alpha multiplier saturates properly
2507 // additive and modulate can just fade out in fog (this is correct)
2508 if (r_refdef.fogenabled)
2509 alpha *= RSurf_FogVertex(p->org);
2510 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2511 alpha *= 1.0f / 256.0f;
2512 c4f[0] = p->color[0] * alpha;
2513 c4f[1] = p->color[1] * alpha;
2514 c4f[2] = p->color[2] * alpha;
2518 alpha = palpha * colormultiplier[3];
2519 // ensure alpha multiplier saturates properly
2522 // additive and modulate can just fade out in fog (this is correct)
2523 if (r_refdef.fogenabled)
2524 alpha *= RSurf_FogVertex(p->org);
2525 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2526 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2527 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2528 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2532 c4f[0] = p->color[0] * colormultiplier[0];
2533 c4f[1] = p->color[1] * colormultiplier[1];
2534 c4f[2] = p->color[2] * colormultiplier[2];
2535 c4f[3] = palpha * colormultiplier[3];
2536 // note: lighting is not cheap!
2537 if (particletype[p->typeindex].lighting)
2539 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2540 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2541 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2542 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2544 // mix in the fog color
2545 if (r_refdef.fogenabled)
2547 fog = RSurf_FogVertex(p->org);
2549 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2550 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2551 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2555 // copy the color into the other three vertices
2556 Vector4Copy(c4f, c4f + 4);
2557 Vector4Copy(c4f, c4f + 8);
2558 Vector4Copy(c4f, c4f + 12);
2560 size = p->size * cl_particles_size.value;
2561 tex = &particletexture[p->texnum];
2562 switch(p->orientation)
2564 // case PARTICLE_INVALID:
2565 case PARTICLE_BILLBOARD:
2566 if (p->angle + p->spin)
2568 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2569 spinsin = sin(spinrad) * size;
2570 spincos = cos(spinrad) * size;
2571 spinm1 = -p->stretch * spincos;
2574 spinm4 = -p->stretch * spincos;
2575 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2576 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2580 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2581 VectorScale(r_refdef.view.up, size, up);
2584 v3f[ 0] = p->org[0] - right[0] - up[0];
2585 v3f[ 1] = p->org[1] - right[1] - up[1];
2586 v3f[ 2] = p->org[2] - right[2] - up[2];
2587 v3f[ 3] = p->org[0] - right[0] + up[0];
2588 v3f[ 4] = p->org[1] - right[1] + up[1];
2589 v3f[ 5] = p->org[2] - right[2] + up[2];
2590 v3f[ 6] = p->org[0] + right[0] + up[0];
2591 v3f[ 7] = p->org[1] + right[1] + up[1];
2592 v3f[ 8] = p->org[2] + right[2] + up[2];
2593 v3f[ 9] = p->org[0] + right[0] - up[0];
2594 v3f[10] = p->org[1] + right[1] - up[1];
2595 v3f[11] = p->org[2] + right[2] - up[2];
2596 t2f[0] = tex->s1;t2f[1] = tex->t2;
2597 t2f[2] = tex->s1;t2f[3] = tex->t1;
2598 t2f[4] = tex->s2;t2f[5] = tex->t1;
2599 t2f[6] = tex->s2;t2f[7] = tex->t2;
2601 case PARTICLE_ORIENTED_DOUBLESIDED:
2602 VectorVectors(p->vel, baseright, baseup);
2603 if (p->angle + p->spin)
2605 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2606 spinsin = sin(spinrad) * size;
2607 spincos = cos(spinrad) * size;
2608 spinm1 = p->stretch * spincos;
2611 spinm4 = p->stretch * spincos;
2612 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2613 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2617 VectorScale(baseright, size * p->stretch, right);
2618 VectorScale(baseup, size, up);
2620 v3f[ 0] = p->org[0] - right[0] - up[0];
2621 v3f[ 1] = p->org[1] - right[1] - up[1];
2622 v3f[ 2] = p->org[2] - right[2] - up[2];
2623 v3f[ 3] = p->org[0] - right[0] + up[0];
2624 v3f[ 4] = p->org[1] - right[1] + up[1];
2625 v3f[ 5] = p->org[2] - right[2] + up[2];
2626 v3f[ 6] = p->org[0] + right[0] + up[0];
2627 v3f[ 7] = p->org[1] + right[1] + up[1];
2628 v3f[ 8] = p->org[2] + right[2] + up[2];
2629 v3f[ 9] = p->org[0] + right[0] - up[0];
2630 v3f[10] = p->org[1] + right[1] - up[1];
2631 v3f[11] = p->org[2] + right[2] - up[2];
2632 t2f[0] = tex->s1;t2f[1] = tex->t2;
2633 t2f[2] = tex->s1;t2f[3] = tex->t1;
2634 t2f[4] = tex->s2;t2f[5] = tex->t1;
2635 t2f[6] = tex->s2;t2f[7] = tex->t2;
2637 case PARTICLE_SPARK:
2638 len = VectorLength(p->vel);
2639 VectorNormalize2(p->vel, up);
2640 lenfactor = p->stretch * 0.04 * len;
2641 if(lenfactor < size * 0.5)
2642 lenfactor = size * 0.5;
2643 VectorMA(p->org, -lenfactor, up, v);
2644 VectorMA(p->org, lenfactor, up, up2);
2645 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2646 t2f[0] = tex->s1;t2f[1] = tex->t2;
2647 t2f[2] = tex->s1;t2f[3] = tex->t1;
2648 t2f[4] = tex->s2;t2f[5] = tex->t1;
2649 t2f[6] = tex->s2;t2f[7] = tex->t2;
2651 case PARTICLE_VBEAM:
2652 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2653 VectorSubtract(p->vel, p->org, up);
2654 VectorNormalize(up);
2655 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2656 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2657 t2f[0] = tex->s2;t2f[1] = v[0];
2658 t2f[2] = tex->s1;t2f[3] = v[0];
2659 t2f[4] = tex->s1;t2f[5] = v[1];
2660 t2f[6] = tex->s2;t2f[7] = v[1];
2662 case PARTICLE_HBEAM:
2663 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2664 VectorSubtract(p->vel, p->org, up);
2665 VectorNormalize(up);
2666 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2667 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2668 t2f[0] = v[0];t2f[1] = tex->t1;
2669 t2f[2] = v[0];t2f[3] = tex->t2;
2670 t2f[4] = v[1];t2f[5] = tex->t2;
2671 t2f[6] = v[1];t2f[7] = tex->t1;
2676 // now render batches of particles based on blendmode and texture
2677 blendmode = PBLEND_INVALID;
2681 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2682 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2684 p = cl.particles + surfacelist[surfacelistindex];
2686 if (blendmode != p->blendmode)
2688 blendmode = (pblend_t)p->blendmode;
2692 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2694 case PBLEND_INVALID:
2696 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2699 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2703 if (texture != particletexture[p->texnum].texture)
2705 texture = particletexture[p->texnum].texture;
2706 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2709 // iterate until we find a change in settings
2710 batchstart = surfacelistindex++;
2711 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2713 p = cl.particles + surfacelist[surfacelistindex];
2714 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2718 batchcount = surfacelistindex - batchstart;
2719 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2723 void R_DrawParticles (void)
2726 int drawparticles = r_drawparticles.integer;
2727 float minparticledist_start;
2729 float gravity, frametime, f, dist, oldorg[3];
2735 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2736 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2738 // LordHavoc: early out conditions
2739 if (!cl.num_particles)
2742 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2743 gravity = frametime * cl.movevars_gravity;
2744 update = frametime > 0;
2745 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2746 drawdist2 = drawdist2*drawdist2;
2748 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2752 if (cl.free_particle > i)
2753 cl.free_particle = i;
2759 if (p->delayedspawn > cl.time)
2762 p->size += p->sizeincrease * frametime;
2763 p->alpha -= p->alphafade * frametime;
2765 if (p->alpha <= 0 || p->die <= cl.time)
2768 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2770 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2772 if (p->typeindex == pt_blood)
2773 p->size += frametime * 8;
2775 p->vel[2] -= p->gravity * gravity;
2776 f = 1.0f - min(p->liquidfriction * frametime, 1);
2777 VectorScale(p->vel, f, p->vel);
2781 p->vel[2] -= p->gravity * gravity;
2784 f = 1.0f - min(p->airfriction * frametime, 1);
2785 VectorScale(p->vel, f, p->vel);
2789 VectorCopy(p->org, oldorg);
2790 VectorMA(p->org, frametime, p->vel, p->org);
2791 // if (p->bounce && cl.time >= p->delayedcollisions)
2792 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2794 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);
2795 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2796 // or if the trace hit something flagged as NOIMPACT
2797 // then remove the particle
2798 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2800 VectorCopy(trace.endpos, p->org);
2801 // react if the particle hit something
2802 if (trace.fraction < 1)
2804 VectorCopy(trace.endpos, p->org);
2806 if (p->staintexnum >= 0)
2808 // blood - splash on solid
2809 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2812 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2813 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2814 if (cl_decals.integer)
2816 // create a decal for the blood splat
2817 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2818 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2823 if (p->typeindex == pt_blood)
2825 // blood - splash on solid
2826 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2828 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2830 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)));
2831 if (cl_decals.integer)
2833 // create a decal for the blood splat
2834 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);
2839 else if (p->bounce < 0)
2841 // bounce -1 means remove on impact
2846 // anything else - bounce off solid
2847 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2848 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2853 if (VectorLength2(p->vel) < 0.03)
2855 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2857 VectorClear(p->vel);
2861 if (p->typeindex != pt_static)
2863 switch (p->typeindex)
2865 case pt_entityparticle:
2866 // particle that removes itself after one rendered frame
2873 a = CL_PointSuperContents(p->org);
2874 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2878 a = CL_PointSuperContents(p->org);
2879 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2883 a = CL_PointSuperContents(p->org);
2884 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2888 if (cl.time > p->time2)
2891 p->time2 = cl.time + (rand() & 3) * 0.1;
2892 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2893 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2895 a = CL_PointSuperContents(p->org);
2896 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2904 else if (p->delayedspawn > cl.time)
2908 // don't render particles too close to the view (they chew fillrate)
2909 // also don't render particles behind the view (useless)
2910 // further checks to cull to the frustum would be too slow here
2911 switch(p->typeindex)
2914 // beams have no culling
2915 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2918 if(cl_particles_visculling.integer)
2919 if (!r_refdef.viewcache.world_novis)
2920 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2922 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2924 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2927 // anything else just has to be in front of the viewer and visible at this distance
2928 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2929 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2936 if (cl.free_particle > i)
2937 cl.free_particle = i;
2940 // reduce cl.num_particles if possible
2941 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2944 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2946 particle_t *oldparticles = cl.particles;
2947 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2948 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2949 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2950 Mem_Free(oldparticles);