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_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
225 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
226 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
229 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
234 particleeffectinfo_t *info = NULL;
235 const char *text = textstart;
237 for (linenumber = 1;;linenumber++)
240 for (arrayindex = 0;arrayindex < 16;arrayindex++)
241 argv[arrayindex][0] = 0;
244 if (!COM_ParseToken_Simple(&text, true, false))
246 if (!strcmp(com_token, "\n"))
250 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
256 #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;}
257 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
258 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
259 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
260 #define readfloat(var) checkparms(2);var = atof(argv[1])
261 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
262 if (!strcmp(argv[0], "effect"))
266 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
268 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
271 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
273 if (particleeffectname[effectnameindex][0])
275 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
280 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
284 // if we run out of names, abort
285 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
287 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
290 info = particleeffectinfo + numparticleeffectinfo++;
291 info->effectnameindex = effectnameindex;
292 info->particletype = pt_alphastatic;
293 info->blendmode = particletype[info->particletype].blendmode;
294 info->orientation = particletype[info->particletype].orientation;
295 info->tex[0] = tex_particle;
296 info->tex[1] = tex_particle;
297 info->color[0] = 0xFFFFFF;
298 info->color[1] = 0xFFFFFF;
302 info->alpha[1] = 256;
303 info->alpha[2] = 256;
304 info->time[0] = 9999;
305 info->time[1] = 9999;
306 VectorSet(info->lightcolor, 1, 1, 1);
307 info->lightshadow = true;
308 info->lighttime = 9999;
309 info->stretchfactor = 1;
310 info->staincolor[0] = (unsigned int)-1;
311 info->staincolor[1] = (unsigned int)-1;
312 info->staintex[0] = -1;
313 info->staintex[1] = -1;
314 info->stainalpha[0] = 1;
315 info->stainalpha[1] = 1;
316 info->stainsize[0] = 2;
317 info->stainsize[1] = 2;
319 info->rotate[1] = 360;
323 else if (info == NULL)
325 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
328 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
329 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
330 else if (!strcmp(argv[0], "type"))
333 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
334 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
335 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
336 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
337 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
338 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
339 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
340 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
341 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
342 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
343 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
344 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
345 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
346 info->blendmode = particletype[info->particletype].blendmode;
347 info->orientation = particletype[info->particletype].orientation;
349 else if (!strcmp(argv[0], "blend"))
352 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
353 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
354 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
355 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
357 else if (!strcmp(argv[0], "orientation"))
360 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
361 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
362 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
363 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
364 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
366 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
367 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
368 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
369 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
370 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
371 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
372 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
373 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
374 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
375 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
376 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
377 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
378 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
379 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
380 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
381 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
382 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
383 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
384 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
385 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
386 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
387 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
388 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
389 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
390 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
391 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
392 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
393 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
394 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
395 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; }
396 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
398 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
407 int CL_ParticleEffectIndexForName(const char *name)
410 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
411 if (!strcmp(particleeffectname[i], name))
416 const char *CL_ParticleEffectNameForIndex(int i)
418 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
420 return particleeffectname[i];
423 // MUST match effectnameindex_t in client.h
424 static const char *standardeffectnames[EFFECT_TOTAL] =
448 "TE_TEI_BIGEXPLOSION",
464 void CL_Particles_LoadEffectInfo(void)
468 unsigned char *filedata;
469 fs_offset_t filesize;
470 char filename[MAX_QPATH];
471 numparticleeffectinfo = 0;
472 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
473 memset(particleeffectname, 0, sizeof(particleeffectname));
474 for (i = 0;i < EFFECT_TOTAL;i++)
475 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
476 for (filepass = 0;;filepass++)
479 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
480 else if (filepass == 1)
482 if (!cl.worldbasename[0])
484 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
488 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
491 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
501 void CL_ReadPointFile_f (void);
502 void CL_Particles_Init (void)
504 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)");
505 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
507 Cvar_RegisterVariable (&cl_particles);
508 Cvar_RegisterVariable (&cl_particles_quality);
509 Cvar_RegisterVariable (&cl_particles_alpha);
510 Cvar_RegisterVariable (&cl_particles_size);
511 Cvar_RegisterVariable (&cl_particles_quake);
512 Cvar_RegisterVariable (&cl_particles_blood);
513 Cvar_RegisterVariable (&cl_particles_blood_alpha);
514 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
515 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
516 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
517 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
518 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
519 Cvar_RegisterVariable (&cl_particles_explosions_shell);
520 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
521 Cvar_RegisterVariable (&cl_particles_rain);
522 Cvar_RegisterVariable (&cl_particles_snow);
523 Cvar_RegisterVariable (&cl_particles_smoke);
524 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
525 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
526 Cvar_RegisterVariable (&cl_particles_sparks);
527 Cvar_RegisterVariable (&cl_particles_bubbles);
528 Cvar_RegisterVariable (&cl_particles_visculling);
529 Cvar_RegisterVariable (&cl_particles_collisions);
530 Cvar_RegisterVariable (&cl_decals);
531 Cvar_RegisterVariable (&cl_decals_visculling);
532 Cvar_RegisterVariable (&cl_decals_time);
533 Cvar_RegisterVariable (&cl_decals_fadetime);
534 Cvar_RegisterVariable (&cl_decals_newsystem);
535 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
536 Cvar_RegisterVariable (&cl_decals_models);
537 Cvar_RegisterVariable (&cl_decals_bias);
538 Cvar_RegisterVariable (&cl_decals_max);
541 void CL_Particles_Shutdown (void)
545 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
546 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
548 // list of all 26 parameters:
549 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
550 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
551 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
552 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
553 // palpha - opacity of particle as 0-255 (can be more than 255)
554 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
555 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
556 // pgravity - how much effect gravity has on the particle (0-1)
557 // 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
558 // px,py,pz - starting origin of particle
559 // pvx,pvy,pvz - starting velocity of particle
560 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
561 // blendmode - one of the PBLEND_ values
562 // orientation - one of the PARTICLE_ values
563 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
564 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
565 // stainalpha: opacity of the stain as factor for alpha
566 // stainsize: size of the stain as factor for palpha
567 // angle: base rotation of the particle geometry around its center normal
568 // spin: rotation speed of the particle geometry around its center normal
569 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])
574 if (!cl_particles.integer)
576 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
577 if (cl.free_particle >= cl.max_particles)
580 lifetime = palpha / min(1, palphafade);
581 part = &cl.particles[cl.free_particle++];
582 if (cl.num_particles < cl.free_particle)
583 cl.num_particles = cl.free_particle;
584 memset(part, 0, sizeof(*part));
585 VectorCopy(sortorigin, part->sortorigin);
586 part->typeindex = ptypeindex;
587 part->blendmode = blendmode;
588 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
590 particletexture_t *tex = &particletexture[ptex];
591 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
592 part->orientation = PARTICLE_VBEAM;
594 part->orientation = PARTICLE_HBEAM;
597 part->orientation = orientation;
598 l2 = (int)lhrandom(0.5, 256.5);
600 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
601 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
602 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
603 part->alpha = palpha;
604 part->alphafade = palphafade;
605 part->staintexnum = staintex;
606 if(staincolor1 >= 0 && staincolor2 >= 0)
608 l2 = (int)lhrandom(0.5, 256.5);
610 if(blendmode == PBLEND_INVMOD)
612 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
613 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
614 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
618 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
619 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
620 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
622 if(r > 0xFF) r = 0xFF;
623 if(g > 0xFF) g = 0xFF;
624 if(b > 0xFF) b = 0xFF;
628 r = part->color[0]; // -1 is shorthand for stain = particle color
632 part->staincolor[0] = r;
633 part->staincolor[1] = g;
634 part->staincolor[2] = b;
635 part->stainalpha = palpha * stainalpha;
636 part->stainsize = psize * stainsize;
639 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
641 part->color[0] *= tint[0];
642 part->color[1] *= tint[1];
643 part->color[2] *= tint[2];
645 part->alpha *= tint[3];
646 part->alphafade *= tint[3];
647 part->stainalpha *= tint[3];
651 part->sizeincrease = psizeincrease;
652 part->gravity = pgravity;
653 part->bounce = pbounce;
654 part->stretch = stretch;
656 part->org[0] = px + originjitter * v[0];
657 part->org[1] = py + originjitter * v[1];
658 part->org[2] = pz + originjitter * v[2];
659 part->vel[0] = pvx + velocityjitter * v[0];
660 part->vel[1] = pvy + velocityjitter * v[1];
661 part->vel[2] = pvz + velocityjitter * v[2];
663 part->airfriction = pairfriction;
664 part->liquidfriction = pliquidfriction;
665 part->die = cl.time + lifetime;
666 part->delayedspawn = cl.time;
667 // part->delayedcollisions = 0;
668 part->qualityreduction = pqualityreduction;
671 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
672 if (part->typeindex == pt_rain)
676 float lifetime = part->die - cl.time;
679 // turn raindrop into simple spark and create delayedspawn splash effect
680 part->typeindex = pt_spark;
682 VectorMA(part->org, lifetime, part->vel, endvec);
683 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
684 part->die = cl.time + lifetime * trace.fraction;
685 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);
688 part2->delayedspawn = part->die;
689 part2->die += part->die - cl.time;
690 for (i = rand() & 7;i < 10;i++)
692 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);
695 part2->delayedspawn = part->die;
696 part2->die += part->die - cl.time;
702 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
704 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
707 VectorMA(part->org, lifetime, part->vel, endvec);
708 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
709 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
716 static void CL_ImmediateBloodStain(particle_t *part)
721 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
722 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
724 VectorCopy(part->vel, v);
726 staintex = part->staintexnum;
727 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);
730 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
731 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
733 VectorCopy(part->vel, v);
735 staintex = tex_blooddecal[rand()&7];
736 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);
740 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
744 entity_render_t *ent = &cl.entities[hitent].render;
745 unsigned char color[3];
746 if (!cl_decals.integer)
748 if (!ent->allowdecals)
751 l2 = (int)lhrandom(0.5, 256.5);
753 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
754 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
755 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
757 if (cl_decals_newsystem.integer)
759 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);
763 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
764 if (cl.free_decal >= cl.max_decals)
766 decal = &cl.decals[cl.free_decal++];
767 if (cl.num_decals < cl.free_decal)
768 cl.num_decals = cl.free_decal;
769 memset(decal, 0, sizeof(*decal));
770 decal->decalsequence = cl.decalsequence++;
771 decal->typeindex = pt_decal;
772 decal->texnum = texnum;
773 VectorMA(org, cl_decals_bias.value, normal, decal->org);
774 VectorCopy(normal, decal->normal);
776 decal->alpha = alpha;
777 decal->time2 = cl.time;
778 decal->color[0] = color[0];
779 decal->color[1] = color[1];
780 decal->color[2] = color[2];
781 decal->owner = hitent;
782 decal->clusterindex = -1000; // no vis culling unless we're sure
785 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
786 decal->ownermodel = cl.entities[decal->owner].render.model;
787 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
788 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
792 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
794 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
796 decal->clusterindex = leaf->clusterindex;
801 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
804 float bestfrac, bestorg[3], bestnormal[3];
806 int besthitent = 0, hitent;
809 for (i = 0;i < 32;i++)
812 VectorMA(org, maxdist, org2, org2);
813 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
814 // take the closest trace result that doesn't end up hitting a NOMARKS
815 // surface (sky for example)
816 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
818 bestfrac = trace.fraction;
820 VectorCopy(trace.endpos, bestorg);
821 VectorCopy(trace.plane.normal, bestnormal);
825 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
828 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
829 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
830 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)
833 matrix4x4_t tempmatrix;
835 VectorLerp(originmins, 0.5, originmaxs, center);
836 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
837 if (effectnameindex == EFFECT_SVC_PARTICLE)
839 if (cl_particles.integer)
841 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
843 CL_ParticleExplosion(center);
844 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
845 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
848 count *= cl_particles_quality.value;
849 for (;count > 0;count--)
851 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
852 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);
857 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
858 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
859 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
860 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
861 else if (effectnameindex == EFFECT_TE_SPIKE)
863 if (cl_particles_bulletimpacts.integer)
865 if (cl_particles_quake.integer)
867 if (cl_particles_smoke.integer)
868 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
872 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
873 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
874 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);
878 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
879 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
881 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
883 if (cl_particles_bulletimpacts.integer)
885 if (cl_particles_quake.integer)
887 if (cl_particles_smoke.integer)
888 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
892 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
893 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
894 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);
898 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
899 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
900 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);
902 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
904 if (cl_particles_bulletimpacts.integer)
906 if (cl_particles_quake.integer)
908 if (cl_particles_smoke.integer)
909 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
913 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
914 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
915 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);
919 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
920 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
922 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
924 if (cl_particles_bulletimpacts.integer)
926 if (cl_particles_quake.integer)
928 if (cl_particles_smoke.integer)
929 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
933 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
934 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
935 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);
939 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
940 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
941 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);
943 else if (effectnameindex == EFFECT_TE_BLOOD)
945 if (!cl_particles_blood.integer)
947 if (cl_particles_quake.integer)
948 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
951 static double bloodaccumulator = 0;
952 qboolean immediatebloodstain = true;
953 //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);
954 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
955 for (;bloodaccumulator > 0;bloodaccumulator--)
957 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);
958 if (immediatebloodstain && part)
960 immediatebloodstain = false;
961 CL_ImmediateBloodStain(part);
966 else if (effectnameindex == EFFECT_TE_SPARK)
967 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
968 else if (effectnameindex == EFFECT_TE_PLASMABURN)
970 // plasma scorch mark
971 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
972 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
973 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
975 else if (effectnameindex == EFFECT_TE_GUNSHOT)
977 if (cl_particles_bulletimpacts.integer)
979 if (cl_particles_quake.integer)
980 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
983 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
984 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
985 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);
989 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
990 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
992 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
994 if (cl_particles_bulletimpacts.integer)
996 if (cl_particles_quake.integer)
997 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1000 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1001 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1002 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);
1006 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1007 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1008 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);
1010 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1012 CL_ParticleExplosion(center);
1013 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);
1015 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1017 CL_ParticleExplosion(center);
1018 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);
1020 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1022 if (cl_particles_quake.integer)
1025 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1028 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);
1030 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);
1034 CL_ParticleExplosion(center);
1035 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);
1037 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1038 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);
1039 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1041 count *= cl_particles_quality.value;
1043 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);
1045 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1047 float i, j, inc, vel;
1050 inc = 8 / cl_particles_quality.value;
1051 for (i = -128;i < 128;i += inc)
1053 for (j = -128;j < 128;j += inc)
1055 dir[0] = j + lhrandom(0, inc);
1056 dir[1] = i + lhrandom(0, inc);
1058 org[0] = center[0] + dir[0];
1059 org[1] = center[1] + dir[1];
1060 org[2] = center[2] + lhrandom(0, 64);
1061 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1062 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);
1066 else if (effectnameindex == EFFECT_TE_TELEPORT)
1068 float i, j, k, inc, vel;
1071 if (cl_particles_quake.integer)
1072 inc = 4 / cl_particles_quality.value;
1074 inc = 8 / cl_particles_quality.value;
1075 for (i = -16;i < 16;i += inc)
1077 for (j = -16;j < 16;j += inc)
1079 for (k = -24;k < 32;k += inc)
1081 VectorSet(dir, i*8, j*8, k*8);
1082 VectorNormalize(dir);
1083 vel = lhrandom(50, 113);
1084 if (cl_particles_quake.integer)
1085 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);
1087 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);
1091 if (!cl_particles_quake.integer)
1092 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);
1093 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);
1095 else if (effectnameindex == EFFECT_TE_TEI_G3)
1096 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);
1097 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1099 if (cl_particles_smoke.integer)
1101 count *= 0.25f * cl_particles_quality.value;
1103 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);
1106 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1108 CL_ParticleExplosion(center);
1109 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);
1111 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1114 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1115 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1116 if (cl_particles_smoke.integer)
1117 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1118 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);
1119 if (cl_particles_sparks.integer)
1120 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1121 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);
1122 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);
1124 else if (effectnameindex == EFFECT_EF_FLAME)
1126 count *= 300 * cl_particles_quality.value;
1128 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);
1129 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);
1131 else if (effectnameindex == EFFECT_EF_STARDUST)
1133 count *= 200 * cl_particles_quality.value;
1135 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);
1136 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);
1138 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1142 int smoke, blood, bubbles, r, color;
1144 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1147 Vector4Set(light, 0, 0, 0, 0);
1149 if (effectnameindex == EFFECT_TR_ROCKET)
1150 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1151 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1153 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1154 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1156 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1158 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1159 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1163 matrix4x4_t tempmatrix;
1164 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1165 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);
1166 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1170 if (!spawnparticles)
1173 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1176 VectorSubtract(originmaxs, originmins, dir);
1177 len = VectorNormalizeLength(dir);
1180 dec = -ent->persistent.trail_time;
1181 ent->persistent.trail_time += len;
1182 if (ent->persistent.trail_time < 0.01f)
1185 // if we skip out, leave it reset
1186 ent->persistent.trail_time = 0.0f;
1191 // advance into this frame to reach the first puff location
1192 VectorMA(originmins, dec, dir, pos);
1195 smoke = cl_particles.integer && cl_particles_smoke.integer;
1196 blood = cl_particles.integer && cl_particles_blood.integer;
1197 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1198 qd = 1.0f / cl_particles_quality.value;
1205 if (effectnameindex == EFFECT_TR_BLOOD)
1207 if (cl_particles_quake.integer)
1209 color = particlepalette[67 + (rand()&3)];
1210 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);
1215 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);
1218 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1220 if (cl_particles_quake.integer)
1223 color = particlepalette[67 + (rand()&3)];
1224 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);
1229 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);
1235 if (effectnameindex == EFFECT_TR_ROCKET)
1237 if (cl_particles_quake.integer)
1240 color = particlepalette[ramp3[r]];
1241 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);
1245 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);
1246 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);
1249 else if (effectnameindex == EFFECT_TR_GRENADE)
1251 if (cl_particles_quake.integer)
1254 color = particlepalette[ramp3[r]];
1255 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);
1259 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);
1262 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1264 if (cl_particles_quake.integer)
1267 color = particlepalette[52 + (rand()&7)];
1268 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);
1269 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 else if (gamemode == GAME_GOODVSBAD2)
1274 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);
1278 color = particlepalette[20 + (rand()&7)];
1279 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);
1282 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1284 if (cl_particles_quake.integer)
1287 color = particlepalette[230 + (rand()&7)];
1288 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);
1289 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);
1293 color = particlepalette[226 + (rand()&7)];
1294 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);
1297 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1299 if (cl_particles_quake.integer)
1301 color = particlepalette[152 + (rand()&3)];
1302 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);
1304 else if (gamemode == GAME_GOODVSBAD2)
1307 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);
1309 else if (gamemode == GAME_PRYDON)
1312 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);
1315 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);
1317 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1320 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);
1322 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1325 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);
1327 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1328 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);
1332 if (effectnameindex == EFFECT_TR_ROCKET)
1333 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);
1334 else if (effectnameindex == EFFECT_TR_GRENADE)
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);
1337 // advance to next time and position
1340 VectorMA (pos, dec, dir, pos);
1343 ent->persistent.trail_time = len;
1346 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1349 // this is also called on point effects with spawndlight = true and
1350 // spawnparticles = true
1351 // it is called CL_ParticleTrail because most code does not want to supply
1352 // these parameters, only trail handling does
1353 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])
1355 qboolean found = false;
1356 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1358 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1359 return; // no such effect
1361 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1363 int effectinfoindex;
1366 particleeffectinfo_t *info;
1373 qboolean underwater;
1374 qboolean immediatebloodstain;
1376 float avgtint[4], tint[4], tintlerp;
1377 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1378 VectorLerp(originmins, 0.5, originmaxs, center);
1379 supercontents = CL_PointSuperContents(center);
1380 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1381 VectorSubtract(originmaxs, originmins, traildir);
1382 traillen = VectorLength(traildir);
1383 VectorNormalize(traildir);
1386 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1390 Vector4Set(avgtint, 1, 1, 1, 1);
1392 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1394 if (info->effectnameindex == effectnameindex)
1397 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1399 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1402 // spawn a dlight if requested
1403 if (info->lightradiusstart > 0 && spawndlight)
1405 matrix4x4_t tempmatrix;
1406 if (info->trailspacing > 0)
1407 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1409 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1410 if (info->lighttime > 0 && info->lightradiusfade > 0)
1412 // light flash (explosion, etc)
1413 // called when effect starts
1414 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);
1416 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1419 // called by CL_LinkNetworkEntity
1420 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1421 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1422 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1423 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1424 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);
1425 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1429 if (!spawnparticles)
1434 if (info->tex[1] > info->tex[0])
1436 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1437 tex = min(tex, info->tex[1] - 1);
1439 if(info->staintex[0] < 0)
1440 staintex = info->staintex[0];
1443 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1444 staintex = min(staintex, info->staintex[1] - 1);
1446 if (info->particletype == pt_decal)
1447 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]);
1448 else if (info->orientation == PARTICLE_HBEAM)
1449 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);
1452 if (!cl_particles.integer)
1454 switch (info->particletype)
1456 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1457 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1458 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1459 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1460 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1461 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1464 VectorCopy(originmins, trailpos);
1465 if (info->trailspacing > 0)
1467 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1468 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1469 immediatebloodstain = false;
1473 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1475 immediatebloodstain = info->particletype == pt_blood || staintex;
1477 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1478 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1480 if (info->tex[1] > info->tex[0])
1482 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1483 tex = min(tex, info->tex[1] - 1);
1487 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1488 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1489 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1493 tintlerp = lhrandom(0, 1);
1494 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1497 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);
1498 if (immediatebloodstain && part)
1500 immediatebloodstain = false;
1501 CL_ImmediateBloodStain(part);
1504 VectorMA(trailpos, trailstep, traildir, trailpos);
1511 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1514 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)
1516 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1524 void CL_EntityParticles (const entity_t *ent)
1527 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1528 static vec3_t avelocities[NUMVERTEXNORMALS];
1529 if (!cl_particles.integer) return;
1530 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1532 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1534 if (!avelocities[0][0])
1535 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1536 avelocities[0][i] = lhrandom(0, 2.55);
1538 for (i = 0;i < NUMVERTEXNORMALS;i++)
1540 yaw = cl.time * avelocities[i][0];
1541 pitch = cl.time * avelocities[i][1];
1542 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1543 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1544 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1545 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);
1550 void CL_ReadPointFile_f (void)
1552 vec3_t org, leakorg;
1554 char *pointfile = NULL, *pointfilepos, *t, tchar;
1555 char name[MAX_QPATH];
1560 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1561 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1564 Con_Printf("Could not open %s\n", name);
1568 Con_Printf("Reading %s...\n", name);
1569 VectorClear(leakorg);
1572 pointfilepos = pointfile;
1573 while (*pointfilepos)
1575 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1580 while (*t && *t != '\n' && *t != '\r')
1584 #if _MSC_VER >= 1400
1585 #define sscanf sscanf_s
1587 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1593 VectorCopy(org, leakorg);
1596 if (cl.num_particles < cl.max_particles - 3)
1599 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);
1602 Mem_Free(pointfile);
1603 VectorCopy(leakorg, org);
1604 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1606 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);
1607 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);
1608 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);
1613 CL_ParseParticleEffect
1615 Parse an effect out of the server message
1618 void CL_ParseParticleEffect (void)
1621 int i, count, msgcount, color;
1623 MSG_ReadVector(org, cls.protocol);
1624 for (i=0 ; i<3 ; i++)
1625 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1626 msgcount = MSG_ReadByte ();
1627 color = MSG_ReadByte ();
1629 if (msgcount == 255)
1634 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1639 CL_ParticleExplosion
1643 void CL_ParticleExplosion (const vec3_t org)
1649 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1650 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1652 if (cl_particles_quake.integer)
1654 for (i = 0;i < 1024;i++)
1660 color = particlepalette[ramp1[r]];
1661 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);
1665 color = particlepalette[ramp2[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, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1672 i = CL_PointSuperContents(org);
1673 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1675 if (cl_particles.integer && cl_particles_bubbles.integer)
1676 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1677 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);
1681 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1683 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1687 for (k = 0;k < 16;k++)
1690 VectorMA(org, 128, v2, v);
1691 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1692 if (trace.fraction >= 0.1)
1695 VectorSubtract(trace.endpos, org, v2);
1696 VectorScale(v2, 2.0f, v2);
1697 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);
1703 if (cl_particles_explosions_shell.integer)
1704 R_NewExplosion(org);
1709 CL_ParticleExplosion2
1713 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1716 if (!cl_particles.integer) return;
1718 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1720 k = particlepalette[colorStart + (i % colorLength)];
1721 if (cl_particles_quake.integer)
1722 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);
1724 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);
1728 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1731 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1732 if (cl_particles_sparks.integer)
1734 sparkcount *= cl_particles_quality.value;
1735 while(sparkcount-- > 0)
1736 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);
1740 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1743 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1744 if (cl_particles_smoke.integer)
1746 smokecount *= cl_particles_quality.value;
1747 while(smokecount-- > 0)
1748 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);
1752 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)
1756 if (!cl_particles.integer) return;
1757 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1759 count = (int)(count * cl_particles_quality.value);
1762 k = particlepalette[colorbase + (rand()&3)];
1763 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);
1767 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1770 float minz, maxz, lifetime = 30;
1772 if (!cl_particles.integer) return;
1773 if (dir[2] < 0) // falling
1775 minz = maxs[2] + dir[2] * 0.1;
1778 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1783 maxz = maxs[2] + dir[2] * 0.1;
1785 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1788 count = (int)(count * cl_particles_quality.value);
1793 if (!cl_particles_rain.integer) break;
1794 count *= 4; // ick, this should be in the mod or maps?
1798 k = particlepalette[colorbase + (rand()&3)];
1799 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1800 if (gamemode == GAME_GOODVSBAD2)
1801 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);
1803 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);
1807 if (!cl_particles_snow.integer) break;
1810 k = particlepalette[colorbase + (rand()&3)];
1811 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1812 if (gamemode == GAME_GOODVSBAD2)
1813 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);
1815 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);
1819 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1823 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1824 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1825 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1826 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1828 #define PARTICLETEXTURESIZE 64
1829 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1831 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1835 dz = 1 - (dx*dx+dy*dy);
1836 if (dz > 0) // it does hit the sphere
1840 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1841 VectorNormalize(normal);
1842 dot = DotProduct(normal, light);
1843 if (dot > 0.5) // interior reflection
1844 f += ((dot * 2) - 1);
1845 else if (dot < -0.5) // exterior reflection
1846 f += ((dot * -2) - 1);
1848 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1849 VectorNormalize(normal);
1850 dot = DotProduct(normal, light);
1851 if (dot > 0.5) // interior reflection
1852 f += ((dot * 2) - 1);
1853 else if (dot < -0.5) // exterior reflection
1854 f += ((dot * -2) - 1);
1856 f += 16; // just to give it a haze so you can see the outline
1857 f = bound(0, f, 255);
1858 return (unsigned char) f;
1864 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1865 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1867 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1868 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1869 *width = particlefontcellwidth;
1870 *height = particlefontcellheight;
1873 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1875 int basex, basey, w, h, y;
1876 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1877 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1878 Sys_Error("invalid particle texture size for autogenerating");
1879 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1880 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1883 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1886 float cx, cy, dx, dy, f, iradius;
1888 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1889 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1890 iradius = 1.0f / radius;
1891 alpha *= (1.0f / 255.0f);
1892 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1894 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1898 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1903 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1904 d[0] += (int)(f * (blue - d[0]));
1905 d[1] += (int)(f * (green - d[1]));
1906 d[2] += (int)(f * (red - d[2]));
1912 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1915 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1917 data[0] = bound(minb, data[0], maxb);
1918 data[1] = bound(ming, data[1], maxg);
1919 data[2] = bound(minr, data[2], maxr);
1923 void particletextureinvert(unsigned char *data)
1926 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1928 data[0] = 255 - data[0];
1929 data[1] = 255 - data[1];
1930 data[2] = 255 - data[2];
1934 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1935 static void R_InitBloodTextures (unsigned char *particletexturedata)
1938 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1939 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1942 for (i = 0;i < 8;i++)
1944 memset(data, 255, datasize);
1945 for (k = 0;k < 24;k++)
1946 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1947 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1948 particletextureinvert(data);
1949 setuptex(tex_bloodparticle[i], data, particletexturedata);
1953 for (i = 0;i < 8;i++)
1955 memset(data, 255, datasize);
1957 for (j = 1;j < 10;j++)
1958 for (k = min(j, m - 1);k < m;k++)
1959 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1960 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1961 particletextureinvert(data);
1962 setuptex(tex_blooddecal[i], data, particletexturedata);
1968 //uncomment this to make engine save out particle font to a tga file when run
1969 //#define DUMPPARTICLEFONT
1971 static void R_InitParticleTexture (void)
1973 int x, y, d, i, k, m;
1974 int basex, basey, w, h;
1975 float dx, dy, f, s1, t1, s2, t2;
1978 fs_offset_t filesize;
1979 char texturename[MAX_QPATH];
1981 // a note: decals need to modulate (multiply) the background color to
1982 // properly darken it (stain), and they need to be able to alpha fade,
1983 // this is a very difficult challenge because it means fading to white
1984 // (no change to background) rather than black (darkening everything
1985 // behind the whole decal polygon), and to accomplish this the texture is
1986 // inverted (dark red blood on white background becomes brilliant cyan
1987 // and white on black background) so we can alpha fade it to black, then
1988 // we invert it again during the blendfunc to make it work...
1990 #ifndef DUMPPARTICLEFONT
1991 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1994 particlefonttexture = decalskinframe->base;
1995 // TODO maybe allow custom grid size?
1996 particlefontwidth = image_width;
1997 particlefontheight = image_height;
1998 particlefontcellwidth = image_width / 8;
1999 particlefontcellheight = image_height / 8;
2000 particlefontcols = 8;
2001 particlefontrows = 8;
2006 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2007 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2008 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2009 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2010 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2012 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2013 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2014 particlefontcols = 8;
2015 particlefontrows = 8;
2017 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2020 for (i = 0;i < 8;i++)
2022 memset(data, 255, datasize);
2025 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2026 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2028 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2030 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2031 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2033 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2034 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2036 d = (int)(d * (1-(dx*dx+dy*dy)));
2037 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2038 d = bound(0, d, 255);
2039 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2046 setuptex(tex_smoke[i], data, particletexturedata);
2050 memset(data, 255, datasize);
2051 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2053 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2054 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2056 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2057 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2058 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2061 setuptex(tex_rainsplash, data, particletexturedata);
2064 memset(data, 255, datasize);
2065 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2067 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2068 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2070 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2071 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2072 d = bound(0, d, 255);
2073 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2076 setuptex(tex_particle, data, particletexturedata);
2079 memset(data, 255, datasize);
2080 light[0] = 1;light[1] = 1;light[2] = 1;
2081 VectorNormalize(light);
2082 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2084 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2085 // stretch upper half of bubble by +50% and shrink lower half by -50%
2086 // (this gives an elongated teardrop shape)
2088 dy = (dy - 0.5f) * 2.0f;
2090 dy = (dy - 0.5f) / 1.5f;
2091 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2093 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2094 // shrink bubble width to half
2096 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2099 setuptex(tex_raindrop, data, particletexturedata);
2102 memset(data, 255, datasize);
2103 light[0] = 1;light[1] = 1;light[2] = 1;
2104 VectorNormalize(light);
2105 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2107 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2108 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2110 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2111 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2114 setuptex(tex_bubble, data, particletexturedata);
2116 // Blood particles and blood decals
2117 R_InitBloodTextures (particletexturedata);
2120 for (i = 0;i < 8;i++)
2122 memset(data, 255, datasize);
2123 for (k = 0;k < 12;k++)
2124 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2125 for (k = 0;k < 3;k++)
2126 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2127 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2128 particletextureinvert(data);
2129 setuptex(tex_bulletdecal[i], data, particletexturedata);
2132 #ifdef DUMPPARTICLEFONT
2133 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2136 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2137 particlefonttexture = decalskinframe->base;
2139 Mem_Free(particletexturedata);
2144 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2146 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2147 particletexture[i].texture = particlefonttexture;
2148 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2149 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2150 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2151 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2154 #ifndef DUMPPARTICLEFONT
2155 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2156 if (!particletexture[tex_beam].texture)
2159 unsigned char noise3[64][64], data2[64][16][4];
2161 fractalnoise(&noise3[0][0], 64, 4);
2163 for (y = 0;y < 64;y++)
2165 dy = (y - 0.5f*64) / (64*0.5f-1);
2166 for (x = 0;x < 16;x++)
2168 dx = (x - 0.5f*16) / (16*0.5f-2);
2169 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2170 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2171 data2[y][x][3] = 255;
2175 #ifdef DUMPPARTICLEFONT
2176 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2178 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2180 particletexture[tex_beam].s1 = 0;
2181 particletexture[tex_beam].t1 = 0;
2182 particletexture[tex_beam].s2 = 1;
2183 particletexture[tex_beam].t2 = 1;
2185 // now load an texcoord/texture override file
2186 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2193 if(!COM_ParseToken_Simple(&bufptr, true, false))
2195 if(!strcmp(com_token, "\n"))
2196 continue; // empty line
2197 i = atoi(com_token);
2205 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2207 s1 = atof(com_token);
2208 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2210 t1 = atof(com_token);
2211 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2213 s2 = atof(com_token);
2214 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2216 t2 = atof(com_token);
2217 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2218 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2219 strlcpy(texturename, com_token, sizeof(texturename));
2226 strlcpy(texturename, com_token, sizeof(texturename));
2229 if (!texturename[0])
2231 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2234 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2236 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2239 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2240 particletexture[i].s1 = s1;
2241 particletexture[i].t1 = t1;
2242 particletexture[i].s2 = s2;
2243 particletexture[i].t2 = t2;
2249 static void r_part_start(void)
2252 // generate particlepalette for convenience from the main one
2253 for (i = 0;i < 256;i++)
2254 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2255 particletexturepool = R_AllocTexturePool();
2256 R_InitParticleTexture ();
2257 CL_Particles_LoadEffectInfo();
2260 static void r_part_shutdown(void)
2262 R_FreeTexturePool(&particletexturepool);
2265 static void r_part_newmap(void)
2268 R_SkinFrame_MarkUsed(decalskinframe);
2269 CL_Particles_LoadEffectInfo();
2272 #define BATCHSIZE 256
2273 unsigned short particle_elements[BATCHSIZE*6];
2274 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2276 void R_Particles_Init (void)
2279 for (i = 0;i < BATCHSIZE;i++)
2281 particle_elements[i*6+0] = i*4+0;
2282 particle_elements[i*6+1] = i*4+1;
2283 particle_elements[i*6+2] = i*4+2;
2284 particle_elements[i*6+3] = i*4+0;
2285 particle_elements[i*6+4] = i*4+2;
2286 particle_elements[i*6+5] = i*4+3;
2289 Cvar_RegisterVariable(&r_drawparticles);
2290 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2291 Cvar_RegisterVariable(&r_drawdecals);
2292 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2293 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2296 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2298 int surfacelistindex;
2300 float *v3f, *t2f, *c4f;
2301 particletexture_t *tex;
2302 float right[3], up[3], size, ca;
2303 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2304 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2306 RSurf_ActiveWorldEntity();
2308 r_refdef.stats.drawndecals += numsurfaces;
2309 R_Mesh_ResetTextureState();
2310 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2311 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2312 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2313 GL_DepthMask(false);
2314 GL_DepthRange(0, 1);
2315 GL_PolygonOffset(0, 0);
2317 GL_CullFace(GL_NONE);
2319 // generate all the vertices at once
2320 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2322 d = cl.decals + surfacelist[surfacelistindex];
2325 c4f = particle_color4f + 16*surfacelistindex;
2326 ca = d->alpha * alphascale;
2327 // ensure alpha multiplier saturates properly
2328 if (ca > 1.0f / 256.0f)
2330 if (r_refdef.fogenabled)
2331 ca *= RSurf_FogVertex(d->org);
2332 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2333 Vector4Copy(c4f, c4f + 4);
2334 Vector4Copy(c4f, c4f + 8);
2335 Vector4Copy(c4f, c4f + 12);
2337 // calculate vertex positions
2338 size = d->size * cl_particles_size.value;
2339 VectorVectors(d->normal, right, up);
2340 VectorScale(right, size, right);
2341 VectorScale(up, size, up);
2342 v3f = particle_vertex3f + 12*surfacelistindex;
2343 v3f[ 0] = d->org[0] - right[0] - up[0];
2344 v3f[ 1] = d->org[1] - right[1] - up[1];
2345 v3f[ 2] = d->org[2] - right[2] - up[2];
2346 v3f[ 3] = d->org[0] - right[0] + up[0];
2347 v3f[ 4] = d->org[1] - right[1] + up[1];
2348 v3f[ 5] = d->org[2] - right[2] + up[2];
2349 v3f[ 6] = d->org[0] + right[0] + up[0];
2350 v3f[ 7] = d->org[1] + right[1] + up[1];
2351 v3f[ 8] = d->org[2] + right[2] + up[2];
2352 v3f[ 9] = d->org[0] + right[0] - up[0];
2353 v3f[10] = d->org[1] + right[1] - up[1];
2354 v3f[11] = d->org[2] + right[2] - up[2];
2356 // calculate texcoords
2357 tex = &particletexture[d->texnum];
2358 t2f = particle_texcoord2f + 8*surfacelistindex;
2359 t2f[0] = tex->s1;t2f[1] = tex->t2;
2360 t2f[2] = tex->s1;t2f[3] = tex->t1;
2361 t2f[4] = tex->s2;t2f[5] = tex->t1;
2362 t2f[6] = tex->s2;t2f[7] = tex->t2;
2365 // now render the decals all at once
2366 // (this assumes they all use one particle font texture!)
2367 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2368 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2369 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 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 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2471 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2472 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2473 GL_DepthMask(false);
2474 GL_DepthRange(0, 1);
2475 GL_PolygonOffset(0, 0);
2477 GL_AlphaTest(false);
2478 GL_CullFace(GL_NONE);
2480 spintime = r_refdef.scene.time;
2482 // first generate all the vertices at once
2483 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2485 p = cl.particles + surfacelist[surfacelistindex];
2487 blendmode = (pblend_t)p->blendmode;
2491 case PBLEND_INVALID:
2493 alpha = p->alpha * colormultiplier[3];
2494 // ensure alpha multiplier saturates properly
2497 // additive and modulate can just fade out in fog (this is correct)
2498 if (r_refdef.fogenabled)
2499 alpha *= RSurf_FogVertex(p->org);
2500 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2501 alpha *= 1.0f / 256.0f;
2502 c4f[0] = p->color[0] * alpha;
2503 c4f[1] = p->color[1] * alpha;
2504 c4f[2] = p->color[2] * alpha;
2508 alpha = p->alpha * colormultiplier[3];
2509 // ensure alpha multiplier saturates properly
2512 // additive and modulate can just fade out in fog (this is correct)
2513 if (r_refdef.fogenabled)
2514 alpha *= RSurf_FogVertex(p->org);
2515 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2516 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2517 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2518 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2522 c4f[0] = p->color[0] * colormultiplier[0];
2523 c4f[1] = p->color[1] * colormultiplier[1];
2524 c4f[2] = p->color[2] * colormultiplier[2];
2525 c4f[3] = p->alpha * colormultiplier[3];
2526 // note: lighting is not cheap!
2527 if (particletype[p->typeindex].lighting)
2529 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2530 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2531 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2532 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2534 // mix in the fog color
2535 if (r_refdef.fogenabled)
2537 fog = RSurf_FogVertex(p->org);
2539 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2540 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2541 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2545 // copy the color into the other three vertices
2546 Vector4Copy(c4f, c4f + 4);
2547 Vector4Copy(c4f, c4f + 8);
2548 Vector4Copy(c4f, c4f + 12);
2550 size = p->size * cl_particles_size.value;
2551 tex = &particletexture[p->texnum];
2552 switch(p->orientation)
2554 // case PARTICLE_INVALID:
2555 case PARTICLE_BILLBOARD:
2556 if (p->angle + p->spin)
2558 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2559 spinsin = sin(spinrad) * size;
2560 spincos = cos(spinrad) * size;
2561 spinm1 = -p->stretch * spincos;
2564 spinm4 = -p->stretch * spincos;
2565 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2566 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2570 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2571 VectorScale(r_refdef.view.up, size, up);
2574 v3f[ 0] = p->org[0] - right[0] - up[0];
2575 v3f[ 1] = p->org[1] - right[1] - up[1];
2576 v3f[ 2] = p->org[2] - right[2] - up[2];
2577 v3f[ 3] = p->org[0] - right[0] + up[0];
2578 v3f[ 4] = p->org[1] - right[1] + up[1];
2579 v3f[ 5] = p->org[2] - right[2] + up[2];
2580 v3f[ 6] = p->org[0] + right[0] + up[0];
2581 v3f[ 7] = p->org[1] + right[1] + up[1];
2582 v3f[ 8] = p->org[2] + right[2] + up[2];
2583 v3f[ 9] = p->org[0] + right[0] - up[0];
2584 v3f[10] = p->org[1] + right[1] - up[1];
2585 v3f[11] = p->org[2] + right[2] - up[2];
2586 t2f[0] = tex->s1;t2f[1] = tex->t2;
2587 t2f[2] = tex->s1;t2f[3] = tex->t1;
2588 t2f[4] = tex->s2;t2f[5] = tex->t1;
2589 t2f[6] = tex->s2;t2f[7] = tex->t2;
2591 case PARTICLE_ORIENTED_DOUBLESIDED:
2592 VectorVectors(p->vel, baseright, baseup);
2593 if (p->angle + p->spin)
2595 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2596 spinsin = sin(spinrad) * size;
2597 spincos = cos(spinrad) * size;
2598 spinm1 = p->stretch * spincos;
2601 spinm4 = p->stretch * spincos;
2602 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2603 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2607 VectorScale(baseright, size * p->stretch, right);
2608 VectorScale(baseup, size, up);
2610 v3f[ 0] = p->org[0] - right[0] - up[0];
2611 v3f[ 1] = p->org[1] - right[1] - up[1];
2612 v3f[ 2] = p->org[2] - right[2] - up[2];
2613 v3f[ 3] = p->org[0] - right[0] + up[0];
2614 v3f[ 4] = p->org[1] - right[1] + up[1];
2615 v3f[ 5] = p->org[2] - right[2] + up[2];
2616 v3f[ 6] = p->org[0] + right[0] + up[0];
2617 v3f[ 7] = p->org[1] + right[1] + up[1];
2618 v3f[ 8] = p->org[2] + right[2] + up[2];
2619 v3f[ 9] = p->org[0] + right[0] - up[0];
2620 v3f[10] = p->org[1] + right[1] - up[1];
2621 v3f[11] = p->org[2] + right[2] - up[2];
2622 t2f[0] = tex->s1;t2f[1] = tex->t2;
2623 t2f[2] = tex->s1;t2f[3] = tex->t1;
2624 t2f[4] = tex->s2;t2f[5] = tex->t1;
2625 t2f[6] = tex->s2;t2f[7] = tex->t2;
2627 case PARTICLE_SPARK:
2628 len = VectorLength(p->vel);
2629 VectorNormalize2(p->vel, up);
2630 lenfactor = p->stretch * 0.04 * len;
2631 if(lenfactor < size * 0.5)
2632 lenfactor = size * 0.5;
2633 VectorMA(p->org, -lenfactor, up, v);
2634 VectorMA(p->org, lenfactor, up, up2);
2635 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2636 t2f[0] = tex->s1;t2f[1] = tex->t2;
2637 t2f[2] = tex->s1;t2f[3] = tex->t1;
2638 t2f[4] = tex->s2;t2f[5] = tex->t1;
2639 t2f[6] = tex->s2;t2f[7] = tex->t2;
2641 case PARTICLE_VBEAM:
2642 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2643 VectorSubtract(p->vel, p->org, up);
2644 VectorNormalize(up);
2645 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2646 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2647 t2f[0] = tex->s2;t2f[1] = v[0];
2648 t2f[2] = tex->s1;t2f[3] = v[0];
2649 t2f[4] = tex->s1;t2f[5] = v[1];
2650 t2f[6] = tex->s2;t2f[7] = v[1];
2652 case PARTICLE_HBEAM:
2653 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2654 VectorSubtract(p->vel, p->org, up);
2655 VectorNormalize(up);
2656 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2657 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2658 t2f[0] = v[0];t2f[1] = tex->t1;
2659 t2f[2] = v[0];t2f[3] = tex->t2;
2660 t2f[4] = v[1];t2f[5] = tex->t2;
2661 t2f[6] = v[1];t2f[7] = tex->t1;
2666 // now render batches of particles based on blendmode and texture
2667 blendmode = PBLEND_INVALID;
2671 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2673 p = cl.particles + surfacelist[surfacelistindex];
2675 if (blendmode != p->blendmode)
2677 blendmode = (pblend_t)p->blendmode;
2681 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2683 case PBLEND_INVALID:
2685 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2688 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2692 if (texture != particletexture[p->texnum].texture)
2694 texture = particletexture[p->texnum].texture;
2695 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2698 // iterate until we find a change in settings
2699 batchstart = surfacelistindex++;
2700 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2702 p = cl.particles + surfacelist[surfacelistindex];
2703 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2707 batchcount = surfacelistindex - batchstart;
2708 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2712 void R_DrawParticles (void)
2715 int drawparticles = r_drawparticles.integer;
2716 float minparticledist;
2718 float gravity, frametime, f, dist, oldorg[3];
2724 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2725 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2727 // LordHavoc: early out conditions
2728 if (!cl.num_particles)
2731 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2732 gravity = frametime * cl.movevars_gravity;
2733 update = frametime > 0;
2734 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2735 drawdist2 = drawdist2*drawdist2;
2737 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2741 if (cl.free_particle > i)
2742 cl.free_particle = i;
2748 if (p->delayedspawn > cl.time)
2751 p->size += p->sizeincrease * frametime;
2752 p->alpha -= p->alphafade * frametime;
2754 if (p->alpha <= 0 || p->die <= cl.time)
2757 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2759 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2761 if (p->typeindex == pt_blood)
2762 p->size += frametime * 8;
2764 p->vel[2] -= p->gravity * gravity;
2765 f = 1.0f - min(p->liquidfriction * frametime, 1);
2766 VectorScale(p->vel, f, p->vel);
2770 p->vel[2] -= p->gravity * gravity;
2773 f = 1.0f - min(p->airfriction * frametime, 1);
2774 VectorScale(p->vel, f, p->vel);
2778 VectorCopy(p->org, oldorg);
2779 VectorMA(p->org, frametime, p->vel, p->org);
2780 // if (p->bounce && cl.time >= p->delayedcollisions)
2781 if (p->bounce && cl_particles_collisions.integer)
2783 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);
2784 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2785 // or if the trace hit something flagged as NOIMPACT
2786 // then remove the particle
2787 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2789 VectorCopy(trace.endpos, p->org);
2790 // react if the particle hit something
2791 if (trace.fraction < 1)
2793 VectorCopy(trace.endpos, p->org);
2795 if (p->staintexnum >= 0)
2797 // blood - splash on solid
2798 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2801 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2802 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2803 if (cl_decals.integer)
2805 // create a decal for the blood splat
2806 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2807 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2812 if (p->typeindex == pt_blood)
2814 // blood - splash on solid
2815 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2817 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2819 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)));
2820 if (cl_decals.integer)
2822 // create a decal for the blood splat
2823 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);
2828 else if (p->bounce < 0)
2830 // bounce -1 means remove on impact
2835 // anything else - bounce off solid
2836 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2837 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2838 if (DotProduct(p->vel, p->vel) < 0.03)
2839 VectorClear(p->vel);
2845 if (p->typeindex != pt_static)
2847 switch (p->typeindex)
2849 case pt_entityparticle:
2850 // particle that removes itself after one rendered frame
2857 a = CL_PointSuperContents(p->org);
2858 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2862 a = CL_PointSuperContents(p->org);
2863 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2867 a = CL_PointSuperContents(p->org);
2868 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2872 if (cl.time > p->time2)
2875 p->time2 = cl.time + (rand() & 3) * 0.1;
2876 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2877 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2879 a = CL_PointSuperContents(p->org);
2880 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2888 else if (p->delayedspawn > cl.time)
2892 // don't render particles too close to the view (they chew fillrate)
2893 // also don't render particles behind the view (useless)
2894 // further checks to cull to the frustum would be too slow here
2895 switch(p->typeindex)
2898 // beams have no culling
2899 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2902 if(cl_particles_visculling.integer)
2903 if (!r_refdef.viewcache.world_novis)
2904 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2906 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2908 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2911 // anything else just has to be in front of the viewer and visible at this distance
2912 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2913 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2920 if (cl.free_particle > i)
2921 cl.free_particle = i;
2924 // reduce cl.num_particles if possible
2925 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2928 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2930 particle_t *oldparticles = cl.particles;
2931 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2932 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2933 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2934 Mem_Free(oldparticles);