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