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++)
1692 for (k = 0;k < 16;k++)
1695 VectorMA(org, 128, v2, v);
1696 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1697 if (trace.fraction >= 0.1)
1700 VectorSubtract(trace.endpos, org, v2);
1701 VectorScale(v2, 2.0f, v2);
1702 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);
1708 if (cl_particles_explosions_shell.integer)
1709 R_NewExplosion(org);
1714 CL_ParticleExplosion2
1718 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1721 if (!cl_particles.integer) return;
1723 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1725 k = particlepalette[colorStart + (i % colorLength)];
1726 if (cl_particles_quake.integer)
1727 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);
1729 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);
1733 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1736 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1737 if (cl_particles_sparks.integer)
1739 sparkcount *= cl_particles_quality.value;
1740 while(sparkcount-- > 0)
1741 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);
1745 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1748 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1749 if (cl_particles_smoke.integer)
1751 smokecount *= cl_particles_quality.value;
1752 while(smokecount-- > 0)
1753 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);
1757 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)
1761 if (!cl_particles.integer) return;
1762 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1764 count = (int)(count * cl_particles_quality.value);
1767 k = particlepalette[colorbase + (rand()&3)];
1768 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);
1772 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1775 float minz, maxz, lifetime = 30;
1777 if (!cl_particles.integer) return;
1778 if (dir[2] < 0) // falling
1780 minz = maxs[2] + dir[2] * 0.1;
1783 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1788 maxz = maxs[2] + dir[2] * 0.1;
1790 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1793 count = (int)(count * cl_particles_quality.value);
1798 if (!cl_particles_rain.integer) break;
1799 count *= 4; // ick, this should be in the mod or maps?
1803 k = particlepalette[colorbase + (rand()&3)];
1804 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1805 if (gamemode == GAME_GOODVSBAD2)
1806 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);
1808 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);
1812 if (!cl_particles_snow.integer) break;
1815 k = particlepalette[colorbase + (rand()&3)];
1816 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1817 if (gamemode == GAME_GOODVSBAD2)
1818 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);
1820 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);
1824 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1828 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1829 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1830 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1831 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1832 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1833 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1835 #define PARTICLETEXTURESIZE 64
1836 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1838 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1842 dz = 1 - (dx*dx+dy*dy);
1843 if (dz > 0) // it does hit the sphere
1847 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1848 VectorNormalize(normal);
1849 dot = DotProduct(normal, light);
1850 if (dot > 0.5) // interior reflection
1851 f += ((dot * 2) - 1);
1852 else if (dot < -0.5) // exterior reflection
1853 f += ((dot * -2) - 1);
1855 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1856 VectorNormalize(normal);
1857 dot = DotProduct(normal, light);
1858 if (dot > 0.5) // interior reflection
1859 f += ((dot * 2) - 1);
1860 else if (dot < -0.5) // exterior reflection
1861 f += ((dot * -2) - 1);
1863 f += 16; // just to give it a haze so you can see the outline
1864 f = bound(0, f, 255);
1865 return (unsigned char) f;
1871 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1872 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1874 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1875 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1876 *width = particlefontcellwidth;
1877 *height = particlefontcellheight;
1880 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1882 int basex, basey, w, h, y;
1883 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1884 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1885 Sys_Error("invalid particle texture size for autogenerating");
1886 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1887 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1890 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1893 float cx, cy, dx, dy, f, iradius;
1895 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1896 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1897 iradius = 1.0f / radius;
1898 alpha *= (1.0f / 255.0f);
1899 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1901 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1905 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1910 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1911 d[0] += (int)(f * (blue - d[0]));
1912 d[1] += (int)(f * (green - d[1]));
1913 d[2] += (int)(f * (red - d[2]));
1919 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1922 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1924 data[0] = bound(minb, data[0], maxb);
1925 data[1] = bound(ming, data[1], maxg);
1926 data[2] = bound(minr, data[2], maxr);
1930 void particletextureinvert(unsigned char *data)
1933 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1935 data[0] = 255 - data[0];
1936 data[1] = 255 - data[1];
1937 data[2] = 255 - data[2];
1941 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1942 static void R_InitBloodTextures (unsigned char *particletexturedata)
1945 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1946 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1949 for (i = 0;i < 8;i++)
1951 memset(data, 255, datasize);
1952 for (k = 0;k < 24;k++)
1953 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1954 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1955 particletextureinvert(data);
1956 setuptex(tex_bloodparticle[i], data, particletexturedata);
1960 for (i = 0;i < 8;i++)
1962 memset(data, 255, datasize);
1964 for (j = 1;j < 10;j++)
1965 for (k = min(j, m - 1);k < m;k++)
1966 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1967 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1968 particletextureinvert(data);
1969 setuptex(tex_blooddecal[i], data, particletexturedata);
1975 //uncomment this to make engine save out particle font to a tga file when run
1976 //#define DUMPPARTICLEFONT
1978 static void R_InitParticleTexture (void)
1980 int x, y, d, i, k, m;
1981 int basex, basey, w, h;
1982 float dx, dy, f, s1, t1, s2, t2;
1985 fs_offset_t filesize;
1986 char texturename[MAX_QPATH];
1988 // a note: decals need to modulate (multiply) the background color to
1989 // properly darken it (stain), and they need to be able to alpha fade,
1990 // this is a very difficult challenge because it means fading to white
1991 // (no change to background) rather than black (darkening everything
1992 // behind the whole decal polygon), and to accomplish this the texture is
1993 // inverted (dark red blood on white background becomes brilliant cyan
1994 // and white on black background) so we can alpha fade it to black, then
1995 // we invert it again during the blendfunc to make it work...
1997 #ifndef DUMPPARTICLEFONT
1998 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
2001 particlefonttexture = decalskinframe->base;
2002 // TODO maybe allow custom grid size?
2003 particlefontwidth = image_width;
2004 particlefontheight = image_height;
2005 particlefontcellwidth = image_width / 8;
2006 particlefontcellheight = image_height / 8;
2007 particlefontcols = 8;
2008 particlefontrows = 8;
2013 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2014 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2015 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2016 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2017 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2019 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2020 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2021 particlefontcols = 8;
2022 particlefontrows = 8;
2024 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2027 for (i = 0;i < 8;i++)
2029 memset(data, 255, datasize);
2032 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2033 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2035 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2037 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2038 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2040 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2041 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2043 d = (int)(d * (1-(dx*dx+dy*dy)));
2044 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2045 d = bound(0, d, 255);
2046 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2053 setuptex(tex_smoke[i], data, particletexturedata);
2057 memset(data, 255, datasize);
2058 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2060 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2061 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2063 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2064 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2065 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2068 setuptex(tex_rainsplash, data, particletexturedata);
2071 memset(data, 255, datasize);
2072 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2074 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2075 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2077 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2078 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2079 d = bound(0, d, 255);
2080 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2083 setuptex(tex_particle, data, particletexturedata);
2086 memset(data, 255, datasize);
2087 light[0] = 1;light[1] = 1;light[2] = 1;
2088 VectorNormalize(light);
2089 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2091 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2092 // stretch upper half of bubble by +50% and shrink lower half by -50%
2093 // (this gives an elongated teardrop shape)
2095 dy = (dy - 0.5f) * 2.0f;
2097 dy = (dy - 0.5f) / 1.5f;
2098 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2100 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2101 // shrink bubble width to half
2103 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2106 setuptex(tex_raindrop, data, particletexturedata);
2109 memset(data, 255, datasize);
2110 light[0] = 1;light[1] = 1;light[2] = 1;
2111 VectorNormalize(light);
2112 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2114 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2115 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2117 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2118 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2121 setuptex(tex_bubble, data, particletexturedata);
2123 // Blood particles and blood decals
2124 R_InitBloodTextures (particletexturedata);
2127 for (i = 0;i < 8;i++)
2129 memset(data, 255, datasize);
2130 for (k = 0;k < 12;k++)
2131 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2132 for (k = 0;k < 3;k++)
2133 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2134 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2135 particletextureinvert(data);
2136 setuptex(tex_bulletdecal[i], data, particletexturedata);
2139 #ifdef DUMPPARTICLEFONT
2140 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2143 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2144 particlefonttexture = decalskinframe->base;
2146 Mem_Free(particletexturedata);
2151 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2153 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2154 particletexture[i].texture = particlefonttexture;
2155 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2156 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2157 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2158 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2161 #ifndef DUMPPARTICLEFONT
2162 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2163 if (!particletexture[tex_beam].texture)
2166 unsigned char noise3[64][64], data2[64][16][4];
2168 fractalnoise(&noise3[0][0], 64, 4);
2170 for (y = 0;y < 64;y++)
2172 dy = (y - 0.5f*64) / (64*0.5f-1);
2173 for (x = 0;x < 16;x++)
2175 dx = (x - 0.5f*16) / (16*0.5f-2);
2176 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2177 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2178 data2[y][x][3] = 255;
2182 #ifdef DUMPPARTICLEFONT
2183 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2185 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL);
2187 particletexture[tex_beam].s1 = 0;
2188 particletexture[tex_beam].t1 = 0;
2189 particletexture[tex_beam].s2 = 1;
2190 particletexture[tex_beam].t2 = 1;
2192 // now load an texcoord/texture override file
2193 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2200 if(!COM_ParseToken_Simple(&bufptr, true, false))
2202 if(!strcmp(com_token, "\n"))
2203 continue; // empty line
2204 i = atoi(com_token);
2212 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2214 strlcpy(texturename, com_token, sizeof(texturename));
2215 s1 = atof(com_token);
2216 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2219 t1 = atof(com_token);
2220 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2222 s2 = atof(com_token);
2223 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2225 t2 = atof(com_token);
2226 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2227 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2228 strlcpy(texturename, com_token, sizeof(texturename));
2235 if (!texturename[0])
2237 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2240 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2242 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2245 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2246 particletexture[i].s1 = s1;
2247 particletexture[i].t1 = t1;
2248 particletexture[i].s2 = s2;
2249 particletexture[i].t2 = t2;
2255 static void r_part_start(void)
2258 // generate particlepalette for convenience from the main one
2259 for (i = 0;i < 256;i++)
2260 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2261 particletexturepool = R_AllocTexturePool();
2262 R_InitParticleTexture ();
2263 CL_Particles_LoadEffectInfo();
2266 static void r_part_shutdown(void)
2268 R_FreeTexturePool(&particletexturepool);
2271 static void r_part_newmap(void)
2274 R_SkinFrame_MarkUsed(decalskinframe);
2275 CL_Particles_LoadEffectInfo();
2278 #define BATCHSIZE 256
2279 unsigned short particle_elements[BATCHSIZE*6];
2280 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2282 void R_Particles_Init (void)
2285 for (i = 0;i < BATCHSIZE;i++)
2287 particle_elements[i*6+0] = i*4+0;
2288 particle_elements[i*6+1] = i*4+1;
2289 particle_elements[i*6+2] = i*4+2;
2290 particle_elements[i*6+3] = i*4+0;
2291 particle_elements[i*6+4] = i*4+2;
2292 particle_elements[i*6+5] = i*4+3;
2295 Cvar_RegisterVariable(&r_drawparticles);
2296 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2297 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2298 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2299 Cvar_RegisterVariable(&r_drawdecals);
2300 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2301 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2304 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2306 int surfacelistindex;
2308 float *v3f, *t2f, *c4f;
2309 particletexture_t *tex;
2310 float right[3], up[3], size, ca;
2311 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2312 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2314 RSurf_ActiveWorldEntity();
2316 r_refdef.stats.drawndecals += numsurfaces;
2317 R_Mesh_ResetTextureState();
2318 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2319 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2320 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2321 GL_DepthMask(false);
2322 GL_DepthRange(0, 1);
2323 GL_PolygonOffset(0, 0);
2325 GL_CullFace(GL_NONE);
2327 // generate all the vertices at once
2328 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2330 d = cl.decals + surfacelist[surfacelistindex];
2333 c4f = particle_color4f + 16*surfacelistindex;
2334 ca = d->alpha * alphascale;
2335 // ensure alpha multiplier saturates properly
2336 if (ca > 1.0f / 256.0f)
2338 if (r_refdef.fogenabled)
2339 ca *= RSurf_FogVertex(d->org);
2340 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2341 Vector4Copy(c4f, c4f + 4);
2342 Vector4Copy(c4f, c4f + 8);
2343 Vector4Copy(c4f, c4f + 12);
2345 // calculate vertex positions
2346 size = d->size * cl_particles_size.value;
2347 VectorVectors(d->normal, right, up);
2348 VectorScale(right, size, right);
2349 VectorScale(up, size, up);
2350 v3f = particle_vertex3f + 12*surfacelistindex;
2351 v3f[ 0] = d->org[0] - right[0] - up[0];
2352 v3f[ 1] = d->org[1] - right[1] - up[1];
2353 v3f[ 2] = d->org[2] - right[2] - up[2];
2354 v3f[ 3] = d->org[0] - right[0] + up[0];
2355 v3f[ 4] = d->org[1] - right[1] + up[1];
2356 v3f[ 5] = d->org[2] - right[2] + up[2];
2357 v3f[ 6] = d->org[0] + right[0] + up[0];
2358 v3f[ 7] = d->org[1] + right[1] + up[1];
2359 v3f[ 8] = d->org[2] + right[2] + up[2];
2360 v3f[ 9] = d->org[0] + right[0] - up[0];
2361 v3f[10] = d->org[1] + right[1] - up[1];
2362 v3f[11] = d->org[2] + right[2] - up[2];
2364 // calculate texcoords
2365 tex = &particletexture[d->texnum];
2366 t2f = particle_texcoord2f + 8*surfacelistindex;
2367 t2f[0] = tex->s1;t2f[1] = tex->t2;
2368 t2f[2] = tex->s1;t2f[3] = tex->t1;
2369 t2f[4] = tex->s2;t2f[5] = tex->t1;
2370 t2f[6] = tex->s2;t2f[7] = tex->t2;
2373 // now render the decals all at once
2374 // (this assumes they all use one particle font texture!)
2375 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2376 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2377 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2380 void R_DrawDecals (void)
2383 int drawdecals = r_drawdecals.integer;
2388 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2390 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2391 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2393 // LordHavoc: early out conditions
2397 decalfade = frametime * 256 / cl_decals_fadetime.value;
2398 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2399 drawdist2 = drawdist2*drawdist2;
2401 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2403 if (!decal->typeindex)
2406 if (killsequence - decal->decalsequence > 0)
2409 if (cl.time > decal->time2 + cl_decals_time.value)
2411 decal->alpha -= decalfade;
2412 if (decal->alpha <= 0)
2418 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2420 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2421 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2427 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2433 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))
2434 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2437 decal->typeindex = 0;
2438 if (cl.free_decal > i)
2442 // reduce cl.num_decals if possible
2443 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2446 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2448 decal_t *olddecals = cl.decals;
2449 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2450 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2451 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2452 Mem_Free(olddecals);
2455 r_refdef.stats.totaldecals = cl.num_decals;
2458 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2460 int surfacelistindex;
2461 int batchstart, batchcount;
2462 const particle_t *p;
2464 rtexture_t *texture;
2465 float *v3f, *t2f, *c4f;
2466 particletexture_t *tex;
2467 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2468 float ambient[3], diffuse[3], diffusenormal[3];
2469 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2470 vec4_t colormultiplier;
2471 float minparticledist_start, minparticledist_end;
2474 RSurf_ActiveWorldEntity();
2476 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));
2478 r_refdef.stats.particles += numsurfaces;
2479 R_Mesh_ResetTextureState();
2480 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2481 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2482 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2483 GL_DepthMask(false);
2484 GL_DepthRange(0, 1);
2485 GL_PolygonOffset(0, 0);
2487 GL_AlphaTest(false);
2488 GL_CullFace(GL_NONE);
2490 spintime = r_refdef.scene.time;
2492 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2493 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2494 dofade = (minparticledist_start < minparticledist_end);
2496 // first generate all the vertices at once
2497 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2499 p = cl.particles + surfacelist[surfacelistindex];
2501 blendmode = (pblend_t)p->blendmode;
2503 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2504 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2508 case PBLEND_INVALID:
2510 alpha = palpha * colormultiplier[3];
2511 // ensure alpha multiplier saturates properly
2514 // additive and modulate can just fade out in fog (this is correct)
2515 if (r_refdef.fogenabled)
2516 alpha *= RSurf_FogVertex(p->org);
2517 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2518 alpha *= 1.0f / 256.0f;
2519 c4f[0] = p->color[0] * alpha;
2520 c4f[1] = p->color[1] * alpha;
2521 c4f[2] = p->color[2] * alpha;
2525 alpha = palpha * colormultiplier[3];
2526 // ensure alpha multiplier saturates properly
2529 // additive and modulate can just fade out in fog (this is correct)
2530 if (r_refdef.fogenabled)
2531 alpha *= RSurf_FogVertex(p->org);
2532 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2533 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2534 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2535 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2539 c4f[0] = p->color[0] * colormultiplier[0];
2540 c4f[1] = p->color[1] * colormultiplier[1];
2541 c4f[2] = p->color[2] * colormultiplier[2];
2542 c4f[3] = palpha * colormultiplier[3];
2543 // note: lighting is not cheap!
2544 if (particletype[p->typeindex].lighting)
2546 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2547 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2548 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2549 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2551 // mix in the fog color
2552 if (r_refdef.fogenabled)
2554 fog = RSurf_FogVertex(p->org);
2556 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2557 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2558 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2562 // copy the color into the other three vertices
2563 Vector4Copy(c4f, c4f + 4);
2564 Vector4Copy(c4f, c4f + 8);
2565 Vector4Copy(c4f, c4f + 12);
2567 size = p->size * cl_particles_size.value;
2568 tex = &particletexture[p->texnum];
2569 switch(p->orientation)
2571 // case PARTICLE_INVALID:
2572 case PARTICLE_BILLBOARD:
2573 if (p->angle + p->spin)
2575 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2576 spinsin = sin(spinrad) * size;
2577 spincos = cos(spinrad) * size;
2578 spinm1 = -p->stretch * spincos;
2581 spinm4 = -p->stretch * spincos;
2582 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2583 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2587 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2588 VectorScale(r_refdef.view.up, size, up);
2591 v3f[ 0] = p->org[0] - right[0] - up[0];
2592 v3f[ 1] = p->org[1] - right[1] - up[1];
2593 v3f[ 2] = p->org[2] - right[2] - up[2];
2594 v3f[ 3] = p->org[0] - right[0] + up[0];
2595 v3f[ 4] = p->org[1] - right[1] + up[1];
2596 v3f[ 5] = p->org[2] - right[2] + up[2];
2597 v3f[ 6] = p->org[0] + right[0] + up[0];
2598 v3f[ 7] = p->org[1] + right[1] + up[1];
2599 v3f[ 8] = p->org[2] + right[2] + up[2];
2600 v3f[ 9] = p->org[0] + right[0] - up[0];
2601 v3f[10] = p->org[1] + right[1] - up[1];
2602 v3f[11] = p->org[2] + right[2] - up[2];
2603 t2f[0] = tex->s1;t2f[1] = tex->t2;
2604 t2f[2] = tex->s1;t2f[3] = tex->t1;
2605 t2f[4] = tex->s2;t2f[5] = tex->t1;
2606 t2f[6] = tex->s2;t2f[7] = tex->t2;
2608 case PARTICLE_ORIENTED_DOUBLESIDED:
2609 VectorVectors(p->vel, baseright, baseup);
2610 if (p->angle + p->spin)
2612 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2613 spinsin = sin(spinrad) * size;
2614 spincos = cos(spinrad) * size;
2615 spinm1 = p->stretch * spincos;
2618 spinm4 = p->stretch * spincos;
2619 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2620 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2624 VectorScale(baseright, size * p->stretch, right);
2625 VectorScale(baseup, size, up);
2627 v3f[ 0] = p->org[0] - right[0] - up[0];
2628 v3f[ 1] = p->org[1] - right[1] - up[1];
2629 v3f[ 2] = p->org[2] - right[2] - up[2];
2630 v3f[ 3] = p->org[0] - right[0] + up[0];
2631 v3f[ 4] = p->org[1] - right[1] + up[1];
2632 v3f[ 5] = p->org[2] - right[2] + up[2];
2633 v3f[ 6] = p->org[0] + right[0] + up[0];
2634 v3f[ 7] = p->org[1] + right[1] + up[1];
2635 v3f[ 8] = p->org[2] + right[2] + up[2];
2636 v3f[ 9] = p->org[0] + right[0] - up[0];
2637 v3f[10] = p->org[1] + right[1] - up[1];
2638 v3f[11] = p->org[2] + right[2] - up[2];
2639 t2f[0] = tex->s1;t2f[1] = tex->t2;
2640 t2f[2] = tex->s1;t2f[3] = tex->t1;
2641 t2f[4] = tex->s2;t2f[5] = tex->t1;
2642 t2f[6] = tex->s2;t2f[7] = tex->t2;
2644 case PARTICLE_SPARK:
2645 len = VectorLength(p->vel);
2646 VectorNormalize2(p->vel, up);
2647 lenfactor = p->stretch * 0.04 * len;
2648 if(lenfactor < size * 0.5)
2649 lenfactor = size * 0.5;
2650 VectorMA(p->org, -lenfactor, up, v);
2651 VectorMA(p->org, lenfactor, up, up2);
2652 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2653 t2f[0] = tex->s1;t2f[1] = tex->t2;
2654 t2f[2] = tex->s1;t2f[3] = tex->t1;
2655 t2f[4] = tex->s2;t2f[5] = tex->t1;
2656 t2f[6] = tex->s2;t2f[7] = tex->t2;
2658 case PARTICLE_VBEAM:
2659 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2660 VectorSubtract(p->vel, p->org, up);
2661 VectorNormalize(up);
2662 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2663 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2664 t2f[0] = tex->s2;t2f[1] = v[0];
2665 t2f[2] = tex->s1;t2f[3] = v[0];
2666 t2f[4] = tex->s1;t2f[5] = v[1];
2667 t2f[6] = tex->s2;t2f[7] = v[1];
2669 case PARTICLE_HBEAM:
2670 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2671 VectorSubtract(p->vel, p->org, up);
2672 VectorNormalize(up);
2673 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2674 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2675 t2f[0] = v[0];t2f[1] = tex->t1;
2676 t2f[2] = v[0];t2f[3] = tex->t2;
2677 t2f[4] = v[1];t2f[5] = tex->t2;
2678 t2f[6] = v[1];t2f[7] = tex->t1;
2683 // now render batches of particles based on blendmode and texture
2684 blendmode = PBLEND_INVALID;
2688 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2690 p = cl.particles + surfacelist[surfacelistindex];
2692 if (blendmode != p->blendmode)
2694 blendmode = (pblend_t)p->blendmode;
2698 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2700 case PBLEND_INVALID:
2702 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2705 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2709 if (texture != particletexture[p->texnum].texture)
2711 texture = particletexture[p->texnum].texture;
2712 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2715 // iterate until we find a change in settings
2716 batchstart = surfacelistindex++;
2717 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2719 p = cl.particles + surfacelist[surfacelistindex];
2720 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2724 batchcount = surfacelistindex - batchstart;
2725 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2729 void R_DrawParticles (void)
2732 int drawparticles = r_drawparticles.integer;
2733 float minparticledist_start;
2735 float gravity, frametime, f, dist, oldorg[3];
2741 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2742 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2744 // LordHavoc: early out conditions
2745 if (!cl.num_particles)
2748 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2749 gravity = frametime * cl.movevars_gravity;
2750 update = frametime > 0;
2751 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2752 drawdist2 = drawdist2*drawdist2;
2754 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2758 if (cl.free_particle > i)
2759 cl.free_particle = i;
2765 if (p->delayedspawn > cl.time)
2768 p->size += p->sizeincrease * frametime;
2769 p->alpha -= p->alphafade * frametime;
2771 if (p->alpha <= 0 || p->die <= cl.time)
2774 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2776 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2778 if (p->typeindex == pt_blood)
2779 p->size += frametime * 8;
2781 p->vel[2] -= p->gravity * gravity;
2782 f = 1.0f - min(p->liquidfriction * frametime, 1);
2783 VectorScale(p->vel, f, p->vel);
2787 p->vel[2] -= p->gravity * gravity;
2790 f = 1.0f - min(p->airfriction * frametime, 1);
2791 VectorScale(p->vel, f, p->vel);
2795 VectorCopy(p->org, oldorg);
2796 VectorMA(p->org, frametime, p->vel, p->org);
2797 // if (p->bounce && cl.time >= p->delayedcollisions)
2798 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2800 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);
2801 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2802 // or if the trace hit something flagged as NOIMPACT
2803 // then remove the particle
2804 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2806 VectorCopy(trace.endpos, p->org);
2807 // react if the particle hit something
2808 if (trace.fraction < 1)
2810 VectorCopy(trace.endpos, p->org);
2812 if (p->staintexnum >= 0)
2814 // blood - splash on solid
2815 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2818 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2819 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2820 if (cl_decals.integer)
2822 // create a decal for the blood splat
2823 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2824 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2829 if (p->typeindex == pt_blood)
2831 // blood - splash on solid
2832 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2834 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2836 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)));
2837 if (cl_decals.integer)
2839 // create a decal for the blood splat
2840 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);
2845 else if (p->bounce < 0)
2847 // bounce -1 means remove on impact
2852 // anything else - bounce off solid
2853 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2854 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2859 if (VectorLength2(p->vel) < 0.03)
2861 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2863 VectorClear(p->vel);
2867 if (p->typeindex != pt_static)
2869 switch (p->typeindex)
2871 case pt_entityparticle:
2872 // particle that removes itself after one rendered frame
2879 a = CL_PointSuperContents(p->org);
2880 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2884 a = CL_PointSuperContents(p->org);
2885 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2889 a = CL_PointSuperContents(p->org);
2890 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2894 if (cl.time > p->time2)
2897 p->time2 = cl.time + (rand() & 3) * 0.1;
2898 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2899 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2901 a = CL_PointSuperContents(p->org);
2902 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2910 else if (p->delayedspawn > cl.time)
2914 // don't render particles too close to the view (they chew fillrate)
2915 // also don't render particles behind the view (useless)
2916 // further checks to cull to the frustum would be too slow here
2917 switch(p->typeindex)
2920 // beams have no culling
2921 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2924 if(cl_particles_visculling.integer)
2925 if (!r_refdef.viewcache.world_novis)
2926 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2928 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2930 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2933 // anything else just has to be in front of the viewer and visible at this distance
2934 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2935 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2942 if (cl.free_particle > i)
2943 cl.free_particle = i;
2946 // reduce cl.num_particles if possible
2947 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2950 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2952 particle_t *oldparticles = cl.particles;
2953 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2954 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2955 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2956 Mem_Free(oldparticles);