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