]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_particles.c
the beginnings of texture fog support (to cure the vertex artifacts and simplify...
[divverent/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
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.
8
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.
12
13 See the GNU General Public License for more details.
14
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.
18
19 */
20
21 #include "quakedef.h"
22
23 #ifdef WORKINGLQUAKE
24 #define lhrandom(MIN,MAX) ((rand() & 32767) * (((MAX)-(MIN)) * (1.0f / 32767.0f)) + (MIN))
25 #define NUMVERTEXNORMALS        162
26 siextern float r_avertexnormals[NUMVERTEXNORMALS][3];
27 #define m_bytenormals r_avertexnormals
28 #define CL_PointQ1Contents(v) (Mod_PointInLeaf(v,cl.worldmodel)->contents)
29 typedef unsigned char qbyte;
30 #define cl_stainmaps.integer 0
31 void R_Stain (vec3_t origin, float radius, int cr1, int cg1, int cb1, int ca1, int cr2, int cg2, int cb2, int ca2)
32 {
33 }
34 #define CL_EntityParticles R_EntityParticles
35 #define CL_ReadPointFile_f R_ReadPointFile_f
36 #define CL_ParseParticleEffect R_ParseParticleEffect
37 #define CL_ParticleExplosion R_ParticleExplosion
38 #define CL_ParticleExplosion2 R_ParticleExplosion2
39 #define CL_TeleportSplash R_TeleportSplash
40 #define CL_BlobExplosion R_BlobExplosion
41 #define CL_RunParticleEffect R_RunParticleEffect
42 #define CL_LavaSplash R_LavaSplash
43 void R_CalcBeam_Vertex3f (float *vert, vec3_t org1, vec3_t org2, float width)
44 {
45         vec3_t right1, right2, diff, normal;
46
47         VectorSubtract (org2, org1, normal);
48         VectorNormalize (normal);
49
50         // calculate 'right' vector for start
51         VectorSubtract (r_vieworigin, org1, diff);
52         VectorNormalize (diff);
53         CrossProduct (normal, diff, right1);
54
55         // calculate 'right' vector for end
56         VectorSubtract (r_vieworigin, org2, diff);
57         VectorNormalize (diff);
58         CrossProduct (normal, diff, right2);
59
60         vert[ 0] = org1[0] + width * right1[0];
61         vert[ 1] = org1[1] + width * right1[1];
62         vert[ 2] = org1[2] + width * right1[2];
63         vert[ 3] = org1[0] - width * right1[0];
64         vert[ 4] = org1[1] - width * right1[1];
65         vert[ 5] = org1[2] - width * right1[2];
66         vert[ 6] = org2[0] - width * right2[0];
67         vert[ 7] = org2[1] - width * right2[1];
68         vert[ 8] = org2[2] - width * right2[2];
69         vert[ 9] = org2[0] + width * right2[0];
70         vert[10] = org2[1] + width * right2[1];
71         vert[11] = org2[2] + width * right2[2];
72 }
73 void fractalnoise(qbyte *noise, int size, int startgrid)
74 {
75         int x, y, g, g2, amplitude, min, max, size1 = size - 1, sizepower, gridpower;
76         int *noisebuf;
77 #define n(x,y) noisebuf[((y)&size1)*size+((x)&size1)]
78
79         for (sizepower = 0;(1 << sizepower) < size;sizepower++);
80         if (size != (1 << sizepower))
81         {
82                 Con_Printf("fractalnoise: size must be power of 2\n");
83                 return;
84         }
85
86         for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++);
87         if (startgrid != (1 << gridpower))
88         {
89                 Con_Printf("fractalnoise: grid must be power of 2\n");
90                 return;
91         }
92
93         startgrid = bound(0, startgrid, size);
94
95         amplitude = 0xFFFF; // this gets halved before use
96         noisebuf = malloc(size*size*sizeof(int));
97         memset(noisebuf, 0, size*size*sizeof(int));
98
99         for (g2 = startgrid;g2;g2 >>= 1)
100         {
101                 // brownian motion (at every smaller level there is random behavior)
102                 amplitude >>= 1;
103                 for (y = 0;y < size;y += g2)
104                         for (x = 0;x < size;x += g2)
105                                 n(x,y) += (rand()&amplitude);
106
107                 g = g2 >> 1;
108                 if (g)
109                 {
110                         // subdivide, diamond-square algorithm (really this has little to do with squares)
111                         // diamond
112                         for (y = 0;y < size;y += g2)
113                                 for (x = 0;x < size;x += g2)
114                                         n(x+g,y+g) = (n(x,y) + n(x+g2,y) + n(x,y+g2) + n(x+g2,y+g2)) >> 2;
115                         // square
116                         for (y = 0;y < size;y += g2)
117                                 for (x = 0;x < size;x += g2)
118                                 {
119                                         n(x+g,y) = (n(x,y) + n(x+g2,y) + n(x+g,y-g) + n(x+g,y+g)) >> 2;
120                                         n(x,y+g) = (n(x,y) + n(x,y+g2) + n(x-g,y+g) + n(x+g,y+g)) >> 2;
121                                 }
122                 }
123         }
124         // find range of noise values
125         min = max = 0;
126         for (y = 0;y < size;y++)
127                 for (x = 0;x < size;x++)
128                 {
129                         if (n(x,y) < min) min = n(x,y);
130                         if (n(x,y) > max) max = n(x,y);
131                 }
132         max -= min;
133         max++;
134         // normalize noise and copy to output
135         for (y = 0;y < size;y++)
136                 for (x = 0;x < size;x++)
137                         *noise++ = (qbyte) (((n(x,y) - min) * 256) / max);
138         free(noisebuf);
139 #undef n
140 }
141 void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up)
142 {
143         float d;
144
145         right[0] = forward[2];
146         right[1] = -forward[0];
147         right[2] = forward[1];
148
149         d = DotProduct(forward, right);
150         right[0] -= d * forward[0];
151         right[1] -= d * forward[1];
152         right[2] -= d * forward[2];
153         VectorNormalize(right);
154         CrossProduct(right, forward, up);
155 }
156 #if QW
157 #include "pmove.h"
158 extern qboolean PM_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, pmtrace_t *trace);
159 #endif
160 trace_t CL_TraceBox (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int hitbmodels, int *hitent, int hitsupercontentsmask, qboolean hitplayers)
161 {
162 #if QW
163         pmtrace_t trace;
164 #else
165         trace_t trace;
166 #endif
167         memset (&trace, 0, sizeof(trace));
168         trace.fraction = 1;
169         VectorCopy (end, trace.endpos);
170 #if QW
171         PM_RecursiveHullCheck (cl.model_precache[1]->hulls, 0, 0, 1, start, end, &trace);
172 #else
173         RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace);
174 #endif
175         return trace;
176 }
177 #else
178 #include "cl_collision.h"
179 #include "image.h"
180 #endif
181
182 #define MAX_PARTICLES                   32768   // default max # of particles at one time
183 #define ABSOLUTE_MIN_PARTICLES  512             // no fewer than this no matter what's on the command line
184
185 typedef enum
186 {
187         PARTICLE_BILLBOARD = 0,
188         PARTICLE_SPARK = 1,
189         PARTICLE_ORIENTED_DOUBLESIDED = 2,
190         PARTICLE_BEAM = 3
191 }
192 porientation_t;
193
194 typedef enum
195 {
196         PBLEND_ALPHA = 0,
197         PBLEND_ADD = 1,
198         PBLEND_MOD = 2
199 }
200 pblend_t;
201
202 typedef struct particletype_s
203 {
204         pblend_t blendmode;
205         porientation_t orientation;
206         qboolean lighting;
207 }
208 particletype_t;
209
210 typedef enum
211 {
212         pt_alphastatic, pt_static, pt_spark, pt_beam, pt_rain, pt_raindecal, pt_snow, pt_bubble, pt_blood, pt_smoke, pt_decal, pt_entityparticle, pt_total
213 }
214 ptype_t;
215
216 // must match ptype_t values
217 particletype_t particletype[pt_total] =
218 {
219         {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
220         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
221         {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
222         {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
223         {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
224         {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
225         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
226         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
227         {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
228         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
229         {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
230         {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
231 };
232
233 typedef struct particle_s
234 {
235         particletype_t *type;
236         int                     texnum;
237         vec3_t          org;
238         vec3_t          vel; // velocity of particle, or orientation of decal, or end point of beam
239         float           size;
240         float           alpha; // 0-255
241         float           alphafade; // how much alpha reduces per second
242         float           time2; // used for snow fluttering and decal fade
243         float           bounce; // how much bounce-back from a surface the particle hits (0 = no physics, 1 = stop and slide, 2 = keep bouncing forever, 1.5 is typical)
244         float           gravity; // how much gravity affects this particle (1.0 = normal gravity, 0.0 = none)
245         float           friction; // how much air friction affects this object (objects with a low mass/size ratio tend to get more air friction)
246         qbyte           color[4];
247         unsigned short owner; // decal stuck to this entity
248         model_t         *ownermodel; // model the decal is stuck to (used to make sure the entity is still alive)
249         vec3_t          relativeorigin; // decal at this location in entity's coordinate space
250         vec3_t          relativedirection; // decal oriented this way relative to entity's coordinate space
251 }
252 particle_t;
253
254 static int particlepalette[256] =
255 {
256         0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b,
257         0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb,
258         0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b,
259         0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23,
260         0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767,
261         0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb,
262         0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07,
263         0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f,
264         0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000,
265         0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000,
266         0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307,
267         0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723,
268         0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b,
269         0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b,
270         0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733,
271         0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397,
272         0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b,
273         0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707,
274         0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b,
275         0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707,
276         0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353,
277         0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07,
278         0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f,
279         0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07,
280         0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307,
281         0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700,
282         0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f,
283         0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f,
284         0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b,
285         0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b,
286         0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000,
287         0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53
288 };
289
290 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
291
292 // texture numbers in particle font
293 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
294 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
295 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
296 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
297 static const int tex_rainsplash[16] = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
298 static const int tex_particle = 63;
299 static const int tex_bubble = 62;
300 static const int tex_raindrop = 61;
301 static const int tex_beam = 60;
302
303 static int                      cl_maxparticles;
304 static int                      cl_numparticles;
305 static int                      cl_freeparticle;
306 static particle_t       *particles;
307
308 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1"};
309 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1"};
310 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1"};
311 cvar_t cl_particles_bloodshowers = {CVAR_SAVE, "cl_particles_bloodshowers", "1"};
312 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1"};
313 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5"};
314 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1"};
315 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1"};
316 cvar_t cl_particles_explosions_bubbles = {CVAR_SAVE, "cl_particles_explosions_bubbles", "1"};
317 cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0"};
318 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1"};
319 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0"};
320 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1"};
321 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5"};
322 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55"};
323 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1"};
324 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1"};
325 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0"};
326 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0"};
327 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20"};
328
329 void CL_Particles_Clear(void)
330 {
331         cl_numparticles = 0;
332         cl_freeparticle = 0;
333         memset(particles, 0, sizeof(particle_t) * cl_maxparticles);
334 }
335
336 /*
337 ===============
338 CL_InitParticles
339 ===============
340 */
341 void CL_ReadPointFile_f (void);
342 void CL_Particles_Init (void)
343 {
344         int             i;
345
346 // COMMANDLINEOPTION: Client: -particles <number> changes maximum number of particles at once, default 32768
347         i = COM_CheckParm ("-particles");
348
349         if (i && i < com_argc - 1)
350         {
351                 cl_maxparticles = (int)(atoi(com_argv[i+1]));
352                 if (cl_maxparticles < ABSOLUTE_MIN_PARTICLES)
353                         cl_maxparticles = ABSOLUTE_MIN_PARTICLES;
354         }
355         else
356                 cl_maxparticles = MAX_PARTICLES;
357
358         Cmd_AddCommand ("pointfile", CL_ReadPointFile_f);
359
360         Cvar_RegisterVariable (&cl_particles);
361         Cvar_RegisterVariable (&cl_particles_quality);
362         Cvar_RegisterVariable (&cl_particles_size);
363         Cvar_RegisterVariable (&cl_particles_bloodshowers);
364         Cvar_RegisterVariable (&cl_particles_blood);
365         Cvar_RegisterVariable (&cl_particles_blood_alpha);
366         Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
367         Cvar_RegisterVariable (&cl_particles_explosions_bubbles);
368         Cvar_RegisterVariable (&cl_particles_explosions_smoke);
369         Cvar_RegisterVariable (&cl_particles_explosions_sparks);
370         Cvar_RegisterVariable (&cl_particles_explosions_shell);
371         Cvar_RegisterVariable (&cl_particles_bulletimpacts);
372         Cvar_RegisterVariable (&cl_particles_smoke);
373         Cvar_RegisterVariable (&cl_particles_smoke_alpha);
374         Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
375         Cvar_RegisterVariable (&cl_particles_sparks);
376         Cvar_RegisterVariable (&cl_particles_bubbles);
377         Cvar_RegisterVariable (&cl_decals);
378         Cvar_RegisterVariable (&cl_decals_time);
379         Cvar_RegisterVariable (&cl_decals_fadetime);
380
381 #ifdef WORKINGLQUAKE
382         particles = (particle_t *) Hunk_AllocName(cl_maxparticles * sizeof(particle_t), "particles");
383 #else
384         particles = (particle_t *) Mem_Alloc(cl_mempool, cl_maxparticles * sizeof(particle_t));
385 #endif
386         CL_Particles_Clear();
387 }
388
389 void CL_Particles_Shutdown (void)
390 {
391 #ifdef WORKINGLQUAKE
392         // No clue what to do here...
393 #endif
394 }
395
396 // list of all 26 parameters:
397 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
398 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
399 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
400 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
401 // palpha - opacity of particle as 0-255 (can be more than 255)
402 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
403 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
404 // pgravity - how much effect gravity has on the particle (0-1)
405 // 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
406 // px,py,pz - starting origin of particle
407 // pvx,pvy,pvz - starting velocity of particle
408 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
409 particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int ptex, float psize, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pfriction)
410 {
411         particle_t *part;
412         int ptempcolor, ptempcolor2, pcr1, pcg1, pcb1, pcr2, pcg2, pcb2;
413         ptempcolor = (pcolor1);
414         ptempcolor2 = (pcolor2);
415         pcr2 = ((ptempcolor2) >> 16) & 0xFF;
416         pcg2 = ((ptempcolor2) >> 8) & 0xFF;
417         pcb2 = (ptempcolor2) & 0xFF;
418         if (ptempcolor != ptempcolor2)
419         {
420                 pcr1 = ((ptempcolor) >> 16) & 0xFF;
421                 pcg1 = ((ptempcolor) >> 8) & 0xFF;
422                 pcb1 = (ptempcolor) & 0xFF;
423                 ptempcolor = rand() & 0xFF;
424                 pcr2 = (((pcr2 - pcr1) * ptempcolor) >> 8) + pcr1;
425                 pcg2 = (((pcg2 - pcg1) * ptempcolor) >> 8) + pcg1;
426                 pcb2 = (((pcb2 - pcb1) * ptempcolor) >> 8) + pcb1;
427         }
428         for (;cl_freeparticle < cl_maxparticles && particles[cl_freeparticle].type;cl_freeparticle++);
429         if (cl_freeparticle >= cl_maxparticles)
430                 return NULL;
431         part = &particles[cl_freeparticle++];
432         if (cl_numparticles < cl_freeparticle)
433                 cl_numparticles = cl_freeparticle;
434         memset(part, 0, sizeof(*part));
435         part->type = (ptype);
436         part->color[0] = pcr2;
437         part->color[1] = pcg2;
438         part->color[2] = pcb2;
439         part->color[3] = 0xFF;
440         part->texnum = ptex;
441         part->size = (psize);
442         part->alpha = (palpha);
443         part->alphafade = (palphafade);
444         part->gravity = (pgravity);
445         part->bounce = (pbounce);
446         part->org[0] = (px);
447         part->org[1] = (py);
448         part->org[2] = (pz);
449         part->vel[0] = (pvx);
450         part->vel[1] = (pvy);
451         part->vel[2] = (pvz);
452         part->time2 = 0;
453         part->friction = (pfriction);
454         return part;
455 }
456
457 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
458 {
459         particle_t *p;
460         if (!cl_decals.integer)
461                 return;
462         p = particle(particletype + pt_decal, color1, color2, texnum, size, alpha, 0, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], normal[0], normal[1], normal[2], 0);
463         if (p)
464         {
465                 p->time2 = cl.time;
466 #ifndef WORKINGLQUAKE
467                 p->owner = hitent;
468                 p->ownermodel = cl_entities[p->owner].render.model;
469                 Matrix4x4_Transform(&cl_entities[p->owner].render.inversematrix, org, p->relativeorigin);
470                 Matrix4x4_Transform3x3(&cl_entities[p->owner].render.inversematrix, normal, p->relativedirection);
471                 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
472 #endif
473         }
474 }
475
476 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
477 {
478         int i;
479         float bestfrac, bestorg[3], bestnormal[3];
480         float org2[3];
481         int besthitent = 0, hitent;
482         trace_t trace;
483         bestfrac = 10;
484         for (i = 0;i < 32;i++)
485         {
486                 VectorRandom(org2);
487                 VectorMA(org, maxdist, org2, org2);
488                 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID, false);
489                 if (bestfrac > trace.fraction)
490                 {
491                         bestfrac = trace.fraction;
492                         besthitent = hitent;
493                         VectorCopy(trace.endpos, bestorg);
494                         VectorCopy(trace.plane.normal, bestnormal);
495                 }
496         }
497         if (bestfrac < 1)
498                 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
499 }
500
501 /*
502 ===============
503 CL_EntityParticles
504 ===============
505 */
506 void CL_EntityParticles (entity_t *ent)
507 {
508         int                     i;
509         float           angle;
510         float           sp, sy, cp, cy;
511         vec3_t          forward;
512         float           dist;
513         float           beamlength;
514         static vec3_t avelocities[NUMVERTEXNORMALS];
515         if (!cl_particles.integer) return;
516
517         dist = 64;
518         beamlength = 16;
519
520         if (!avelocities[0][0])
521                 for (i=0 ; i<NUMVERTEXNORMALS*3 ; i++)
522                         avelocities[0][i] = (rand()&255) * 0.01;
523
524         for (i=0 ; i<NUMVERTEXNORMALS ; i++)
525         {
526                 angle = cl.time * avelocities[i][0];
527                 sy = sin(angle);
528                 cy = cos(angle);
529                 angle = cl.time * avelocities[i][1];
530                 sp = sin(angle);
531                 cp = cos(angle);
532
533                 forward[0] = cp*cy;
534                 forward[1] = cp*sy;
535                 forward[2] = -sp;
536
537 #ifdef WORKINGLQUAKE
538                 particle(particletype + pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 2, 255, 0, 0, 0, ent->origin[0] + m_bytenormals[i][0]*dist + forward[0]*beamlength, ent->origin[1] + m_bytenormals[i][1]*dist + forward[1]*beamlength, ent->origin[2] + m_bytenormals[i][2]*dist + forward[2]*beamlength, 0, 0, 0, 0);
539 #else
540                 particle(particletype + pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 2, 255, 0, 0, 0, ent->render.origin[0] + m_bytenormals[i][0]*dist + forward[0]*beamlength, ent->render.origin[1] + m_bytenormals[i][1]*dist + forward[1]*beamlength, ent->render.origin[2] + m_bytenormals[i][2]*dist + forward[2]*beamlength, 0, 0, 0, 0);
541 #endif
542         }
543 }
544
545
546 void CL_ReadPointFile_f (void)
547 {
548         vec3_t org, leakorg;
549         int r, c, s;
550         char *pointfile = NULL, *pointfilepos, *t, tchar;
551         char name[MAX_OSPATH];
552
553         if (!cl.worldmodel)
554                 return;
555
556         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
557         strlcat (name, ".pts", sizeof (name));
558 #if WORKINGLQUAKE
559         pointfile = COM_LoadTempFile (name);
560 #else
561         pointfile = (char *)FS_LoadFile(name, tempmempool, true);
562 #endif
563         if (!pointfile)
564         {
565                 Con_Printf("Could not open %s\n", name);
566                 return;
567         }
568
569         Con_Printf("Reading %s...\n", name);
570         VectorClear(leakorg);
571         c = 0;
572         s = 0;
573         pointfilepos = pointfile;
574         while (*pointfilepos)
575         {
576                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
577                         pointfilepos++;
578                 if (!*pointfilepos)
579                         break;
580                 t = pointfilepos;
581                 while (*t && *t != '\n' && *t != '\r')
582                         t++;
583                 tchar = *t;
584                 *t = 0;
585                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
586                 *t = tchar;
587                 pointfilepos = t;
588                 if (r != 3)
589                         break;
590                 if (c == 0)
591                         VectorCopy(org, leakorg);
592                 c++;
593
594                 if (cl_numparticles < cl_maxparticles - 3)
595                 {
596                         s++;
597                         particle(particletype + pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0);
598                 }
599         }
600 #ifndef WORKINGLQUAKE
601         Mem_Free(pointfile);
602 #endif
603         VectorCopy(leakorg, org);
604         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
605
606         particle(particletype + pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0);
607         particle(particletype + pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0);
608         particle(particletype + pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0);
609 }
610
611 /*
612 ===============
613 CL_ParseParticleEffect
614
615 Parse an effect out of the server message
616 ===============
617 */
618 void CL_ParseParticleEffect (void)
619 {
620         vec3_t org, dir;
621         int i, count, msgcount, color;
622
623         MSG_ReadVector(org, cl.protocol);
624         for (i=0 ; i<3 ; i++)
625                 dir[i] = MSG_ReadChar () * (1.0/16);
626         msgcount = MSG_ReadByte ();
627         color = MSG_ReadByte ();
628
629         if (msgcount == 255)
630                 count = 1024;
631         else
632                 count = msgcount;
633
634         if (cl_particles_blood_bloodhack.integer)
635         {
636                 if (color == 73)
637                 {
638                         // regular blood
639                         CL_BloodPuff(org, dir, count / 2);
640                         return;
641                 }
642                 if (color == 225)
643                 {
644                         // lightning blood
645                         CL_BloodPuff(org, dir, count / 2);
646                         return;
647                 }
648         }
649         CL_RunParticleEffect (org, dir, color, count);
650 }
651
652 /*
653 ===============
654 CL_ParticleExplosion
655
656 ===============
657 */
658 void CL_ParticleExplosion (vec3_t org)
659 {
660         int i;
661         trace_t trace;
662         //vec3_t v;
663         //vec3_t v2;
664         if (cl_stainmaps.integer)
665                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
666         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
667
668         i = CL_PointSuperContents(org);
669         if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
670         {
671                 if (cl_particles.integer && cl_particles_bubbles.integer && cl_particles_explosions_bubbles.integer)
672                         for (i = 0;i < 128 * cl_particles_quality.value;i++)
673                                 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, (1.0f / cl_particles_quality.value) * lhrandom(128, 255), (1.0f / cl_particles_quality.value) * 128, -0.125, 1.5, org[0] + lhrandom(-16, 16), org[1] + lhrandom(-16, 16), org[2] + lhrandom(-16, 16), lhrandom(-96, 96), lhrandom(-96, 96), lhrandom(-96, 96), (1.0 / 16.0));
674         }
675         else
676         {
677                 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
678                 // smoke puff
679                 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
680                 {
681                         for (i = 0;i < 32;i++)
682                         {
683                                 int k;
684                                 vec3_t v, v2;
685 #ifdef WORKINGLQUAKE
686                                 v2[0] = lhrandom(-48, 48);
687                                 v2[1] = lhrandom(-48, 48);
688                                 v2[2] = lhrandom(-48, 48);
689 #else
690                                 for (k = 0;k < 16;k++)
691                                 {
692                                         v[0] = org[0] + lhrandom(-48, 48);
693                                         v[1] = org[1] + lhrandom(-48, 48);
694                                         v[2] = org[2] + lhrandom(-48, 48);
695                                         trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
696                                         if (trace.fraction >= 0.1)
697                                                 break;
698                                 }
699                                 VectorSubtract(trace.endpos, org, v2);
700 #endif
701                                 VectorScale(v2, 2.0f, v2);
702                                 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0);
703                         }
704                 }
705
706                 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
707                         for (i = 0;i < 128 * cl_particles_quality.value;i++)
708                                 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, (1.0f / cl_particles_quality.value) * lhrandom(0, 255), (1.0f / cl_particles_quality.value) * 512, 1, 0, org[0], org[1], org[2], lhrandom(-256, 256), lhrandom(-256, 256), lhrandom(-256, 256) + 80, 0.2);
709         }
710
711         if (cl_particles_explosions_shell.integer)
712                 R_NewExplosion(org);
713 }
714
715 /*
716 ===============
717 CL_ParticleExplosion2
718
719 ===============
720 */
721 void CL_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength)
722 {
723         vec3_t vel;
724         vec3_t offset;
725         int i, k;
726         float pscale;
727         if (!cl_particles.integer) return;
728
729         for (i = 0;i < 512 * cl_particles_quality.value;i++)
730         {
731                 VectorRandom (offset);
732                 VectorScale (offset, 192, vel);
733                 VectorScale (offset, 8, offset);
734                 k = particlepalette[colorStart + (i % colorLength)];
735                 pscale = lhrandom(0.5, 1.5);
736                 particle(particletype + pt_static, k, k, tex_particle, pscale, (1.0f / cl_particles_quality.value) * 255, (1.0f/cl_particles_quality.value)*512, 0, 0, org[0] + offset[0], org[1] + offset[1], org[2] + offset[2], vel[0], vel[1], vel[2], lhrandom(1.5, 3));
737         }
738 }
739
740 /*
741 ===============
742 CL_BlobExplosion
743
744 ===============
745 */
746 void CL_BlobExplosion (vec3_t org)
747 {
748         CL_ParticleExplosion(org);
749 }
750
751 /*
752 ===============
753 CL_RunParticleEffect
754
755 ===============
756 */
757 void CL_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count)
758 {
759         int k;
760
761         if (count == 1024)
762         {
763                 CL_ParticleExplosion(org);
764                 return;
765         }
766         if (!cl_particles.integer) return;
767         count *= cl_particles_quality.value;
768         while (count--)
769         {
770                 k = particlepalette[color + (rand()&7)];
771                 if (gamemode == GAME_GOODVSBAD2)
772                         particle(particletype + pt_alphastatic, k, k, tex_particle, 5, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 300, 0, 0, org[0] + lhrandom(-8, 8), org[1] + lhrandom(-8, 8), org[2] + lhrandom(-8, 8), lhrandom(-10, 10), lhrandom(-10, 10), lhrandom(-10, 10), 0);
773                 else
774                         particle(particletype + pt_alphastatic, k, k, tex_particle, 1, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 512, 0, 0, org[0] + lhrandom(-8, 8), org[1] + lhrandom(-8, 8), org[2] + lhrandom(-8, 8), dir[0] + lhrandom(-15, 15), dir[1] + lhrandom(-15, 15), dir[2] + lhrandom(-15, 15), 0);
775         }
776 }
777
778 // LordHavoc: added this for spawning sparks/dust (which have strong gravity)
779 /*
780 ===============
781 CL_SparkShower
782 ===============
783 */
784 void CL_SparkShower (vec3_t org, vec3_t dir, int count, vec_t gravityscale)
785 {
786         int k;
787
788         if (!cl_particles.integer) return;
789
790         if (cl_particles_sparks.integer)
791         {
792                 // sparks
793                 count *= cl_particles_quality.value;
794                 while(count--)
795                 {
796                         k = particlepalette[0x68 + (rand() & 7)];
797                         particle(particletype + pt_spark, k, k, tex_particle, 0.4f, (1.0f / cl_particles_quality.value) * lhrandom(64, 255), (1.0f / cl_particles_quality.value) * 512, gravityscale, 0, org[0], org[1], org[2], lhrandom(-64, 64) + dir[0], lhrandom(-64, 64) + dir[1], lhrandom(0, 128) + dir[2], 0);
798                 }
799         }
800 }
801
802 void CL_Smoke (vec3_t org, vec3_t dir, int count)
803 {
804         vec3_t org2;
805         int k;
806         trace_t trace;
807
808         if (!cl_particles.integer) return;
809
810         // smoke puff
811         if (cl_particles_smoke.integer)
812         {
813                 k = count * 0.25 * cl_particles_quality.value;
814                 while(k--)
815                 {
816                         org2[0] = org[0] + 0.125f * lhrandom(-count, count);
817                         org2[1] = org[1] + 0.125f * lhrandom(-count, count);
818                         org2[2] = org[2] + 0.125f * lhrandom(-count, count);
819                         trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, NULL, SUPERCONTENTS_SOLID, false);
820                         particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 1024, 0, 0, trace.endpos[0], trace.endpos[1], trace.endpos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0);
821                 }
822         }
823 }
824
825 void CL_BulletMark (vec3_t org)
826 {
827         if (cl_stainmaps.integer)
828                 R_Stain(org, 32, 96, 96, 96, 24, 128, 128, 128, 24);
829         CL_SpawnDecalParticleForPoint(org, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
830 }
831
832 void CL_PlasmaBurn (vec3_t org)
833 {
834         if (cl_stainmaps.integer)
835                 R_Stain(org, 48, 96, 96, 96, 32, 128, 128, 128, 32);
836         CL_SpawnDecalParticleForPoint(org, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
837 }
838
839 static float bloodcount = 0;
840 void CL_BloodPuff (vec3_t org, vec3_t vel, int count)
841 {
842         float s;
843         vec3_t org2;
844         trace_t trace;
845         // bloodcount is used to accumulate counts too small to cause a blood particle
846         if (!cl_particles.integer) return;
847         if (!cl_particles_blood.integer) return;
848
849         s = count + 64.0f;
850         count *= 5.0f;
851         if (count > 1000)
852                 count = 1000;
853         bloodcount += count;
854         while(bloodcount > 0)
855         {
856                 org2[0] = org[0] + 0.125f * lhrandom(-bloodcount, bloodcount);
857                 org2[1] = org[1] + 0.125f * lhrandom(-bloodcount, bloodcount);
858                 org2[2] = org[2] + 0.125f * lhrandom(-bloodcount, bloodcount);
859                 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, NULL, SUPERCONTENTS_SOLID, false);
860                 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, cl_particles_blood_alpha.value * 768 / cl_particles_quality.value, cl_particles_blood_alpha.value * 384 / cl_particles_quality.value, 0, -1, trace.endpos[0], trace.endpos[1], trace.endpos[2], vel[0] + lhrandom(-s, s), vel[1] + lhrandom(-s, s), vel[2] + lhrandom(-s, s), 1);
861                 bloodcount -= 16 / cl_particles_quality.value;
862         }
863 }
864
865 void CL_BloodShower (vec3_t mins, vec3_t maxs, float velspeed, int count)
866 {
867         vec3_t org, vel, diff, center, velscale;
868         if (!cl_particles.integer) return;
869         if (!cl_particles_bloodshowers.integer) return;
870         if (!cl_particles_blood.integer) return;
871
872         VectorSubtract(maxs, mins, diff);
873         center[0] = (mins[0] + maxs[0]) * 0.5;
874         center[1] = (mins[1] + maxs[1]) * 0.5;
875         center[2] = (mins[2] + maxs[2]) * 0.5;
876         velscale[0] = velspeed * 2.0 / diff[0];
877         velscale[1] = velspeed * 2.0 / diff[1];
878         velscale[2] = velspeed * 2.0 / diff[2];
879
880         bloodcount += count * 5.0f;
881         while (bloodcount > 0)
882         {
883                 org[0] = lhrandom(mins[0], maxs[0]);
884                 org[1] = lhrandom(mins[1], maxs[1]);
885                 org[2] = lhrandom(mins[2], maxs[2]);
886                 vel[0] = (org[0] - center[0]) * velscale[0];
887                 vel[1] = (org[1] - center[1]) * velscale[1];
888                 vel[2] = (org[2] - center[2]) * velscale[2];
889                 bloodcount -= 16 / cl_particles_quality.value;
890                 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, cl_particles_blood_alpha.value * 768 / cl_particles_quality.value, cl_particles_blood_alpha.value * 384 / cl_particles_quality.value, 0, -1, org[0], org[1], org[2], vel[0], vel[1], vel[2], 1);
891         }
892 }
893
894 void CL_ParticleCube (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int gravity, int randomvel)
895 {
896         int k;
897         float t;
898         if (!cl_particles.integer) return;
899         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
900         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
901         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
902
903         count *= cl_particles_quality.value;
904         while (count--)
905         {
906                 k = particlepalette[colorbase + (rand()&3)];
907                 particle(particletype + pt_alphastatic, k, k, tex_particle, 2, 255 / cl_particles_quality.value, (255 / cl_particles_quality.value) / 2, gravity ? 1 : 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0] + lhrandom(-randomvel, randomvel), dir[1] + lhrandom(-randomvel, randomvel), dir[2] + lhrandom(-randomvel, randomvel), 0);
908         }
909 }
910
911 void CL_ParticleRain (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int type)
912 {
913         int k;
914         float t, z, minz, maxz;
915         particle_t *p;
916         if (!cl_particles.integer) return;
917         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
918         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
919         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
920         if (dir[2] < 0) // falling
921                 z = maxs[2];
922         else // rising??
923                 z = mins[2];
924
925         minz = z - fabs(dir[2]) * 0.1;
926         maxz = z + fabs(dir[2]) * 0.1;
927         minz = bound(mins[2], minz, maxs[2]);
928         maxz = bound(mins[2], maxz, maxs[2]);
929
930         count *= cl_particles_quality.value;
931
932         switch(type)
933         {
934         case 0:
935                 count *= 4; // ick, this should be in the mod or maps?
936
937                 while(count--)
938                 {
939                         k = particlepalette[colorbase + (rand()&3)];
940                         if (gamemode == GAME_GOODVSBAD2)
941                                 particle(particletype + pt_rain, k, k, tex_particle, 20, lhrandom(8, 16) / cl_particles_quality.value, 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0);
942                         else
943                                 particle(particletype + pt_rain, k, k, tex_particle, 0.5, lhrandom(8, 16) / cl_particles_quality.value, 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0);
944                 }
945                 break;
946         case 1:
947                 while(count--)
948                 {
949                         k = particlepalette[colorbase + (rand()&3)];
950                         if (gamemode == GAME_GOODVSBAD2)
951                                 p = particle(particletype + pt_snow, k, k, tex_particle, 20, lhrandom(64, 128) / cl_particles_quality.value, 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0);
952                         else
953                                 p = particle(particletype + pt_snow, k, k, tex_particle, 1, lhrandom(64, 128) / cl_particles_quality.value, 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0);
954                         if (p)
955                                 VectorCopy(p->vel, p->relativedirection);
956                 }
957                 break;
958         default:
959                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
960         }
961 }
962
963 void CL_Stardust (vec3_t mins, vec3_t maxs, int count)
964 {
965         int k;
966         float t;
967         vec3_t o, v, center;
968         if (!cl_particles.integer) return;
969
970         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
971         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
972         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
973
974         center[0] = (mins[0] + maxs[0]) * 0.5f;
975         center[1] = (mins[1] + maxs[1]) * 0.5f;
976         center[2] = (mins[2] + maxs[2]) * 0.5f;
977
978         count *= cl_particles_quality.value;
979         while (count--)
980         {
981                 k = particlepalette[224 + (rand()&15)];
982                 o[0] = lhrandom(mins[0], maxs[0]);
983                 o[1] = lhrandom(mins[1], maxs[1]);
984                 o[2] = lhrandom(mins[2], maxs[2]);
985                 VectorSubtract(o, center, v);
986                 VectorNormalize(v);
987                 VectorScale(v, 100, v);
988                 v[2] += sv_gravity.value * 0.15f;
989                 particle(particletype + pt_static, 0x903010, 0xFFD030, tex_particle, 1.5, lhrandom(64, 128) / cl_particles_quality.value, 128 / cl_particles_quality.value, 1, 0, o[0], o[1], o[2], v[0], v[1], v[2], 0.2);
990         }
991 }
992
993 void CL_FlameCube (vec3_t mins, vec3_t maxs, int count)
994 {
995         int k;
996         float t;
997         if (!cl_particles.integer) return;
998         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
999         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
1000         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
1001
1002         count *= cl_particles_quality.value;
1003         while (count--)
1004         {
1005                 k = particlepalette[224 + (rand()&15)];
1006                 particle(particletype + pt_static, k, k, tex_particle, 4, lhrandom(64, 128) / cl_particles_quality.value, 384 / cl_particles_quality.value, -1, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), lhrandom(-32, 32), lhrandom(-32, 32), lhrandom(0, 64), 1);
1007                 if (count & 1)
1008                         particle(particletype + pt_static, 0x303030, 0x606060, tex_smoke[rand()&7], 6, lhrandom(48, 96) / cl_particles_quality.value, 64 / cl_particles_quality.value, 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(0, 32), 0);
1009         }
1010 }
1011
1012 void CL_Flames (vec3_t org, vec3_t vel, int count)
1013 {
1014         int k;
1015         if (!cl_particles.integer) return;
1016
1017         count *= cl_particles_quality.value;
1018         while (count--)
1019         {
1020                 k = particlepalette[224 + (rand()&15)];
1021                 particle(particletype + pt_static, k, k, tex_particle, 4, lhrandom(64, 128) / cl_particles_quality.value, 384 / cl_particles_quality.value, -1, 1.1, org[0], org[1], org[2], vel[0] + lhrandom(-128, 128), vel[1] + lhrandom(-128, 128), vel[2] + lhrandom(-128, 128), 1);
1022         }
1023 }
1024
1025
1026
1027 /*
1028 ===============
1029 CL_LavaSplash
1030
1031 ===============
1032 */
1033 void CL_LavaSplash (vec3_t origin)
1034 {
1035         float i, j, inc, vel;
1036         int k, l;
1037         vec3_t          dir, org;
1038         if (!cl_particles.integer) return;
1039
1040         inc = 32 / cl_particles_quality.value;
1041         for (i = -128;i < 128;i += inc)
1042         {
1043                 for (j = -128;j < 128;j += inc)
1044                 {
1045                         dir[0] = j + lhrandom(0, 8);
1046                         dir[1] = i + lhrandom(0, 8);
1047                         dir[2] = 256;
1048                         org[0] = origin[0] + dir[0];
1049                         org[1] = origin[1] + dir[1];
1050                         org[2] = origin[2] + lhrandom(0, 64);
1051                         vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1052                         if (gamemode == GAME_GOODVSBAD2)
1053                         {
1054                                 k = particlepalette[0 + (rand()&255)];
1055                                 l = particlepalette[0 + (rand()&255)];
1056                                 particle(particletype + pt_static, k, l, tex_particle, 12, inc * 8, inc * 8, 0.05, 1, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0);
1057                         }
1058                         else
1059                         {
1060                                 k = l = particlepalette[224 + (rand()&7)];
1061                                 particle(particletype + pt_static, k, l, tex_particle, 12, inc * 8, inc * 8, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0);
1062                         }
1063                 }
1064         }
1065 }
1066
1067 /*
1068 ===============
1069 CL_TeleportSplash
1070
1071 ===============
1072 */
1073 void CL_TeleportSplash (vec3_t org)
1074 {
1075         float i, j, k, inc;
1076         if (!cl_particles.integer) return;
1077
1078         inc = 8 / cl_particles_quality.value;
1079         for (i = -16;i < 16;i += inc)
1080                 for (j = -16;j < 16;j += inc)
1081                         for (k = -24;k < 32;k += inc)
1082                                 particle(particletype + pt_static, 0xA0A0A0, 0xFFFFFF, tex_particle, 10, inc * lhrandom(8, 16), inc * 32, 0, 0, org[0] + i + lhrandom(0, 8), org[1] + j + lhrandom(0, 8), org[2] + k + lhrandom(0, 8), lhrandom(-64, 64), lhrandom(-64, 64), lhrandom(-256, 256), 1);
1083 }
1084
1085 #ifdef WORKINGLQUAKE
1086 void R_RocketTrail (vec3_t start, vec3_t end, int type)
1087 #else
1088 void CL_RocketTrail (vec3_t start, vec3_t end, int type, int color, entity_t *ent)
1089 #endif
1090 {
1091         vec3_t vec, dir, vel, pos;
1092         float len, dec, speed, qd;
1093         int smoke, blood, bubbles;
1094 #ifdef WORKINGLQUAKE
1095         int contents;
1096 #endif
1097
1098         if (end[0] == start[0] && end[1] == start[1] && end[2] == start[2])
1099                 return;
1100
1101         VectorSubtract(end, start, dir);
1102         VectorNormalize(dir);
1103
1104         VectorSubtract (end, start, vec);
1105 #ifdef WORKINGLQUAKE
1106         len = VectorNormalize (vec);
1107         dec = 0;
1108         speed = 1.0f / cl.frametime;
1109         VectorSubtract(end, start, vel);
1110 #else
1111         len = VectorNormalizeLength (vec);
1112         dec = -ent->persistent.trail_time;
1113         ent->persistent.trail_time += len;
1114         if (ent->persistent.trail_time < 0.01f)
1115                 return;
1116
1117         // if we skip out, leave it reset
1118         ent->persistent.trail_time = 0.0f;
1119
1120         speed = ent->state_current.time - ent->state_previous.time;
1121         if (speed)
1122                 speed = 1.0f / speed;
1123         VectorSubtract(ent->state_current.origin, ent->state_previous.origin, vel);
1124         color = particlepalette[color];
1125 #endif
1126         VectorScale(vel, speed, vel);
1127
1128         // advance into this frame to reach the first puff location
1129         VectorMA(start, dec, vec, pos);
1130         len -= dec;
1131
1132         smoke = cl_particles.integer && cl_particles_smoke.integer;
1133         blood = cl_particles.integer && cl_particles_blood.integer;
1134 #ifdef WORKINGLQUAKE
1135         contents = CL_PointQ1Contents(pos);
1136         bubbles = cl_particles.integer && cl_particles_bubbles.integer && (contents == CONTENTS_WATER || contents == CONTENTS_SLIME);
1137 #else
1138         bubbles = cl_particles.integer && cl_particles_bubbles.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1139 #endif
1140         qd = 1.0f / cl_particles_quality.value;
1141
1142         while (len >= 0)
1143         {
1144                 switch (type)
1145                 {
1146                         case 0: // rocket trail
1147                                 dec = qd*3;
1148                                 if (smoke)
1149                                 {
1150                                         particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, qd*cl_particles_smoke_alpha.value*125, qd*cl_particles_smoke_alphafade.value*125, 0, 0, pos[0], pos[1], pos[2], lhrandom(-5, 5), lhrandom(-5, 5), lhrandom(-5, 5), 0);
1151                                         particle(particletype + pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, qd*cl_particles_smoke_alpha.value*288, qd*cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], lhrandom(-20, 20), lhrandom(-20, 20), lhrandom(-20, 20), 0);
1152                                 }
1153                                 if (bubbles)
1154                                         particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, qd*lhrandom(64, 255), qd*256, -0.25, 1.5, pos[0], pos[1], pos[2], lhrandom(-16, 16), lhrandom(-16, 16), lhrandom(-16, 16), (1.0 / 16.0));
1155                                 break;
1156
1157                         case 1: // grenade trail
1158                                 // FIXME: make it gradually stop smoking
1159                                 dec = qd*3;
1160                                 if (smoke)
1161                                         particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, qd*cl_particles_smoke_alpha.value*100, qd*cl_particles_smoke_alphafade.value*100, 0, 0, pos[0], pos[1], pos[2], lhrandom(-5, 5), lhrandom(-5, 5), lhrandom(-5, 5), 0);
1162                                 break;
1163
1164
1165                         case 2: // blood
1166                         case 4: // slight blood
1167                                 dec = qd*16;
1168                                 if (blood)
1169                                         particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], vel[0] * 0.5f + lhrandom(-64, 64), vel[1] * 0.5f + lhrandom(-64, 64), vel[2] * 0.5f + lhrandom(-64, 64), 1);
1170                                 break;
1171
1172                         case 3: // green tracer
1173                                 dec = qd*6;
1174                                 if (smoke)
1175                                 {
1176                                         if (gamemode == GAME_GOODVSBAD2)
1177                                                 particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, qd*128, qd*384, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0);
1178                                         else
1179                                                 particle(particletype + pt_static, 0x002000, 0x003000, tex_particle, 6, qd*128, qd*384, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0);
1180                                 }
1181                                 break;
1182
1183                         case 5: // flame tracer
1184                                 dec = qd*6;
1185                                 if (smoke)
1186                                         particle(particletype + pt_static, 0x301000, 0x502000, tex_particle, 6, qd*128, qd*384, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0);
1187                                 break;
1188
1189                         case 6: // voor trail
1190                                 dec = qd*6;
1191                                 if (smoke)
1192                                 {
1193                                         if (gamemode == GAME_GOODVSBAD2)
1194                                                 particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, qd*255, qd*384, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0);
1195                                         else if (gamemode == GAME_PRYDON)
1196                                                 particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, qd*128, qd*384, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0);
1197                                         else
1198                                                 particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 6, qd*128, qd*384, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0);
1199                                 }
1200                                 break;
1201 #ifndef WORKINGLQUAKE
1202                         case 7: // Nehahra smoke tracer
1203                                 dec = qd*7;
1204                                 if (smoke)
1205                                         particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, qd*64, qd*320, 0, 0, pos[0], pos[1], pos[2], lhrandom(-4, 4), lhrandom(-4, 4), lhrandom(0, 16), 0);
1206                                 break;
1207                         case 8: // Nexuiz plasma trail
1208                                 dec = qd*4;
1209                                 if (smoke)
1210                                         particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, qd*255, qd*1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0);
1211                                 break;
1212                         case 9: // glow trail
1213                                 dec = qd*3;
1214                                 if (smoke)
1215                                         particle(particletype + pt_alphastatic, color, color, tex_particle, 5, qd*128, qd*320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0);
1216                                 break;
1217 #endif
1218                         default:
1219                                 Sys_Error("CL_RocketTrail: unknown trail type %i\n", type);
1220                 }
1221
1222                 // advance to next time and position
1223                 len -= dec;
1224                 VectorMA (pos, dec, vec, pos);
1225         }
1226 #ifndef WORKINGLQUAKE
1227         ent->persistent.trail_time = len;
1228 #endif
1229 }
1230
1231 void CL_BeamParticle (const vec3_t start, const vec3_t end, vec_t radius, float red, float green, float blue, float alpha, float lifetime)
1232 {
1233         int tempcolor2, cr, cg, cb;
1234         cr = red * 255;
1235         cg = green * 255;
1236         cb = blue * 255;
1237         tempcolor2 = (bound(0, cr, 255) << 16) | (bound(0, cg, 255) << 8) | bound(0, cb, 255);
1238         particle(particletype + pt_beam, tempcolor2, tempcolor2, tex_beam, radius, alpha * 255, alpha * 255 / lifetime, 0, 0, start[0], start[1], start[2], end[0], end[1], end[2], 0);
1239 }
1240
1241 void CL_Tei_Smoke(const vec3_t org, const vec3_t dir, int count)
1242 {
1243         float f;
1244         if (!cl_particles.integer) return;
1245
1246         // smoke puff
1247         if (cl_particles_smoke.integer)
1248                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1249                         particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 255 / cl_particles_quality.value, 512 / cl_particles_quality.value, 0, 0, org[0] + 0.125f * lhrandom(-count, count), org[1] + 0.125f * lhrandom (-count, count), org[2] + 0.125f * lhrandom(-count, count), dir[0] + lhrandom(-count, count) * 0.5f, dir[1] + lhrandom(-count, count) * 0.5f, dir[2] + lhrandom(-count, count) * 0.5f, 0);
1250 }
1251
1252 void CL_Tei_PlasmaHit(const vec3_t org, const vec3_t dir, int count)
1253 {
1254         float f;
1255         if (!cl_particles.integer) return;
1256
1257         if (cl_stainmaps.integer)
1258                 R_Stain(org, 40, 96, 96, 96, 40, 128, 128, 128, 40);
1259         CL_SpawnDecalParticleForPoint(org, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1260
1261         // smoke puff
1262         if (cl_particles_smoke.integer)
1263                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1264                         particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 255 / cl_particles_quality.value, 512 / cl_particles_quality.value, 0, 0, org[0] + 0.125f * lhrandom(-count, count), org[1] + 0.125f * lhrandom (-count, count), org[2] + 0.125f * lhrandom(-count, count), dir[0] + lhrandom(-count, count), dir[1] + lhrandom(-count, count), dir[2] + lhrandom(-count, count), 0);
1265
1266         // sparks
1267         if (cl_particles_sparks.integer)
1268                 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1269                         particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, lhrandom(64, 255) / cl_particles_quality.value, 512 / cl_particles_quality.value, 0, 0, org[0], org[1], org[2], lhrandom(-count, count) * 3.0f + dir[0], lhrandom(-count, count) * 3.0f + dir[1], lhrandom(-count, count) * 3.0f + dir[2], 0);
1270 }
1271
1272 /*
1273 ===============
1274 CL_MoveParticles
1275 ===============
1276 */
1277 void CL_MoveParticles (void)
1278 {
1279         particle_t *p;
1280         int i, maxparticle, j, a, content;
1281         float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1282         int hitent;
1283         trace_t trace;
1284
1285         // LordHavoc: early out condition
1286         if (!cl_numparticles)
1287         {
1288                 cl_freeparticle = 0;
1289                 return;
1290         }
1291
1292 #ifdef WORKINGLQUAKE
1293         frametime = cl.frametime;
1294 #else
1295         frametime = cl.time - cl.oldtime;
1296 #endif
1297         gravity = frametime * sv_gravity.value;
1298         dvel = 1+4*frametime;
1299         bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1300
1301         maxparticle = -1;
1302         j = 0;
1303         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1304         {
1305                 if (!p->type)
1306                         continue;
1307                 maxparticle = i;
1308                 content = 0;
1309
1310                 p->alpha -= p->alphafade * frametime;
1311
1312                 if (p->alpha <= 0)
1313                 {
1314                         p->type = NULL;
1315                         continue;
1316                 }
1317
1318                 if (p->type->orientation != PARTICLE_BEAM)
1319                 {
1320                         VectorCopy(p->org, oldorg);
1321                         VectorMA(p->org, frametime, p->vel, p->org);
1322                         VectorCopy(p->org, org);
1323                         if (p->bounce)
1324                         {
1325                                 if (p->type == particletype + pt_rain)
1326                                 {
1327                                         // raindrop - splash on solid/water/slime/lava
1328                                         trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK, false);
1329                                         if (trace.fraction < 1)
1330                                         {
1331                                                 // convert from a raindrop particle to a rainsplash decal
1332                                                 VectorCopy(trace.endpos, p->org);
1333                                                 VectorCopy(trace.plane.normal, p->vel);
1334                                                 VectorAdd(p->org, p->vel, p->org);
1335                                                 p->type = particletype + pt_raindecal;
1336                                                 p->texnum = tex_rainsplash[0];
1337                                                 p->time2 = cl.time;
1338                                                 p->alphafade = p->alpha / 0.4;
1339                                                 p->bounce = 0;
1340                                                 p->friction = 0;
1341                                                 p->gravity = 0;
1342                                                 p->size = 8.0;
1343                                         }
1344                                 }
1345                                 else if (p->type == particletype + pt_blood)
1346                                 {
1347                                         // blood - splash on solid
1348                                         trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, &hitent, SUPERCONTENTS_SOLID, false);
1349                                         if (trace.fraction < 1)
1350                                         {
1351                                                 // convert from a blood particle to a blood decal
1352                                                 VectorCopy(trace.endpos, p->org);
1353                                                 VectorCopy(trace.plane.normal, p->vel);
1354                                                 VectorAdd(p->org, p->vel, p->org);
1355 #ifndef WORKINGLQUAKE
1356                                                 if (cl_stainmaps.integer)
1357                                                         R_Stain(p->org, 32, 32, 16, 16, p->alpha * p->size * (1.0f / 40.0f), 192, 48, 48, p->alpha * p->size * (1.0f / 40.0f));
1358 #endif
1359                                                 if (!cl_decals.integer)
1360                                                 {
1361                                                         p->type = NULL;
1362                                                         continue;
1363                                                 }
1364
1365                                                 p->type = particletype + pt_decal;
1366                                                 p->texnum = tex_blooddecal[rand()&7];
1367 #ifndef WORKINGLQUAKE
1368                                                 p->owner = hitent;
1369                                                 p->ownermodel = cl_entities[hitent].render.model;
1370                                                 Matrix4x4_Transform(&cl_entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1371                                                 Matrix4x4_Transform3x3(&cl_entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1372 #endif
1373                                                 p->time2 = cl.time;
1374                                                 p->alphafade = 0;
1375                                                 p->bounce = 0;
1376                                                 p->friction = 0;
1377                                                 p->gravity = 0;
1378                                                 p->size *= 2.0f;
1379                                         }
1380                                 }
1381                                 else
1382                                 {
1383                                         trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, NULL, SUPERCONTENTS_SOLID, false);
1384                                         if (trace.fraction < 1)
1385                                         {
1386                                                 VectorCopy(trace.endpos, p->org);
1387                                                 if (p->bounce < 0)
1388                                                 {
1389                                                         p->type = NULL;
1390                                                         continue;
1391                                                 }
1392                                                 else
1393                                                 {
1394                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1395                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1396                                                         if (DotProduct(p->vel, p->vel) < 0.03)
1397                                                                 VectorClear(p->vel);
1398                                                 }
1399                                         }
1400                                 }
1401                         }
1402                         p->vel[2] -= p->gravity * gravity;
1403
1404                         if (p->friction)
1405                         {
1406                                 f = p->friction * frametime;
1407 #ifdef WORKINGLQUAKE
1408                                 if (CL_PointQ1Contents(p->org) != CONTENTS_EMPTY)
1409 #else
1410                                 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1411 #endif
1412                                         f *= 4;
1413                                 f = 1.0f - f;
1414                                 VectorScale(p->vel, f, p->vel);
1415                         }
1416                 }
1417
1418                 if (p->type != particletype + pt_static)
1419                 {
1420                         switch (p->type - particletype)
1421                         {
1422                         case pt_entityparticle:
1423                                 // particle that removes itself after one rendered frame
1424                                 if (p->time2)
1425                                         p->type = NULL;
1426                                 else
1427                                         p->time2 = 1;
1428                                 break;
1429                         case pt_blood:
1430 #ifdef WORKINGLQUAKE
1431                                 a = CL_PointQ1Contents(p->org);
1432                                 if (a <= CONTENTS_WATER)
1433 #else
1434                                 a = CL_PointSuperContents(p->org);
1435                                 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1436 #endif
1437                                 {
1438                                         p->size += frametime * 8;
1439                                         //p->alpha -= bloodwaterfade;
1440                                 }
1441                                 else
1442                                         p->vel[2] -= gravity;
1443 #ifdef WORKINGLQUAKE
1444                                 if (a == CONTENTS_SOLID || a == CONTENTS_LAVA)
1445 #else
1446                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1447 #endif
1448                                         p->type = NULL;
1449                                 break;
1450                         case pt_bubble:
1451 #ifdef WORKINGLQUAKE
1452                                 a = CL_PointQ1Contents(p->org);
1453                                 if (a != CONTENTS_WATER && a != CONTENTS_SLIME)
1454 #else
1455                                 a = CL_PointSuperContents(p->org);
1456                                 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1457 #endif
1458                                 {
1459                                         p->type = NULL;
1460                                         break;
1461                                 }
1462                                 break;
1463                         case pt_rain:
1464 #ifdef WORKINGLQUAKE
1465                                 a = CL_PointQ1Contents(p->org);
1466                                 if (a != CONTENTS_EMPTY && a != CONTENTS_SKY)
1467 #else
1468                                 a = CL_PointSuperContents(p->org);
1469                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
1470 #endif
1471                                         p->type = NULL;
1472                                 break;
1473                         case pt_snow:
1474                                 if (cl.time > p->time2)
1475                                 {
1476                                         // snow flutter
1477                                         p->time2 = cl.time + (rand() & 3) * 0.1;
1478                                         p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1479                                         p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1480                                         //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1481                                 }
1482 #ifdef WORKINGLQUAKE
1483                                 a = CL_PointQ1Contents(p->org);
1484                                 if (a != CONTENTS_EMPTY && a != CONTENTS_SKY)
1485 #else
1486                                 a = CL_PointSuperContents(p->org);
1487                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
1488 #endif
1489                                         p->type = NULL;
1490                                 break;
1491                         case pt_smoke:
1492                                 //p->size += frametime * 15;
1493                                 break;
1494                         case pt_decal:
1495                                 // FIXME: this has fairly wacky handling of alpha
1496                                 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1497 #ifndef WORKINGLQUAKE
1498                                 if (cl_entities[p->owner].render.model == p->ownermodel)
1499                                 {
1500                                         Matrix4x4_Transform(&cl_entities[p->owner].render.matrix, p->relativeorigin, p->org);
1501                                         Matrix4x4_Transform3x3(&cl_entities[p->owner].render.matrix, p->relativedirection, p->vel);
1502                                 }
1503                                 else
1504                                         p->type = NULL;
1505 #endif
1506                                 break;
1507                         case pt_raindecal:
1508                                 a = max(0, (cl.time - p->time2) * 40);
1509                                 if (a < 16)
1510                                         p->texnum = tex_rainsplash[a];
1511                                 else
1512                                         p->type = NULL;
1513                                 break;
1514                         default:
1515                                 break;
1516                         }
1517                 }
1518         }
1519         cl_numparticles = maxparticle + 1;
1520         cl_freeparticle = 0;
1521 }
1522
1523 #define MAX_PARTICLETEXTURES 64
1524 // particletexture_t is a rectangle in the particlefonttexture
1525 typedef struct particletexture_s
1526 {
1527         rtexture_t *texture;
1528         float s1, t1, s2, t2;
1529 }
1530 particletexture_t;
1531
1532 #if WORKINGLQUAKE
1533 static int particlefonttexture;
1534 #else
1535 static rtexturepool_t *particletexturepool;
1536 static rtexture_t *particlefonttexture;
1537 #endif
1538 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1539
1540 static cvar_t r_drawparticles = {0, "r_drawparticles", "1"};
1541
1542 #define PARTICLETEXTURESIZE 64
1543 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1544
1545 static qbyte shadebubble(float dx, float dy, vec3_t light)
1546 {
1547         float dz, f, dot;
1548         vec3_t normal;
1549         dz = 1 - (dx*dx+dy*dy);
1550         if (dz > 0) // it does hit the sphere
1551         {
1552                 f = 0;
1553                 // back side
1554                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1555                 VectorNormalize(normal);
1556                 dot = DotProduct(normal, light);
1557                 if (dot > 0.5) // interior reflection
1558                         f += ((dot *  2) - 1);
1559                 else if (dot < -0.5) // exterior reflection
1560                         f += ((dot * -2) - 1);
1561                 // front side
1562                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1563                 VectorNormalize(normal);
1564                 dot = DotProduct(normal, light);
1565                 if (dot > 0.5) // interior reflection
1566                         f += ((dot *  2) - 1);
1567                 else if (dot < -0.5) // exterior reflection
1568                         f += ((dot * -2) - 1);
1569                 f *= 128;
1570                 f += 16; // just to give it a haze so you can see the outline
1571                 f = bound(0, f, 255);
1572                 return (qbyte) f;
1573         }
1574         else
1575                 return 0;
1576 }
1577
1578 static void setuptex(int texnum, qbyte *data, qbyte *particletexturedata)
1579 {
1580         int basex, basey, y;
1581         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1582         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1583         particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1584         particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1585         particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1586         particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1587         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1588                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1589 }
1590
1591 void particletextureblotch(qbyte *data, float radius, float red, float green, float blue, float alpha)
1592 {
1593         int x, y;
1594         float cx, cy, dx, dy, f, iradius;
1595         qbyte *d;
1596         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1597         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1598         iradius = 1.0f / radius;
1599         alpha *= (1.0f / 255.0f);
1600         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1601         {
1602                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1603                 {
1604                         dx = (x - cx);
1605                         dy = (y - cy);
1606                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1607                         if (f > 0)
1608                         {
1609                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1610                                 d[0] += f * (red   - d[0]);
1611                                 d[1] += f * (green - d[1]);
1612                                 d[2] += f * (blue  - d[2]);
1613                         }
1614                 }
1615         }
1616 }
1617
1618 void particletextureclamp(qbyte *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1619 {
1620         int i;
1621         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1622         {
1623                 data[0] = bound(minr, data[0], maxr);
1624                 data[1] = bound(ming, data[1], maxg);
1625                 data[2] = bound(minb, data[2], maxb);
1626         }
1627 }
1628
1629 void particletextureinvert(qbyte *data)
1630 {
1631         int i;
1632         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1633         {
1634                 data[0] = 255 - data[0];
1635                 data[1] = 255 - data[1];
1636                 data[2] = 255 - data[2];
1637         }
1638 }
1639
1640 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1641 static void R_InitBloodTextures (qbyte *particletexturedata)
1642 {
1643         int i, j, k, m;
1644         qbyte data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1645
1646         // blood particles
1647         for (i = 0;i < 8;i++)
1648         {
1649                 memset(&data[0][0][0], 255, sizeof(data));
1650                 for (k = 0;k < 24;k++)
1651                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1652                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1653                 particletextureinvert(&data[0][0][0]);
1654                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1655         }
1656
1657         // blood decals
1658         for (i = 0;i < 8;i++)
1659         {
1660                 memset(&data[0][0][0], 255, sizeof(data));
1661                 m = 8;
1662                 for (j = 1;j < 10;j++)
1663                         for (k = min(j, m - 1);k < m;k++)
1664                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1665                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1666                 particletextureinvert(&data[0][0][0]);
1667                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1668         }
1669
1670 }
1671
1672 static void R_InitParticleTexture (void)
1673 {
1674         int x, y, d, i, k, m;
1675         float dx, dy, radius, f, f2;
1676         qbyte data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1677         vec3_t light;
1678         qbyte *particletexturedata;
1679
1680         // a note: decals need to modulate (multiply) the background color to
1681         // properly darken it (stain), and they need to be able to alpha fade,
1682         // this is a very difficult challenge because it means fading to white
1683         // (no change to background) rather than black (darkening everything
1684         // behind the whole decal polygon), and to accomplish this the texture is
1685         // inverted (dark red blood on white background becomes brilliant cyan
1686         // and white on black background) so we can alpha fade it to black, then
1687         // we invert it again during the blendfunc to make it work...
1688
1689         particletexturedata = (qbyte *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1690         memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1691
1692         // smoke
1693         for (i = 0;i < 8;i++)
1694         {
1695                 memset(&data[0][0][0], 255, sizeof(data));
1696                 do
1697                 {
1698                         qbyte noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1699
1700                         fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1701                         fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1702                         m = 0;
1703                         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1704                         {
1705                                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1706                                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1707                                 {
1708                                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1709                                         d = (noise2[y][x] - 128) * 3 + 192;
1710                                         if (d > 0)
1711                                                 d = d * (1-(dx*dx+dy*dy));
1712                                         d = (d * noise1[y][x]) >> 7;
1713                                         d = bound(0, d, 255);
1714                                         data[y][x][3] = (qbyte) d;
1715                                         if (m < d)
1716                                                 m = d;
1717                                 }
1718                         }
1719                 }
1720                 while (m < 224);
1721                 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1722         }
1723
1724         // rain splash
1725         for (i = 0;i < 16;i++)
1726         {
1727                 memset(&data[0][0][0], 255, sizeof(data));
1728                 radius = i * 3.0f / 4.0f / 16.0f;
1729                 f2 = 255.0f * ((15.0f - i) / 15.0f);
1730                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1731                 {
1732                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1733                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1734                         {
1735                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1736                                 f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
1737                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1738                         }
1739                 }
1740                 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1741         }
1742
1743         // normal particle
1744         memset(&data[0][0][0], 255, sizeof(data));
1745         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1746         {
1747                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1748                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1749                 {
1750                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1751                         d = 256 * (1 - (dx*dx+dy*dy));
1752                         d = bound(0, d, 255);
1753                         data[y][x][3] = (qbyte) d;
1754                 }
1755         }
1756         setuptex(tex_particle, &data[0][0][0], particletexturedata);
1757
1758         // rain
1759         memset(&data[0][0][0], 255, sizeof(data));
1760         light[0] = 1;light[1] = 1;light[2] = 1;
1761         VectorNormalize(light);
1762         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1763         {
1764                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1765                 // stretch upper half of bubble by +50% and shrink lower half by -50%
1766                 // (this gives an elongated teardrop shape)
1767                 if (dy > 0.5f)
1768                         dy = (dy - 0.5f) * 2.0f;
1769                 else
1770                         dy = (dy - 0.5f) / 1.5f;
1771                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1772                 {
1773                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1774                         // shrink bubble width to half
1775                         dx *= 2.0f;
1776                         data[y][x][3] = shadebubble(dx, dy, light);
1777                 }
1778         }
1779         setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1780
1781         // bubble
1782         memset(&data[0][0][0], 255, sizeof(data));
1783         light[0] = 1;light[1] = 1;light[2] = 1;
1784         VectorNormalize(light);
1785         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1786         {
1787                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1788                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1789                 {
1790                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1791                         data[y][x][3] = shadebubble(dx, dy, light);
1792                 }
1793         }
1794         setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1795
1796         // Blood particles and blood decals
1797         R_InitBloodTextures (particletexturedata);
1798
1799         // bullet decals
1800         for (i = 0;i < 8;i++)
1801         {
1802                 memset(&data[0][0][0], 255, sizeof(data));
1803                 for (k = 0;k < 12;k++)
1804                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1805                 for (k = 0;k < 3;k++)
1806                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1807                 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1808                 particletextureinvert(&data[0][0][0]);
1809                 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1810         }
1811
1812 #if WORKINGLQUAKE
1813         glBindTexture(GL_TEXTURE_2D, (particlefonttexture = gl_extension_number++));
1814         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1815         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1816 #else
1817
1818 #if 0
1819         Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1820 #endif
1821
1822         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1823         if (!particlefonttexture)
1824                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1825         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1826                 particletexture[i].texture = particlefonttexture;
1827
1828         // nexbeam
1829         fractalnoise(&noise3[0][0], 64, 4);
1830         m = 0;
1831         for (y = 0;y < 64;y++)
1832         {
1833                 dy = (y - 0.5f*64) / (64*0.5f-1);
1834                 for (x = 0;x < 16;x++)
1835                 {
1836                         dx = (x - 0.5f*16) / (16*0.5f-2);
1837                         d = (1 - sqrt(fabs(dx))) * noise3[y][x];
1838                         data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (qbyte) bound(0, d, 255);
1839                         data2[y][x][3] = 255;
1840                 }
1841         }
1842
1843 #if 0
1844         Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1845 #endif
1846
1847         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1848         if (!particletexture[tex_beam].texture)
1849                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1850         particletexture[tex_beam].s1 = 0;
1851         particletexture[tex_beam].t1 = 0;
1852         particletexture[tex_beam].s2 = 1;
1853         particletexture[tex_beam].t2 = 1;
1854 #endif
1855         Mem_Free(particletexturedata);
1856 }
1857
1858 static void r_part_start(void)
1859 {
1860         particletexturepool = R_AllocTexturePool();
1861         R_InitParticleTexture ();
1862 }
1863
1864 static void r_part_shutdown(void)
1865 {
1866         R_FreeTexturePool(&particletexturepool);
1867 }
1868
1869 static void r_part_newmap(void)
1870 {
1871         cl_numparticles = 0;
1872         cl_freeparticle = 0;
1873 }
1874
1875 void R_Particles_Init (void)
1876 {
1877         Cvar_RegisterVariable(&r_drawparticles);
1878 #ifdef WORKINGLQUAKE
1879         r_part_start();
1880 #else
1881         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1882 #endif
1883 }
1884
1885 #ifdef WORKINGLQUAKE
1886 void R_InitParticles(void)
1887 {
1888         CL_Particles_Init();
1889         R_Particles_Init();
1890 }
1891 #endif
1892
1893 float particle_vertex3f[12], particle_texcoord2f[8];
1894
1895 #ifdef WORKINGLQUAKE
1896 void R_DrawParticle(particle_t *p)
1897 {
1898 #else
1899 void R_DrawParticleCallback(const void *calldata1, int calldata2)
1900 {
1901         const particle_t *p = (particle_t *)calldata1;
1902         rmeshstate_t m;
1903 #endif
1904         pblend_t blendmode;
1905         float org[3], up2[3], v[3], right[3], up[3], fog, ifog, fogvec[3], cr, cg, cb, ca, size;
1906         particletexture_t *tex;
1907
1908         VectorCopy(p->org, org);
1909
1910         blendmode = p->type->blendmode;
1911         tex = &particletexture[p->texnum];
1912         cr = p->color[0] * (1.0f / 255.0f);
1913         cg = p->color[1] * (1.0f / 255.0f);
1914         cb = p->color[2] * (1.0f / 255.0f);
1915         ca = p->alpha * (1.0f / 255.0f);
1916         if (blendmode == PBLEND_MOD)
1917         {
1918                 cr *= ca;
1919                 cg *= ca;
1920                 cb *= ca;
1921                 cr = min(cr, 1);
1922                 cg = min(cg, 1);
1923                 cb = min(cb, 1);
1924                 ca = 1;
1925         }
1926 #ifndef WORKINGLQUAKE
1927         if (p->type->lighting)
1928         {
1929                 float ambient[3], diffuse[3], diffusenormal[3];
1930                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
1931                 cr *= (ambient[0] + 0.5 * diffuse[0]);
1932                 cg *= (ambient[1] + 0.5 * diffuse[1]);
1933                 cb *= (ambient[2] + 0.5 * diffuse[2]);
1934         }
1935         if (fogenabled)
1936         {
1937                 VectorSubtract(org, r_vieworigin, fogvec);
1938                 fog = exp(fogdensity/DotProduct(fogvec,fogvec));
1939                 ifog = 1 - fog;
1940                 cr = cr * ifog;
1941                 cg = cg * ifog;
1942                 cb = cb * ifog;
1943                 if (blendmode == PBLEND_ALPHA)
1944                 {
1945                         cr += fogcolor[0] * fog;
1946                         cg += fogcolor[1] * fog;
1947                         cb += fogcolor[2] * fog;
1948                 }
1949         }
1950
1951         R_Mesh_Matrix(&r_identitymatrix);
1952
1953         memset(&m, 0, sizeof(m));
1954         m.tex[0] = R_GetTexture(tex->texture);
1955         m.pointer_texcoord[0] = particle_texcoord2f;
1956         m.pointer_vertex = particle_vertex3f;
1957         R_Mesh_State(&m);
1958
1959         GL_Color(cr, cg, cb, ca);
1960
1961         if (blendmode == PBLEND_ALPHA)
1962                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1963         else if (blendmode == PBLEND_ADD)
1964                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
1965         else //if (blendmode == PBLEND_MOD)
1966                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1967         GL_DepthMask(false);
1968         GL_DepthTest(true);
1969 #endif
1970         size = p->size * cl_particles_size.value;
1971         if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
1972         {
1973                 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
1974                 {
1975                         // double-sided
1976                         if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
1977                         {
1978                                 VectorNegate(p->vel, v);
1979                                 VectorVectors(v, right, up);
1980                         }
1981                         else
1982                                 VectorVectors(p->vel, right, up);
1983                         VectorScale(right, size, right);
1984                         VectorScale(up, size, up);
1985                 }
1986                 else
1987                 {
1988                         VectorScale(r_viewleft, -size, right);
1989                         VectorScale(r_viewup, size, up);
1990                 }
1991                 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
1992                 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
1993                 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
1994                 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
1995                 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
1996                 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
1997                 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
1998                 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
1999                 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
2000                 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
2001                 particle_vertex3f[10] = org[1] + right[1] - up[1];
2002                 particle_vertex3f[11] = org[2] + right[2] - up[2];
2003                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2004                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2005                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2006                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2007         }
2008         else if (p->type->orientation == PARTICLE_SPARK)
2009         {
2010                 VectorMA(p->org, -0.02, p->vel, v);
2011                 VectorMA(p->org, 0.02, p->vel, up2);
2012                 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
2013                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2014                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2015                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2016                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2017         }
2018         else if (p->type->orientation == PARTICLE_BEAM)
2019         {
2020                 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
2021                 VectorSubtract(p->vel, p->org, up);
2022                 VectorNormalize(up);
2023                 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2024                 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2025                 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
2026                 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
2027                 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
2028                 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
2029         }
2030         else
2031         {
2032                 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2033                 return;
2034         }
2035
2036 #if WORKINGLQUAKE
2037         if (blendmode == PBLEND_ALPHA)
2038                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2039         else if (blendmode == PBLEND_ADD)
2040                 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
2041         else //if (blendmode == PBLEND_MOD)
2042                 glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2043         glColor4f(cr, cg, cb, ca);
2044         glBegin(GL_QUADS);
2045         glTexCoord2f(particle_texcoord2f[0], particle_texcoord2f[1]);glVertex3f(particle_vertex3f[ 0], particle_vertex3f[ 1], particle_vertex3f[ 2]);
2046         glTexCoord2f(particle_texcoord2f[2], particle_texcoord2f[3]);glVertex3f(particle_vertex3f[ 3], particle_vertex3f[ 4], particle_vertex3f[ 5]);
2047         glTexCoord2f(particle_texcoord2f[4], particle_texcoord2f[5]);glVertex3f(particle_vertex3f[ 6], particle_vertex3f[ 7], particle_vertex3f[ 8]);
2048         glTexCoord2f(particle_texcoord2f[6], particle_texcoord2f[7]);glVertex3f(particle_vertex3f[ 9], particle_vertex3f[10], particle_vertex3f[11]);
2049         glEnd();
2050 #else
2051         R_Mesh_Draw(0, 4, 2, polygonelements);
2052 #endif
2053 }
2054
2055 void R_DrawParticles (void)
2056 {
2057         int i;
2058         float minparticledist;
2059         particle_t *p;
2060
2061 #ifdef WORKINGLQUAKE
2062         CL_MoveParticles();
2063 #endif
2064
2065         // LordHavoc: early out conditions
2066         if ((!cl_numparticles) || (!r_drawparticles.integer))
2067                 return;
2068
2069         minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2070
2071 #ifdef WORKINGLQUAKE
2072         glBindTexture(GL_TEXTURE_2D, particlefonttexture);
2073         glEnable(GL_BLEND);
2074         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
2075         glDepthMask(0);
2076         // LordHavoc: only render if not too close
2077         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
2078                 if (p->type && DotProduct(p->org, r_viewforward) >= minparticledist)
2079                         R_DrawParticle(p);
2080         glDepthMask(1);
2081         glDisable(GL_BLEND);
2082         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2083 #else
2084         // LordHavoc: only render if not too close
2085         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
2086         {
2087                 if (p->type)
2088                 {
2089                         renderstats.particles++;
2090                         if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2091                         {
2092                                 if (p->type == particletype + pt_decal)
2093                                         R_DrawParticleCallback(p, 0);
2094                                 else
2095                                         R_MeshQueue_AddTransparent(p->org, R_DrawParticleCallback, p, 0);
2096                         }
2097                 }
2098         }
2099 #endif
2100 }
2101