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