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;
2305 RSurf_ActiveWorldEntity();
2307 r_refdef.stats.drawndecals += numsurfaces;
2308 R_Mesh_ResetTextureState();
2309 GL_DepthMask(false);
2310 GL_DepthRange(0, 1);
2311 GL_PolygonOffset(0, 0);
2313 GL_CullFace(GL_NONE);
2315 // generate all the vertices at once
2316 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2318 d = cl.decals + surfacelist[surfacelistindex];
2321 c4f = particle_color4f + 16*surfacelistindex;
2322 ca = d->alpha * alphascale;
2323 // ensure alpha multiplier saturates properly
2324 if (ca > 1.0f / 256.0f)
2326 if (r_refdef.fogenabled)
2327 ca *= RSurf_FogVertex(d->org);
2328 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2329 Vector4Copy(c4f, c4f + 4);
2330 Vector4Copy(c4f, c4f + 8);
2331 Vector4Copy(c4f, c4f + 12);
2333 // calculate vertex positions
2334 size = d->size * cl_particles_size.value;
2335 VectorVectors(d->normal, right, up);
2336 VectorScale(right, size, right);
2337 VectorScale(up, size, up);
2338 v3f = particle_vertex3f + 12*surfacelistindex;
2339 v3f[ 0] = d->org[0] - right[0] - up[0];
2340 v3f[ 1] = d->org[1] - right[1] - up[1];
2341 v3f[ 2] = d->org[2] - right[2] - up[2];
2342 v3f[ 3] = d->org[0] - right[0] + up[0];
2343 v3f[ 4] = d->org[1] - right[1] + up[1];
2344 v3f[ 5] = d->org[2] - right[2] + up[2];
2345 v3f[ 6] = d->org[0] + right[0] + up[0];
2346 v3f[ 7] = d->org[1] + right[1] + up[1];
2347 v3f[ 8] = d->org[2] + right[2] + up[2];
2348 v3f[ 9] = d->org[0] + right[0] - up[0];
2349 v3f[10] = d->org[1] + right[1] - up[1];
2350 v3f[11] = d->org[2] + right[2] - up[2];
2352 // calculate texcoords
2353 tex = &particletexture[d->texnum];
2354 t2f = particle_texcoord2f + 8*surfacelistindex;
2355 t2f[0] = tex->s1;t2f[1] = tex->t2;
2356 t2f[2] = tex->s1;t2f[3] = tex->t1;
2357 t2f[4] = tex->s2;t2f[5] = tex->t1;
2358 t2f[6] = tex->s2;t2f[7] = tex->t2;
2361 // now render the decals all at once
2362 // (this assumes they all use one particle font texture!)
2363 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2364 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2365 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2366 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2369 void R_DrawDecals (void)
2372 int drawdecals = r_drawdecals.integer;
2377 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2379 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2380 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2382 // LordHavoc: early out conditions
2386 decalfade = frametime * 256 / cl_decals_fadetime.value;
2387 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2388 drawdist2 = drawdist2*drawdist2;
2390 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2392 if (!decal->typeindex)
2395 if (killsequence - decal->decalsequence > 0)
2398 if (cl.time > decal->time2 + cl_decals_time.value)
2400 decal->alpha -= decalfade;
2401 if (decal->alpha <= 0)
2407 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2409 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2410 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2416 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2422 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))
2423 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2426 decal->typeindex = 0;
2427 if (cl.free_decal > i)
2431 // reduce cl.num_decals if possible
2432 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2435 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2437 decal_t *olddecals = cl.decals;
2438 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2439 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2440 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2441 Mem_Free(olddecals);
2444 r_refdef.stats.totaldecals = cl.num_decals;
2447 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2449 int surfacelistindex;
2450 int batchstart, batchcount;
2451 const particle_t *p;
2453 rtexture_t *texture;
2454 float *v3f, *t2f, *c4f;
2455 particletexture_t *tex;
2456 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2457 float ambient[3], diffuse[3], diffusenormal[3];
2458 float spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2459 vec4_t colormultiplier;
2461 RSurf_ActiveWorldEntity();
2463 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));
2465 r_refdef.stats.particles += numsurfaces;
2466 R_Mesh_ResetTextureState();
2467 GL_DepthMask(false);
2468 GL_DepthRange(0, 1);
2469 GL_PolygonOffset(0, 0);
2471 GL_AlphaTest(false);
2472 GL_CullFace(GL_NONE);
2474 spintime = r_refdef.scene.time;
2476 // first generate all the vertices at once
2477 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2479 p = cl.particles + surfacelist[surfacelistindex];
2481 blendmode = (pblend_t)p->blendmode;
2485 case PBLEND_INVALID:
2487 alpha = p->alpha * colormultiplier[3];
2488 // ensure alpha multiplier saturates properly
2491 // additive and modulate can just fade out in fog (this is correct)
2492 if (r_refdef.fogenabled)
2493 alpha *= RSurf_FogVertex(p->org);
2494 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2495 alpha *= 1.0f / 256.0f;
2496 c4f[0] = p->color[0] * alpha;
2497 c4f[1] = p->color[1] * alpha;
2498 c4f[2] = p->color[2] * alpha;
2502 alpha = p->alpha * colormultiplier[3];
2503 // ensure alpha multiplier saturates properly
2506 // additive and modulate can just fade out in fog (this is correct)
2507 if (r_refdef.fogenabled)
2508 alpha *= RSurf_FogVertex(p->org);
2509 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2510 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2511 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2512 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2516 c4f[0] = p->color[0] * colormultiplier[0];
2517 c4f[1] = p->color[1] * colormultiplier[1];
2518 c4f[2] = p->color[2] * colormultiplier[2];
2519 c4f[3] = p->alpha * colormultiplier[3];
2520 // note: lighting is not cheap!
2521 if (particletype[p->typeindex].lighting)
2523 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2524 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2525 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2526 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2528 // mix in the fog color
2529 if (r_refdef.fogenabled)
2531 fog = RSurf_FogVertex(p->org);
2533 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2534 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2535 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2539 // copy the color into the other three vertices
2540 Vector4Copy(c4f, c4f + 4);
2541 Vector4Copy(c4f, c4f + 8);
2542 Vector4Copy(c4f, c4f + 12);
2544 size = p->size * cl_particles_size.value;
2545 tex = &particletexture[p->texnum];
2546 switch(p->orientation)
2548 // case PARTICLE_INVALID:
2549 case PARTICLE_BILLBOARD:
2550 if (p->angle + p->spin)
2552 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2553 spinsin = sin(spinrad) * size;
2554 spincos = cos(spinrad) * size;
2555 spinm1 = -p->stretch * spincos;
2558 spinm4 = -p->stretch * spincos;
2559 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2560 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2564 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2565 VectorScale(r_refdef.view.up, size, up);
2568 v3f[ 0] = p->org[0] - right[0] - up[0];
2569 v3f[ 1] = p->org[1] - right[1] - up[1];
2570 v3f[ 2] = p->org[2] - right[2] - up[2];
2571 v3f[ 3] = p->org[0] - right[0] + up[0];
2572 v3f[ 4] = p->org[1] - right[1] + up[1];
2573 v3f[ 5] = p->org[2] - right[2] + up[2];
2574 v3f[ 6] = p->org[0] + right[0] + up[0];
2575 v3f[ 7] = p->org[1] + right[1] + up[1];
2576 v3f[ 8] = p->org[2] + right[2] + up[2];
2577 v3f[ 9] = p->org[0] + right[0] - up[0];
2578 v3f[10] = p->org[1] + right[1] - up[1];
2579 v3f[11] = p->org[2] + right[2] - up[2];
2580 t2f[0] = tex->s1;t2f[1] = tex->t2;
2581 t2f[2] = tex->s1;t2f[3] = tex->t1;
2582 t2f[4] = tex->s2;t2f[5] = tex->t1;
2583 t2f[6] = tex->s2;t2f[7] = tex->t2;
2585 case PARTICLE_ORIENTED_DOUBLESIDED:
2586 VectorVectors(p->vel, baseright, baseup);
2587 if (p->angle + p->spin)
2589 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2590 spinsin = sin(spinrad) * size;
2591 spincos = cos(spinrad) * size;
2592 spinm1 = p->stretch * spincos;
2595 spinm4 = p->stretch * spincos;
2596 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2597 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2601 VectorScale(baseright, size * p->stretch, right);
2602 VectorScale(baseup, size, up);
2604 v3f[ 0] = p->org[0] - right[0] - up[0];
2605 v3f[ 1] = p->org[1] - right[1] - up[1];
2606 v3f[ 2] = p->org[2] - right[2] - up[2];
2607 v3f[ 3] = p->org[0] - right[0] + up[0];
2608 v3f[ 4] = p->org[1] - right[1] + up[1];
2609 v3f[ 5] = p->org[2] - right[2] + up[2];
2610 v3f[ 6] = p->org[0] + right[0] + up[0];
2611 v3f[ 7] = p->org[1] + right[1] + up[1];
2612 v3f[ 8] = p->org[2] + right[2] + up[2];
2613 v3f[ 9] = p->org[0] + right[0] - up[0];
2614 v3f[10] = p->org[1] + right[1] - up[1];
2615 v3f[11] = p->org[2] + right[2] - up[2];
2616 t2f[0] = tex->s1;t2f[1] = tex->t2;
2617 t2f[2] = tex->s1;t2f[3] = tex->t1;
2618 t2f[4] = tex->s2;t2f[5] = tex->t1;
2619 t2f[6] = tex->s2;t2f[7] = tex->t2;
2621 case PARTICLE_SPARK:
2622 len = VectorLength(p->vel);
2623 VectorNormalize2(p->vel, up);
2624 lenfactor = p->stretch * 0.04 * len;
2625 if(lenfactor < size * 0.5)
2626 lenfactor = size * 0.5;
2627 VectorMA(p->org, -lenfactor, up, v);
2628 VectorMA(p->org, lenfactor, up, up2);
2629 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2630 t2f[0] = tex->s1;t2f[1] = tex->t2;
2631 t2f[2] = tex->s1;t2f[3] = tex->t1;
2632 t2f[4] = tex->s2;t2f[5] = tex->t1;
2633 t2f[6] = tex->s2;t2f[7] = tex->t2;
2635 case PARTICLE_VBEAM:
2636 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2637 VectorSubtract(p->vel, p->org, up);
2638 VectorNormalize(up);
2639 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2640 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2641 t2f[0] = tex->s2;t2f[1] = v[0];
2642 t2f[2] = tex->s1;t2f[3] = v[0];
2643 t2f[4] = tex->s1;t2f[5] = v[1];
2644 t2f[6] = tex->s2;t2f[7] = v[1];
2646 case PARTICLE_HBEAM:
2647 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2648 VectorSubtract(p->vel, p->org, up);
2649 VectorNormalize(up);
2650 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2651 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2652 t2f[0] = v[0];t2f[1] = tex->t1;
2653 t2f[2] = v[0];t2f[3] = tex->t2;
2654 t2f[4] = v[1];t2f[5] = tex->t2;
2655 t2f[6] = v[1];t2f[7] = tex->t1;
2660 // now render batches of particles based on blendmode and texture
2661 blendmode = PBLEND_INVALID;
2665 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2666 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2668 p = cl.particles + surfacelist[surfacelistindex];
2670 if (blendmode != p->blendmode)
2672 blendmode = (pblend_t)p->blendmode;
2676 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2678 case PBLEND_INVALID:
2680 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2683 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2687 if (texture != particletexture[p->texnum].texture)
2689 texture = particletexture[p->texnum].texture;
2690 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2693 // iterate until we find a change in settings
2694 batchstart = surfacelistindex++;
2695 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2697 p = cl.particles + surfacelist[surfacelistindex];
2698 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2702 batchcount = surfacelistindex - batchstart;
2703 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2707 void R_DrawParticles (void)
2710 int drawparticles = r_drawparticles.integer;
2711 float minparticledist;
2713 float gravity, frametime, f, dist, oldorg[3];
2719 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2720 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2722 // LordHavoc: early out conditions
2723 if (!cl.num_particles)
2726 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2727 gravity = frametime * cl.movevars_gravity;
2728 update = frametime > 0;
2729 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2730 drawdist2 = drawdist2*drawdist2;
2732 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2736 if (cl.free_particle > i)
2737 cl.free_particle = i;
2743 if (p->delayedspawn > cl.time)
2746 p->size += p->sizeincrease * frametime;
2747 p->alpha -= p->alphafade * frametime;
2749 if (p->alpha <= 0 || p->die <= cl.time)
2752 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2754 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2756 if (p->typeindex == pt_blood)
2757 p->size += frametime * 8;
2759 p->vel[2] -= p->gravity * gravity;
2760 f = 1.0f - min(p->liquidfriction * frametime, 1);
2761 VectorScale(p->vel, f, p->vel);
2765 p->vel[2] -= p->gravity * gravity;
2768 f = 1.0f - min(p->airfriction * frametime, 1);
2769 VectorScale(p->vel, f, p->vel);
2773 VectorCopy(p->org, oldorg);
2774 VectorMA(p->org, frametime, p->vel, p->org);
2775 // if (p->bounce && cl.time >= p->delayedcollisions)
2776 if (p->bounce && cl_particles_collisions.integer)
2778 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);
2779 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2780 // or if the trace hit something flagged as NOIMPACT
2781 // then remove the particle
2782 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2784 VectorCopy(trace.endpos, p->org);
2785 // react if the particle hit something
2786 if (trace.fraction < 1)
2788 VectorCopy(trace.endpos, p->org);
2790 if (p->staintexnum >= 0)
2792 // blood - splash on solid
2793 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2796 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2797 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2798 if (cl_decals.integer)
2800 // create a decal for the blood splat
2801 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2802 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2807 if (p->typeindex == pt_blood)
2809 // blood - splash on solid
2810 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2812 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2814 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)));
2815 if (cl_decals.integer)
2817 // create a decal for the blood splat
2818 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);
2823 else if (p->bounce < 0)
2825 // bounce -1 means remove on impact
2830 // anything else - bounce off solid
2831 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2832 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2833 if (DotProduct(p->vel, p->vel) < 0.03)
2834 VectorClear(p->vel);
2840 if (p->typeindex != pt_static)
2842 switch (p->typeindex)
2844 case pt_entityparticle:
2845 // particle that removes itself after one rendered frame
2852 a = CL_PointSuperContents(p->org);
2853 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2857 a = CL_PointSuperContents(p->org);
2858 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2862 a = CL_PointSuperContents(p->org);
2863 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2867 if (cl.time > p->time2)
2870 p->time2 = cl.time + (rand() & 3) * 0.1;
2871 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2872 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2874 a = CL_PointSuperContents(p->org);
2875 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2883 else if (p->delayedspawn > cl.time)
2887 // don't render particles too close to the view (they chew fillrate)
2888 // also don't render particles behind the view (useless)
2889 // further checks to cull to the frustum would be too slow here
2890 switch(p->typeindex)
2893 // beams have no culling
2894 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2897 if(cl_particles_visculling.integer)
2898 if (!r_refdef.viewcache.world_novis)
2899 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2901 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2903 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2906 // anything else just has to be in front of the viewer and visible at this distance
2907 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2908 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2915 if (cl.free_particle > i)
2916 cl.free_particle = i;
2919 // reduce cl.num_particles if possible
2920 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2923 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2925 particle_t *oldparticles = cl.particles;
2926 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2927 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2928 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2929 Mem_Free(oldparticles);