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", "1", "enables new advanced decal system"};
223 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
224 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
225 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
226 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
227 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
230 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
235 particleeffectinfo_t *info = NULL;
236 const char *text = textstart;
238 for (linenumber = 1;;linenumber++)
241 for (arrayindex = 0;arrayindex < 16;arrayindex++)
242 argv[arrayindex][0] = 0;
245 if (!COM_ParseToken_Simple(&text, true, false))
247 if (!strcmp(com_token, "\n"))
251 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
257 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
258 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
259 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
260 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
261 #define readfloat(var) checkparms(2);var = atof(argv[1])
262 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
263 if (!strcmp(argv[0], "effect"))
267 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
269 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
272 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
274 if (particleeffectname[effectnameindex][0])
276 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
281 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
285 // if we run out of names, abort
286 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
288 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
291 info = particleeffectinfo + numparticleeffectinfo++;
292 info->effectnameindex = effectnameindex;
293 info->particletype = pt_alphastatic;
294 info->blendmode = particletype[info->particletype].blendmode;
295 info->orientation = particletype[info->particletype].orientation;
296 info->tex[0] = tex_particle;
297 info->tex[1] = tex_particle;
298 info->color[0] = 0xFFFFFF;
299 info->color[1] = 0xFFFFFF;
303 info->alpha[1] = 256;
304 info->alpha[2] = 256;
305 info->time[0] = 9999;
306 info->time[1] = 9999;
307 VectorSet(info->lightcolor, 1, 1, 1);
308 info->lightshadow = true;
309 info->lighttime = 9999;
310 info->stretchfactor = 1;
311 info->staincolor[0] = (unsigned int)-1;
312 info->staincolor[1] = (unsigned int)-1;
313 info->staintex[0] = -1;
314 info->staintex[1] = -1;
315 info->stainalpha[0] = 1;
316 info->stainalpha[1] = 1;
317 info->stainsize[0] = 2;
318 info->stainsize[1] = 2;
320 info->rotate[1] = 360;
324 else if (info == NULL)
326 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
329 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
330 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
331 else if (!strcmp(argv[0], "type"))
334 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
335 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
336 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
337 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
338 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
339 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
340 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
341 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
342 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
343 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
344 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
345 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
346 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
347 info->blendmode = particletype[info->particletype].blendmode;
348 info->orientation = particletype[info->particletype].orientation;
350 else if (!strcmp(argv[0], "blend"))
353 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
354 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
355 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
356 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
358 else if (!strcmp(argv[0], "orientation"))
361 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
362 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
363 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
364 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
365 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
367 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
368 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
369 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
370 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
371 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
372 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
373 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
374 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
375 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
376 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
377 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
378 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
379 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
380 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
381 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
382 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
383 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
384 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
385 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
386 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
387 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
388 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
389 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
390 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
391 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
392 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
393 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
394 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
395 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
396 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
397 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
399 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
408 int CL_ParticleEffectIndexForName(const char *name)
411 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
412 if (!strcmp(particleeffectname[i], name))
417 const char *CL_ParticleEffectNameForIndex(int i)
419 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
421 return particleeffectname[i];
424 // MUST match effectnameindex_t in client.h
425 static const char *standardeffectnames[EFFECT_TOTAL] =
449 "TE_TEI_BIGEXPLOSION",
465 void CL_Particles_LoadEffectInfo(void)
469 unsigned char *filedata;
470 fs_offset_t filesize;
471 char filename[MAX_QPATH];
472 numparticleeffectinfo = 0;
473 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
474 memset(particleeffectname, 0, sizeof(particleeffectname));
475 for (i = 0;i < EFFECT_TOTAL;i++)
476 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
477 for (filepass = 0;;filepass++)
480 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
481 else if (filepass == 1)
483 if (!cl.worldbasename[0])
485 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
489 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
492 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
502 void CL_ReadPointFile_f (void);
503 void CL_Particles_Init (void)
505 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
506 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
508 Cvar_RegisterVariable (&cl_particles);
509 Cvar_RegisterVariable (&cl_particles_quality);
510 Cvar_RegisterVariable (&cl_particles_alpha);
511 Cvar_RegisterVariable (&cl_particles_size);
512 Cvar_RegisterVariable (&cl_particles_quake);
513 Cvar_RegisterVariable (&cl_particles_blood);
514 Cvar_RegisterVariable (&cl_particles_blood_alpha);
515 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
516 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
517 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
518 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
519 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
520 Cvar_RegisterVariable (&cl_particles_explosions_shell);
521 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
522 Cvar_RegisterVariable (&cl_particles_rain);
523 Cvar_RegisterVariable (&cl_particles_snow);
524 Cvar_RegisterVariable (&cl_particles_smoke);
525 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
526 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
527 Cvar_RegisterVariable (&cl_particles_sparks);
528 Cvar_RegisterVariable (&cl_particles_bubbles);
529 Cvar_RegisterVariable (&cl_particles_visculling);
530 Cvar_RegisterVariable (&cl_particles_collisions);
531 Cvar_RegisterVariable (&cl_decals);
532 Cvar_RegisterVariable (&cl_decals_visculling);
533 Cvar_RegisterVariable (&cl_decals_time);
534 Cvar_RegisterVariable (&cl_decals_fadetime);
535 Cvar_RegisterVariable (&cl_decals_newsystem);
536 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
537 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
538 Cvar_RegisterVariable (&cl_decals_models);
539 Cvar_RegisterVariable (&cl_decals_bias);
540 Cvar_RegisterVariable (&cl_decals_max);
543 void CL_Particles_Shutdown (void)
547 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
548 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
550 // list of all 26 parameters:
551 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
552 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
553 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
554 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
555 // palpha - opacity of particle as 0-255 (can be more than 255)
556 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
557 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
558 // pgravity - how much effect gravity has on the particle (0-1)
559 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
560 // px,py,pz - starting origin of particle
561 // pvx,pvy,pvz - starting velocity of particle
562 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
563 // blendmode - one of the PBLEND_ values
564 // orientation - one of the PARTICLE_ values
565 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
566 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
567 // stainalpha: opacity of the stain as factor for alpha
568 // stainsize: size of the stain as factor for palpha
569 // angle: base rotation of the particle geometry around its center normal
570 // spin: rotation speed of the particle geometry around its center normal
571 particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4])
576 if (!cl_particles.integer)
578 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
579 if (cl.free_particle >= cl.max_particles)
582 lifetime = palpha / min(1, palphafade);
583 part = &cl.particles[cl.free_particle++];
584 if (cl.num_particles < cl.free_particle)
585 cl.num_particles = cl.free_particle;
586 memset(part, 0, sizeof(*part));
587 VectorCopy(sortorigin, part->sortorigin);
588 part->typeindex = ptypeindex;
589 part->blendmode = blendmode;
590 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
592 particletexture_t *tex = &particletexture[ptex];
593 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
594 part->orientation = PARTICLE_VBEAM;
596 part->orientation = PARTICLE_HBEAM;
599 part->orientation = orientation;
600 l2 = (int)lhrandom(0.5, 256.5);
602 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
603 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
604 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
607 part->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[0]) * 256.0f);
608 part->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[1]) * 256.0f);
609 part->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[2]) * 256.0f);
611 part->alpha = palpha;
612 part->alphafade = palphafade;
613 part->staintexnum = staintex;
614 if(staincolor1 >= 0 && staincolor2 >= 0)
616 l2 = (int)lhrandom(0.5, 256.5);
618 if(blendmode == PBLEND_INVMOD)
620 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
621 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
622 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
626 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
627 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
628 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
630 if(r > 0xFF) r = 0xFF;
631 if(g > 0xFF) g = 0xFF;
632 if(b > 0xFF) b = 0xFF;
636 r = part->color[0]; // -1 is shorthand for stain = particle color
640 part->staincolor[0] = r;
641 part->staincolor[1] = g;
642 part->staincolor[2] = b;
643 part->stainalpha = palpha * stainalpha;
644 part->stainsize = psize * stainsize;
647 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
649 part->color[0] *= tint[0];
650 part->color[1] *= tint[1];
651 part->color[2] *= tint[2];
653 part->alpha *= tint[3];
654 part->alphafade *= tint[3];
655 part->stainalpha *= tint[3];
659 part->sizeincrease = psizeincrease;
660 part->gravity = pgravity;
661 part->bounce = pbounce;
662 part->stretch = stretch;
664 part->org[0] = px + originjitter * v[0];
665 part->org[1] = py + originjitter * v[1];
666 part->org[2] = pz + originjitter * v[2];
667 part->vel[0] = pvx + velocityjitter * v[0];
668 part->vel[1] = pvy + velocityjitter * v[1];
669 part->vel[2] = pvz + velocityjitter * v[2];
671 part->airfriction = pairfriction;
672 part->liquidfriction = pliquidfriction;
673 part->die = cl.time + lifetime;
674 part->delayedspawn = cl.time;
675 // part->delayedcollisions = 0;
676 part->qualityreduction = pqualityreduction;
679 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
680 if (part->typeindex == pt_rain)
684 float lifetime = part->die - cl.time;
687 // turn raindrop into simple spark and create delayedspawn splash effect
688 part->typeindex = pt_spark;
690 VectorMA(part->org, lifetime, part->vel, endvec);
691 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
692 part->die = cl.time + lifetime * trace.fraction;
693 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);
696 part2->delayedspawn = part->die;
697 part2->die += part->die - cl.time;
698 for (i = rand() & 7;i < 10;i++)
700 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);
703 part2->delayedspawn = part->die;
704 part2->die += part->die - cl.time;
710 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
712 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
715 VectorMA(part->org, lifetime, part->vel, endvec);
716 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
717 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
724 static void CL_ImmediateBloodStain(particle_t *part)
729 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
730 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
732 VectorCopy(part->vel, v);
734 staintex = part->staintexnum;
735 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);
738 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
739 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
741 VectorCopy(part->vel, v);
743 staintex = tex_blooddecal[rand()&7];
744 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);
748 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
752 entity_render_t *ent = &cl.entities[hitent].render;
753 unsigned char color[3];
754 if (!cl_decals.integer)
756 if (!ent->allowdecals)
759 l2 = (int)lhrandom(0.5, 256.5);
761 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
762 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
763 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
765 if (cl_decals_newsystem.integer)
768 R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
770 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);
774 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
775 if (cl.free_decal >= cl.max_decals)
777 decal = &cl.decals[cl.free_decal++];
778 if (cl.num_decals < cl.free_decal)
779 cl.num_decals = cl.free_decal;
780 memset(decal, 0, sizeof(*decal));
781 decal->decalsequence = cl.decalsequence++;
782 decal->typeindex = pt_decal;
783 decal->texnum = texnum;
784 VectorMA(org, cl_decals_bias.value, normal, decal->org);
785 VectorCopy(normal, decal->normal);
787 decal->alpha = alpha;
788 decal->time2 = cl.time;
789 decal->color[0] = color[0];
790 decal->color[1] = color[1];
791 decal->color[2] = color[2];
794 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
795 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
796 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
798 decal->owner = hitent;
799 decal->clusterindex = -1000; // no vis culling unless we're sure
802 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
803 decal->ownermodel = cl.entities[decal->owner].render.model;
804 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
805 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
809 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
811 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
813 decal->clusterindex = leaf->clusterindex;
818 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
821 float bestfrac, bestorg[3], bestnormal[3];
823 int besthitent = 0, hitent;
826 for (i = 0;i < 32;i++)
829 VectorMA(org, maxdist, org2, org2);
830 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
831 // take the closest trace result that doesn't end up hitting a NOMARKS
832 // surface (sky for example)
833 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
835 bestfrac = trace.fraction;
837 VectorCopy(trace.endpos, bestorg);
838 VectorCopy(trace.plane.normal, bestnormal);
842 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
845 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
846 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
847 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)
850 matrix4x4_t tempmatrix;
852 VectorLerp(originmins, 0.5, originmaxs, center);
853 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
854 if (effectnameindex == EFFECT_SVC_PARTICLE)
856 if (cl_particles.integer)
858 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
860 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
861 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
862 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
865 count *= cl_particles_quality.value;
866 for (;count > 0;count--)
868 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
869 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);
874 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
875 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
876 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
877 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
878 else if (effectnameindex == EFFECT_TE_SPIKE)
880 if (cl_particles_bulletimpacts.integer)
882 if (cl_particles_quake.integer)
884 if (cl_particles_smoke.integer)
885 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
889 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
890 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
891 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);
895 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
896 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
898 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
900 if (cl_particles_bulletimpacts.integer)
902 if (cl_particles_quake.integer)
904 if (cl_particles_smoke.integer)
905 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
909 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
910 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
911 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);
915 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
916 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
917 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);
919 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
921 if (cl_particles_bulletimpacts.integer)
923 if (cl_particles_quake.integer)
925 if (cl_particles_smoke.integer)
926 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
930 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
931 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
932 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);
936 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
937 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
939 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
941 if (cl_particles_bulletimpacts.integer)
943 if (cl_particles_quake.integer)
945 if (cl_particles_smoke.integer)
946 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
950 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
951 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
952 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);
956 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
957 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
958 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);
960 else if (effectnameindex == EFFECT_TE_BLOOD)
962 if (!cl_particles_blood.integer)
964 if (cl_particles_quake.integer)
965 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
968 static double bloodaccumulator = 0;
969 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
970 //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);
971 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
972 for (;bloodaccumulator > 0;bloodaccumulator--)
974 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);
975 if (immediatebloodstain && part)
977 immediatebloodstain = false;
978 CL_ImmediateBloodStain(part);
983 else if (effectnameindex == EFFECT_TE_SPARK)
984 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
985 else if (effectnameindex == EFFECT_TE_PLASMABURN)
987 // plasma scorch mark
988 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
989 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
990 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
992 else if (effectnameindex == EFFECT_TE_GUNSHOT)
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);
1009 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1011 if (cl_particles_bulletimpacts.integer)
1013 if (cl_particles_quake.integer)
1014 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1017 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1018 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1019 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);
1023 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1024 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1025 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);
1027 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1029 CL_ParticleExplosion(center);
1030 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);
1032 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1034 CL_ParticleExplosion(center);
1035 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);
1037 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1039 if (cl_particles_quake.integer)
1042 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1045 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);
1047 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);
1051 CL_ParticleExplosion(center);
1052 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);
1054 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1055 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);
1056 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1058 count *= cl_particles_quality.value;
1060 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);
1062 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1064 float i, j, inc, vel;
1067 inc = 8 / cl_particles_quality.value;
1068 for (i = -128;i < 128;i += inc)
1070 for (j = -128;j < 128;j += inc)
1072 dir[0] = j + lhrandom(0, inc);
1073 dir[1] = i + lhrandom(0, inc);
1075 org[0] = center[0] + dir[0];
1076 org[1] = center[1] + dir[1];
1077 org[2] = center[2] + lhrandom(0, 64);
1078 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1079 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);
1083 else if (effectnameindex == EFFECT_TE_TELEPORT)
1085 float i, j, k, inc, vel;
1088 if (cl_particles_quake.integer)
1089 inc = 4 / cl_particles_quality.value;
1091 inc = 8 / cl_particles_quality.value;
1092 for (i = -16;i < 16;i += inc)
1094 for (j = -16;j < 16;j += inc)
1096 for (k = -24;k < 32;k += inc)
1098 VectorSet(dir, i*8, j*8, k*8);
1099 VectorNormalize(dir);
1100 vel = lhrandom(50, 113);
1101 if (cl_particles_quake.integer)
1102 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);
1104 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);
1108 if (!cl_particles_quake.integer)
1109 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);
1110 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);
1112 else if (effectnameindex == EFFECT_TE_TEI_G3)
1113 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);
1114 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1116 if (cl_particles_smoke.integer)
1118 count *= 0.25f * cl_particles_quality.value;
1120 CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1123 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1125 CL_ParticleExplosion(center);
1126 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);
1128 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1131 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1132 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1133 if (cl_particles_smoke.integer)
1134 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1135 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);
1136 if (cl_particles_sparks.integer)
1137 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1138 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);
1139 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);
1141 else if (effectnameindex == EFFECT_EF_FLAME)
1143 count *= 300 * cl_particles_quality.value;
1145 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);
1146 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);
1148 else if (effectnameindex == EFFECT_EF_STARDUST)
1150 count *= 200 * cl_particles_quality.value;
1152 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);
1153 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);
1155 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1159 int smoke, blood, bubbles, r, color;
1161 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1164 Vector4Set(light, 0, 0, 0, 0);
1166 if (effectnameindex == EFFECT_TR_ROCKET)
1167 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1168 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1170 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1171 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1173 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1175 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1176 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1180 matrix4x4_t tempmatrix;
1181 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1182 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);
1183 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1187 if (!spawnparticles)
1190 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1193 VectorSubtract(originmaxs, originmins, dir);
1194 len = VectorNormalizeLength(dir);
1197 dec = -ent->persistent.trail_time;
1198 ent->persistent.trail_time += len;
1199 if (ent->persistent.trail_time < 0.01f)
1202 // if we skip out, leave it reset
1203 ent->persistent.trail_time = 0.0f;
1208 // advance into this frame to reach the first puff location
1209 VectorMA(originmins, dec, dir, pos);
1212 smoke = cl_particles.integer && cl_particles_smoke.integer;
1213 blood = cl_particles.integer && cl_particles_blood.integer;
1214 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1215 qd = 1.0f / cl_particles_quality.value;
1222 if (effectnameindex == EFFECT_TR_BLOOD)
1224 if (cl_particles_quake.integer)
1226 color = particlepalette[67 + (rand()&3)];
1227 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);
1232 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 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1237 if (cl_particles_quake.integer)
1240 color = particlepalette[67 + (rand()&3)];
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, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1246 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);
1252 if (effectnameindex == EFFECT_TR_ROCKET)
1254 if (cl_particles_quake.integer)
1257 color = particlepalette[ramp3[r]];
1258 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);
1262 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);
1263 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);
1266 else if (effectnameindex == EFFECT_TR_GRENADE)
1268 if (cl_particles_quake.integer)
1271 color = particlepalette[ramp3[r]];
1272 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);
1276 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);
1279 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1281 if (cl_particles_quake.integer)
1284 color = particlepalette[52 + (rand()&7)];
1285 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);
1286 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);
1288 else if (gamemode == GAME_GOODVSBAD2)
1291 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);
1295 color = particlepalette[20 + (rand()&7)];
1296 CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1299 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1301 if (cl_particles_quake.integer)
1304 color = particlepalette[230 + (rand()&7)];
1305 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);
1306 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);
1310 color = particlepalette[226 + (rand()&7)];
1311 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);
1314 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1316 if (cl_particles_quake.integer)
1318 color = particlepalette[152 + (rand()&3)];
1319 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);
1321 else if (gamemode == GAME_GOODVSBAD2)
1324 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);
1326 else if (gamemode == GAME_PRYDON)
1329 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);
1332 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);
1334 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1337 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);
1339 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1342 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);
1344 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1345 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);
1349 if (effectnameindex == EFFECT_TR_ROCKET)
1350 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);
1351 else if (effectnameindex == EFFECT_TR_GRENADE)
1352 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);
1354 // advance to next time and position
1357 VectorMA (pos, dec, dir, pos);
1360 ent->persistent.trail_time = len;
1363 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1366 // this is also called on point effects with spawndlight = true and
1367 // spawnparticles = true
1368 // it is called CL_ParticleTrail because most code does not want to supply
1369 // these parameters, only trail handling does
1370 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])
1372 qboolean found = false;
1373 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1375 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1376 return; // no such effect
1378 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1380 int effectinfoindex;
1383 particleeffectinfo_t *info;
1390 qboolean underwater;
1391 qboolean immediatebloodstain;
1393 float avgtint[4], tint[4], tintlerp;
1394 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1395 VectorLerp(originmins, 0.5, originmaxs, center);
1396 supercontents = CL_PointSuperContents(center);
1397 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1398 VectorSubtract(originmaxs, originmins, traildir);
1399 traillen = VectorLength(traildir);
1400 VectorNormalize(traildir);
1403 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1407 Vector4Set(avgtint, 1, 1, 1, 1);
1409 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1411 if (info->effectnameindex == effectnameindex)
1414 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1416 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1419 // spawn a dlight if requested
1420 if (info->lightradiusstart > 0 && spawndlight)
1422 matrix4x4_t tempmatrix;
1423 if (info->trailspacing > 0)
1424 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1426 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1427 if (info->lighttime > 0 && info->lightradiusfade > 0)
1429 // light flash (explosion, etc)
1430 // called when effect starts
1431 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);
1433 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1436 // called by CL_LinkNetworkEntity
1437 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1438 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1439 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1440 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1441 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);
1442 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1446 if (!spawnparticles)
1451 if (info->tex[1] > info->tex[0])
1453 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1454 tex = min(tex, info->tex[1] - 1);
1456 if(info->staintex[0] < 0)
1457 staintex = info->staintex[0];
1460 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1461 staintex = min(staintex, info->staintex[1] - 1);
1463 if (info->particletype == pt_decal)
1464 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]);
1465 else if (info->orientation == PARTICLE_HBEAM)
1466 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);
1469 if (!cl_particles.integer)
1471 switch (info->particletype)
1473 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1474 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1475 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1476 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1477 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1478 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1481 VectorCopy(originmins, trailpos);
1482 if (info->trailspacing > 0)
1484 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1485 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1486 immediatebloodstain = false;
1490 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1492 immediatebloodstain =
1493 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1495 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1497 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1498 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1500 if (info->tex[1] > info->tex[0])
1502 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1503 tex = min(tex, info->tex[1] - 1);
1507 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1508 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1509 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1513 tintlerp = lhrandom(0, 1);
1514 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1517 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);
1518 if (immediatebloodstain && part)
1520 immediatebloodstain = false;
1521 CL_ImmediateBloodStain(part);
1524 VectorMA(trailpos, trailstep, traildir, trailpos);
1531 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1534 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)
1536 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1544 void CL_EntityParticles (const entity_t *ent)
1547 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1548 static vec3_t avelocities[NUMVERTEXNORMALS];
1549 if (!cl_particles.integer) return;
1550 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1552 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1554 if (!avelocities[0][0])
1555 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1556 avelocities[0][i] = lhrandom(0, 2.55);
1558 for (i = 0;i < NUMVERTEXNORMALS;i++)
1560 yaw = cl.time * avelocities[i][0];
1561 pitch = cl.time * avelocities[i][1];
1562 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1563 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1564 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1565 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);
1570 void CL_ReadPointFile_f (void)
1572 vec3_t org, leakorg;
1574 char *pointfile = NULL, *pointfilepos, *t, tchar;
1575 char name[MAX_QPATH];
1580 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1581 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1584 Con_Printf("Could not open %s\n", name);
1588 Con_Printf("Reading %s...\n", name);
1589 VectorClear(leakorg);
1592 pointfilepos = pointfile;
1593 while (*pointfilepos)
1595 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1600 while (*t && *t != '\n' && *t != '\r')
1604 #if _MSC_VER >= 1400
1605 #define sscanf sscanf_s
1607 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1613 VectorCopy(org, leakorg);
1616 if (cl.num_particles < cl.max_particles - 3)
1619 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);
1622 Mem_Free(pointfile);
1623 VectorCopy(leakorg, org);
1624 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1626 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);
1627 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);
1628 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);
1633 CL_ParseParticleEffect
1635 Parse an effect out of the server message
1638 void CL_ParseParticleEffect (void)
1641 int i, count, msgcount, color;
1643 MSG_ReadVector(org, cls.protocol);
1644 for (i=0 ; i<3 ; i++)
1645 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1646 msgcount = MSG_ReadByte ();
1647 color = MSG_ReadByte ();
1649 if (msgcount == 255)
1654 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1659 CL_ParticleExplosion
1663 void CL_ParticleExplosion (const vec3_t org)
1669 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1670 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1672 if (cl_particles_quake.integer)
1674 for (i = 0;i < 1024;i++)
1680 color = particlepalette[ramp1[r]];
1681 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);
1685 color = particlepalette[ramp2[r]];
1686 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);
1692 i = CL_PointSuperContents(org);
1693 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1695 if (cl_particles.integer && cl_particles_bubbles.integer)
1696 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1697 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);
1701 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1703 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1710 VectorMA(org, 128, v2, v);
1711 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
1713 while (k < 16 && trace.fraction < 0.1f);
1714 VectorSubtract(trace.endpos, org, v2);
1715 VectorScale(v2, 2.0f, v2);
1716 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);
1722 if (cl_particles_explosions_shell.integer)
1723 R_NewExplosion(org);
1728 CL_ParticleExplosion2
1732 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1735 if (!cl_particles.integer) return;
1737 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1739 k = particlepalette[colorStart + (i % colorLength)];
1740 if (cl_particles_quake.integer)
1741 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);
1743 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);
1747 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1750 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1751 if (cl_particles_sparks.integer)
1753 sparkcount *= cl_particles_quality.value;
1754 while(sparkcount-- > 0)
1755 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);
1759 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1762 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1763 if (cl_particles_smoke.integer)
1765 smokecount *= cl_particles_quality.value;
1766 while(smokecount-- > 0)
1767 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);
1771 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)
1775 if (!cl_particles.integer) return;
1776 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1778 count = (int)(count * cl_particles_quality.value);
1781 k = particlepalette[colorbase + (rand()&3)];
1782 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);
1786 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1789 float minz, maxz, lifetime = 30;
1791 if (!cl_particles.integer) return;
1792 if (dir[2] < 0) // falling
1794 minz = maxs[2] + dir[2] * 0.1;
1797 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1802 maxz = maxs[2] + dir[2] * 0.1;
1804 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1807 count = (int)(count * cl_particles_quality.value);
1812 if (!cl_particles_rain.integer) break;
1813 count *= 4; // ick, this should be in the mod or maps?
1817 k = particlepalette[colorbase + (rand()&3)];
1818 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1819 if (gamemode == GAME_GOODVSBAD2)
1820 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);
1822 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);
1826 if (!cl_particles_snow.integer) break;
1829 k = particlepalette[colorbase + (rand()&3)];
1830 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1831 if (gamemode == GAME_GOODVSBAD2)
1832 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);
1834 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);
1838 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1842 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1843 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1844 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1845 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1846 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1847 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1849 #define PARTICLETEXTURESIZE 64
1850 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1852 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1856 dz = 1 - (dx*dx+dy*dy);
1857 if (dz > 0) // it does hit the sphere
1861 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1862 VectorNormalize(normal);
1863 dot = DotProduct(normal, light);
1864 if (dot > 0.5) // interior reflection
1865 f += ((dot * 2) - 1);
1866 else if (dot < -0.5) // exterior reflection
1867 f += ((dot * -2) - 1);
1869 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1870 VectorNormalize(normal);
1871 dot = DotProduct(normal, light);
1872 if (dot > 0.5) // interior reflection
1873 f += ((dot * 2) - 1);
1874 else if (dot < -0.5) // exterior reflection
1875 f += ((dot * -2) - 1);
1877 f += 16; // just to give it a haze so you can see the outline
1878 f = bound(0, f, 255);
1879 return (unsigned char) f;
1885 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1886 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1888 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1889 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1890 *width = particlefontcellwidth;
1891 *height = particlefontcellheight;
1894 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1896 int basex, basey, w, h, y;
1897 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1898 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1899 Sys_Error("invalid particle texture size for autogenerating");
1900 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1901 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1904 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1907 float cx, cy, dx, dy, f, iradius;
1909 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1910 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1911 iradius = 1.0f / radius;
1912 alpha *= (1.0f / 255.0f);
1913 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1915 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1919 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1924 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1925 d[0] += (int)(f * (blue - d[0]));
1926 d[1] += (int)(f * (green - d[1]));
1927 d[2] += (int)(f * (red - d[2]));
1933 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1936 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1938 data[0] = bound(minb, data[0], maxb);
1939 data[1] = bound(ming, data[1], maxg);
1940 data[2] = bound(minr, data[2], maxr);
1944 void particletextureinvert(unsigned char *data)
1947 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1949 data[0] = 255 - data[0];
1950 data[1] = 255 - data[1];
1951 data[2] = 255 - data[2];
1955 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1956 static void R_InitBloodTextures (unsigned char *particletexturedata)
1959 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1960 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1963 for (i = 0;i < 8;i++)
1965 memset(data, 255, datasize);
1966 for (k = 0;k < 24;k++)
1967 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1968 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1969 particletextureinvert(data);
1970 setuptex(tex_bloodparticle[i], data, particletexturedata);
1974 for (i = 0;i < 8;i++)
1976 memset(data, 255, datasize);
1978 for (j = 1;j < 10;j++)
1979 for (k = min(j, m - 1);k < m;k++)
1980 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1981 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1982 particletextureinvert(data);
1983 setuptex(tex_blooddecal[i], data, particletexturedata);
1989 //uncomment this to make engine save out particle font to a tga file when run
1990 //#define DUMPPARTICLEFONT
1992 static void R_InitParticleTexture (void)
1994 int x, y, d, i, k, m;
1995 int basex, basey, w, h;
1996 float dx, dy, f, s1, t1, s2, t2;
1999 fs_offset_t filesize;
2000 char texturename[MAX_QPATH];
2003 // a note: decals need to modulate (multiply) the background color to
2004 // properly darken it (stain), and they need to be able to alpha fade,
2005 // this is a very difficult challenge because it means fading to white
2006 // (no change to background) rather than black (darkening everything
2007 // behind the whole decal polygon), and to accomplish this the texture is
2008 // inverted (dark red blood on white background becomes brilliant cyan
2009 // and white on black background) so we can alpha fade it to black, then
2010 // we invert it again during the blendfunc to make it work...
2012 #ifndef DUMPPARTICLEFONT
2013 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2016 particlefonttexture = decalskinframe->base;
2017 // TODO maybe allow custom grid size?
2018 particlefontwidth = image_width;
2019 particlefontheight = image_height;
2020 particlefontcellwidth = image_width / 8;
2021 particlefontcellheight = image_height / 8;
2022 particlefontcols = 8;
2023 particlefontrows = 8;
2028 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2029 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2030 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2031 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2032 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2034 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2035 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2036 particlefontcols = 8;
2037 particlefontrows = 8;
2039 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2042 for (i = 0;i < 8;i++)
2044 memset(data, 255, datasize);
2047 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2048 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2050 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2052 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2053 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2055 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2056 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2058 d = (int)(d * (1-(dx*dx+dy*dy)));
2059 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2060 d = bound(0, d, 255);
2061 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2068 setuptex(tex_smoke[i], data, particletexturedata);
2072 memset(data, 255, datasize);
2073 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2075 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2076 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2078 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2079 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2080 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2083 setuptex(tex_rainsplash, data, particletexturedata);
2086 memset(data, 255, datasize);
2087 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2089 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2090 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2092 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2093 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2094 d = bound(0, d, 255);
2095 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2098 setuptex(tex_particle, data, particletexturedata);
2101 memset(data, 255, datasize);
2102 light[0] = 1;light[1] = 1;light[2] = 1;
2103 VectorNormalize(light);
2104 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2106 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2107 // stretch upper half of bubble by +50% and shrink lower half by -50%
2108 // (this gives an elongated teardrop shape)
2110 dy = (dy - 0.5f) * 2.0f;
2112 dy = (dy - 0.5f) / 1.5f;
2113 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2115 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2116 // shrink bubble width to half
2118 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2121 setuptex(tex_raindrop, data, particletexturedata);
2124 memset(data, 255, datasize);
2125 light[0] = 1;light[1] = 1;light[2] = 1;
2126 VectorNormalize(light);
2127 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2129 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2130 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2132 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2133 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2136 setuptex(tex_bubble, data, particletexturedata);
2138 // Blood particles and blood decals
2139 R_InitBloodTextures (particletexturedata);
2142 for (i = 0;i < 8;i++)
2144 memset(data, 255, datasize);
2145 for (k = 0;k < 12;k++)
2146 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2147 for (k = 0;k < 3;k++)
2148 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2149 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2150 particletextureinvert(data);
2151 setuptex(tex_bulletdecal[i], data, particletexturedata);
2154 #ifdef DUMPPARTICLEFONT
2155 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2158 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2159 particlefonttexture = decalskinframe->base;
2161 Mem_Free(particletexturedata);
2166 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2168 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2169 particletexture[i].texture = particlefonttexture;
2170 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2171 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2172 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2173 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2176 #ifndef DUMPPARTICLEFONT
2177 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2178 if (!particletexture[tex_beam].texture)
2181 unsigned char noise3[64][64], data2[64][16][4];
2183 fractalnoise(&noise3[0][0], 64, 4);
2185 for (y = 0;y < 64;y++)
2187 dy = (y - 0.5f*64) / (64*0.5f-1);
2188 for (x = 0;x < 16;x++)
2190 dx = (x - 0.5f*16) / (16*0.5f-2);
2191 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2192 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2193 data2[y][x][3] = 255;
2197 #ifdef DUMPPARTICLEFONT
2198 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2200 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2202 particletexture[tex_beam].s1 = 0;
2203 particletexture[tex_beam].t1 = 0;
2204 particletexture[tex_beam].s2 = 1;
2205 particletexture[tex_beam].t2 = 1;
2207 // now load an texcoord/texture override file
2208 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2215 if(!COM_ParseToken_Simple(&bufptr, true, false))
2217 if(!strcmp(com_token, "\n"))
2218 continue; // empty line
2219 i = atoi(com_token);
2227 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2229 strlcpy(texturename, com_token, sizeof(texturename));
2230 s1 = atof(com_token);
2231 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2234 t1 = atof(com_token);
2235 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2237 s2 = atof(com_token);
2238 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2240 t2 = atof(com_token);
2241 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2242 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2243 strlcpy(texturename, com_token, sizeof(texturename));
2250 if (!texturename[0])
2252 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2255 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2257 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2260 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true);
2263 // R_SkinFrame_LoadExternal already complained
2266 particletexture[i].texture = sf->base;
2267 particletexture[i].s1 = s1;
2268 particletexture[i].t1 = t1;
2269 particletexture[i].s2 = s2;
2270 particletexture[i].t2 = t2;
2276 static void r_part_start(void)
2279 // generate particlepalette for convenience from the main one
2280 for (i = 0;i < 256;i++)
2281 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2282 particletexturepool = R_AllocTexturePool();
2283 R_InitParticleTexture ();
2284 CL_Particles_LoadEffectInfo();
2287 static void r_part_shutdown(void)
2289 R_FreeTexturePool(&particletexturepool);
2292 static void r_part_newmap(void)
2295 R_SkinFrame_MarkUsed(decalskinframe);
2296 CL_Particles_LoadEffectInfo();
2299 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2300 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2302 void R_Particles_Init (void)
2305 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2307 particle_elements[i*6+0] = i*4+0;
2308 particle_elements[i*6+1] = i*4+1;
2309 particle_elements[i*6+2] = i*4+2;
2310 particle_elements[i*6+3] = i*4+0;
2311 particle_elements[i*6+4] = i*4+2;
2312 particle_elements[i*6+5] = i*4+3;
2315 Cvar_RegisterVariable(&r_drawparticles);
2316 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2317 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2318 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2319 Cvar_RegisterVariable(&r_drawdecals);
2320 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2321 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2324 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2326 int surfacelistindex;
2328 float *v3f, *t2f, *c4f;
2329 particletexture_t *tex;
2330 float right[3], up[3], size, ca;
2331 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2333 RSurf_ActiveWorldEntity();
2335 r_refdef.stats.drawndecals += numsurfaces;
2336 // R_Mesh_ResetTextureState();
2337 GL_DepthMask(false);
2338 GL_DepthRange(0, 1);
2339 GL_PolygonOffset(0, 0);
2341 GL_CullFace(GL_NONE);
2343 // generate all the vertices at once
2344 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2346 d = cl.decals + surfacelist[surfacelistindex];
2349 c4f = particle_color4f + 16*surfacelistindex;
2350 ca = d->alpha * alphascale;
2351 // ensure alpha multiplier saturates properly
2352 if (ca > 1.0f / 256.0f)
2354 if (r_refdef.fogenabled)
2355 ca *= RSurf_FogVertex(d->org);
2356 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2357 Vector4Copy(c4f, c4f + 4);
2358 Vector4Copy(c4f, c4f + 8);
2359 Vector4Copy(c4f, c4f + 12);
2361 // calculate vertex positions
2362 size = d->size * cl_particles_size.value;
2363 VectorVectors(d->normal, right, up);
2364 VectorScale(right, size, right);
2365 VectorScale(up, size, up);
2366 v3f = particle_vertex3f + 12*surfacelistindex;
2367 v3f[ 0] = d->org[0] - right[0] - up[0];
2368 v3f[ 1] = d->org[1] - right[1] - up[1];
2369 v3f[ 2] = d->org[2] - right[2] - up[2];
2370 v3f[ 3] = d->org[0] - right[0] + up[0];
2371 v3f[ 4] = d->org[1] - right[1] + up[1];
2372 v3f[ 5] = d->org[2] - right[2] + up[2];
2373 v3f[ 6] = d->org[0] + right[0] + up[0];
2374 v3f[ 7] = d->org[1] + right[1] + up[1];
2375 v3f[ 8] = d->org[2] + right[2] + up[2];
2376 v3f[ 9] = d->org[0] + right[0] - up[0];
2377 v3f[10] = d->org[1] + right[1] - up[1];
2378 v3f[11] = d->org[2] + right[2] - up[2];
2380 // calculate texcoords
2381 tex = &particletexture[d->texnum];
2382 t2f = particle_texcoord2f + 8*surfacelistindex;
2383 t2f[0] = tex->s1;t2f[1] = tex->t2;
2384 t2f[2] = tex->s1;t2f[3] = tex->t1;
2385 t2f[4] = tex->s2;t2f[5] = tex->t1;
2386 t2f[6] = tex->s2;t2f[7] = tex->t2;
2389 // now render the decals all at once
2390 // (this assumes they all use one particle font texture!)
2391 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2392 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false);
2393 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2394 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2397 void R_DrawDecals (void)
2400 int drawdecals = r_drawdecals.integer;
2405 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2407 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2408 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2410 // LordHavoc: early out conditions
2414 decalfade = frametime * 256 / cl_decals_fadetime.value;
2415 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2416 drawdist2 = drawdist2*drawdist2;
2418 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2420 if (!decal->typeindex)
2423 if (killsequence - decal->decalsequence > 0)
2426 if (cl.time > decal->time2 + cl_decals_time.value)
2428 decal->alpha -= decalfade;
2429 if (decal->alpha <= 0)
2435 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2437 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2438 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2444 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2450 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))
2451 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2454 decal->typeindex = 0;
2455 if (cl.free_decal > i)
2459 // reduce cl.num_decals if possible
2460 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2463 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2465 decal_t *olddecals = cl.decals;
2466 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2467 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2468 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2469 Mem_Free(olddecals);
2472 r_refdef.stats.totaldecals = cl.num_decals;
2475 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2477 int surfacelistindex;
2478 int batchstart, batchcount;
2479 const particle_t *p;
2481 rtexture_t *texture;
2482 float *v3f, *t2f, *c4f;
2483 particletexture_t *tex;
2484 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2485 // float ambient[3], diffuse[3], diffusenormal[3];
2486 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2487 vec4_t colormultiplier;
2488 float minparticledist_start, minparticledist_end;
2491 RSurf_ActiveWorldEntity();
2493 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));
2495 r_refdef.stats.particles += numsurfaces;
2496 // R_Mesh_ResetTextureState();
2497 GL_DepthMask(false);
2498 GL_DepthRange(0, 1);
2499 GL_PolygonOffset(0, 0);
2501 GL_CullFace(GL_NONE);
2503 spintime = r_refdef.scene.time;
2505 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2506 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2507 dofade = (minparticledist_start < minparticledist_end);
2509 // first generate all the vertices at once
2510 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2512 p = cl.particles + surfacelist[surfacelistindex];
2514 blendmode = (pblend_t)p->blendmode;
2516 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2517 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2518 alpha = palpha * colormultiplier[3];
2519 // ensure alpha multiplier saturates properly
2525 case PBLEND_INVALID:
2527 // additive and modulate can just fade out in fog (this is correct)
2528 if (r_refdef.fogenabled)
2529 alpha *= RSurf_FogVertex(p->org);
2530 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2531 alpha *= 1.0f / 256.0f;
2532 c4f[0] = p->color[0] * alpha;
2533 c4f[1] = p->color[1] * alpha;
2534 c4f[2] = p->color[2] * alpha;
2538 // additive and modulate can just fade out in fog (this is correct)
2539 if (r_refdef.fogenabled)
2540 alpha *= RSurf_FogVertex(p->org);
2541 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2542 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2543 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2544 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2548 c4f[0] = p->color[0] * colormultiplier[0];
2549 c4f[1] = p->color[1] * colormultiplier[1];
2550 c4f[2] = p->color[2] * colormultiplier[2];
2552 // note: lighting is not cheap!
2553 if (particletype[p->typeindex].lighting)
2554 R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2555 // mix in the fog color
2556 if (r_refdef.fogenabled)
2558 fog = RSurf_FogVertex(p->org);
2560 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2561 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2562 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2564 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2565 VectorScale(c4f, alpha, c4f);
2568 // copy the color into the other three vertices
2569 Vector4Copy(c4f, c4f + 4);
2570 Vector4Copy(c4f, c4f + 8);
2571 Vector4Copy(c4f, c4f + 12);
2573 size = p->size * cl_particles_size.value;
2574 tex = &particletexture[p->texnum];
2575 switch(p->orientation)
2577 // case PARTICLE_INVALID:
2578 case PARTICLE_BILLBOARD:
2579 if (p->angle + p->spin)
2581 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2582 spinsin = sin(spinrad) * size;
2583 spincos = cos(spinrad) * size;
2584 spinm1 = -p->stretch * spincos;
2587 spinm4 = -p->stretch * spincos;
2588 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2589 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2593 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2594 VectorScale(r_refdef.view.up, size, up);
2597 v3f[ 0] = p->org[0] - right[0] - up[0];
2598 v3f[ 1] = p->org[1] - right[1] - up[1];
2599 v3f[ 2] = p->org[2] - right[2] - up[2];
2600 v3f[ 3] = p->org[0] - right[0] + up[0];
2601 v3f[ 4] = p->org[1] - right[1] + up[1];
2602 v3f[ 5] = p->org[2] - right[2] + up[2];
2603 v3f[ 6] = p->org[0] + right[0] + up[0];
2604 v3f[ 7] = p->org[1] + right[1] + up[1];
2605 v3f[ 8] = p->org[2] + right[2] + up[2];
2606 v3f[ 9] = p->org[0] + right[0] - up[0];
2607 v3f[10] = p->org[1] + right[1] - up[1];
2608 v3f[11] = p->org[2] + right[2] - up[2];
2609 t2f[0] = tex->s1;t2f[1] = tex->t2;
2610 t2f[2] = tex->s1;t2f[3] = tex->t1;
2611 t2f[4] = tex->s2;t2f[5] = tex->t1;
2612 t2f[6] = tex->s2;t2f[7] = tex->t2;
2614 case PARTICLE_ORIENTED_DOUBLESIDED:
2615 VectorVectors(p->vel, baseright, baseup);
2616 if (p->angle + p->spin)
2618 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2619 spinsin = sin(spinrad) * size;
2620 spincos = cos(spinrad) * size;
2621 spinm1 = p->stretch * spincos;
2624 spinm4 = p->stretch * spincos;
2625 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2626 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2630 VectorScale(baseright, size * p->stretch, right);
2631 VectorScale(baseup, size, up);
2633 v3f[ 0] = p->org[0] - right[0] - up[0];
2634 v3f[ 1] = p->org[1] - right[1] - up[1];
2635 v3f[ 2] = p->org[2] - right[2] - up[2];
2636 v3f[ 3] = p->org[0] - right[0] + up[0];
2637 v3f[ 4] = p->org[1] - right[1] + up[1];
2638 v3f[ 5] = p->org[2] - right[2] + up[2];
2639 v3f[ 6] = p->org[0] + right[0] + up[0];
2640 v3f[ 7] = p->org[1] + right[1] + up[1];
2641 v3f[ 8] = p->org[2] + right[2] + up[2];
2642 v3f[ 9] = p->org[0] + right[0] - up[0];
2643 v3f[10] = p->org[1] + right[1] - up[1];
2644 v3f[11] = p->org[2] + right[2] - up[2];
2645 t2f[0] = tex->s1;t2f[1] = tex->t2;
2646 t2f[2] = tex->s1;t2f[3] = tex->t1;
2647 t2f[4] = tex->s2;t2f[5] = tex->t1;
2648 t2f[6] = tex->s2;t2f[7] = tex->t2;
2650 case PARTICLE_SPARK:
2651 len = VectorLength(p->vel);
2652 VectorNormalize2(p->vel, up);
2653 lenfactor = p->stretch * 0.04 * len;
2654 if(lenfactor < size * 0.5)
2655 lenfactor = size * 0.5;
2656 VectorMA(p->org, -lenfactor, up, v);
2657 VectorMA(p->org, lenfactor, up, up2);
2658 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2659 t2f[0] = tex->s1;t2f[1] = tex->t2;
2660 t2f[2] = tex->s1;t2f[3] = tex->t1;
2661 t2f[4] = tex->s2;t2f[5] = tex->t1;
2662 t2f[6] = tex->s2;t2f[7] = tex->t2;
2664 case PARTICLE_VBEAM:
2665 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2666 VectorSubtract(p->vel, p->org, up);
2667 VectorNormalize(up);
2668 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2669 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2670 t2f[0] = tex->s2;t2f[1] = v[0];
2671 t2f[2] = tex->s1;t2f[3] = v[0];
2672 t2f[4] = tex->s1;t2f[5] = v[1];
2673 t2f[6] = tex->s2;t2f[7] = v[1];
2675 case PARTICLE_HBEAM:
2676 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2677 VectorSubtract(p->vel, p->org, up);
2678 VectorNormalize(up);
2679 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2680 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2681 t2f[0] = v[0];t2f[1] = tex->t1;
2682 t2f[2] = v[0];t2f[3] = tex->t2;
2683 t2f[4] = v[1];t2f[5] = tex->t2;
2684 t2f[6] = v[1];t2f[7] = tex->t1;
2689 // now render batches of particles based on blendmode and texture
2690 blendmode = PBLEND_INVALID;
2694 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2695 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2697 p = cl.particles + surfacelist[surfacelistindex];
2699 if (texture != particletexture[p->texnum].texture)
2701 texture = particletexture[p->texnum].texture;
2702 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false);
2705 if (p->blendmode == PBLEND_INVMOD)
2707 // inverse modulate blend - group these
2708 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2709 // iterate until we find a change in settings
2710 batchstart = surfacelistindex++;
2711 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2713 p = cl.particles + surfacelist[surfacelistindex];
2714 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2720 // additive or alpha blend - group these
2721 // (we can group these because we premultiplied the texture alpha)
2722 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2723 // iterate until we find a change in settings
2724 batchstart = surfacelistindex++;
2725 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2727 p = cl.particles + surfacelist[surfacelistindex];
2728 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2733 batchcount = surfacelistindex - batchstart;
2734 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2738 void R_DrawParticles (void)
2741 int drawparticles = r_drawparticles.integer;
2742 float minparticledist_start;
2744 float gravity, frametime, f, dist, oldorg[3];
2750 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2751 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2753 // LordHavoc: early out conditions
2754 if (!cl.num_particles)
2757 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2758 gravity = frametime * cl.movevars_gravity;
2759 update = frametime > 0;
2760 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2761 drawdist2 = drawdist2*drawdist2;
2763 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2767 if (cl.free_particle > i)
2768 cl.free_particle = i;
2774 if (p->delayedspawn > cl.time)
2777 p->size += p->sizeincrease * frametime;
2778 p->alpha -= p->alphafade * frametime;
2780 if (p->alpha <= 0 || p->die <= cl.time)
2783 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2785 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2787 if (p->typeindex == pt_blood)
2788 p->size += frametime * 8;
2790 p->vel[2] -= p->gravity * gravity;
2791 f = 1.0f - min(p->liquidfriction * frametime, 1);
2792 VectorScale(p->vel, f, p->vel);
2796 p->vel[2] -= p->gravity * gravity;
2799 f = 1.0f - min(p->airfriction * frametime, 1);
2800 VectorScale(p->vel, f, p->vel);
2804 VectorCopy(p->org, oldorg);
2805 VectorMA(p->org, frametime, p->vel, p->org);
2806 // if (p->bounce && cl.time >= p->delayedcollisions)
2807 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2809 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, false);
2810 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2811 // or if the trace hit something flagged as NOIMPACT
2812 // then remove the particle
2813 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2815 VectorCopy(trace.endpos, p->org);
2816 // react if the particle hit something
2817 if (trace.fraction < 1)
2819 VectorCopy(trace.endpos, p->org);
2821 if (p->staintexnum >= 0)
2823 // blood - splash on solid
2824 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2827 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2828 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2829 if (cl_decals.integer)
2831 // create a decal for the blood splat
2832 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2833 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2838 if (p->typeindex == pt_blood)
2840 // blood - splash on solid
2841 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2843 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2845 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)));
2846 if (cl_decals.integer)
2848 // create a decal for the blood splat
2849 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);
2854 else if (p->bounce < 0)
2856 // bounce -1 means remove on impact
2861 // anything else - bounce off solid
2862 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2863 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2868 if (VectorLength2(p->vel) < 0.03)
2870 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2872 VectorClear(p->vel);
2876 if (p->typeindex != pt_static)
2878 switch (p->typeindex)
2880 case pt_entityparticle:
2881 // particle that removes itself after one rendered frame
2888 a = CL_PointSuperContents(p->org);
2889 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2893 a = CL_PointSuperContents(p->org);
2894 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2898 a = CL_PointSuperContents(p->org);
2899 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2903 if (cl.time > p->time2)
2906 p->time2 = cl.time + (rand() & 3) * 0.1;
2907 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2908 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2910 a = CL_PointSuperContents(p->org);
2911 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2919 else if (p->delayedspawn > cl.time)
2923 // don't render particles too close to the view (they chew fillrate)
2924 // also don't render particles behind the view (useless)
2925 // further checks to cull to the frustum would be too slow here
2926 switch(p->typeindex)
2929 // beams have no culling
2930 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2933 if(cl_particles_visculling.integer)
2934 if (!r_refdef.viewcache.world_novis)
2935 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2937 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2939 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2942 // anything else just has to be in front of the viewer and visible at this distance
2943 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2944 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2951 if (cl.free_particle > i)
2952 cl.free_particle = i;
2955 // reduce cl.num_particles if possible
2956 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2959 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2961 particle_t *oldparticles = cl.particles;
2962 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2963 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2964 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2965 Mem_Free(oldparticles);