+
+#define MAX_PARTICLETEXTURES 64
+// particletexture_t is a rectangle in the particlefonttexture
+typedef struct
+{
+ rtexture_t *texture;
+ float s1, t1, s2, t2;
+}
+particletexture_t;
+
+#if WORKINGLQUAKE
+static int particlefonttexture;
+#else
+static rtexturepool_t *particletexturepool;
+static rtexture_t *particlefonttexture;
+#endif
+static particletexture_t particletexture[MAX_PARTICLETEXTURES];
+
+static cvar_t r_drawparticles = {0, "r_drawparticles", "1"};
+
+static qbyte shadebubble(float dx, float dy, vec3_t light)
+{
+ float dz, f, dot;
+ vec3_t normal;
+ dz = 1 - (dx*dx+dy*dy);
+ if (dz > 0) // it does hit the sphere
+ {
+ f = 0;
+ // back side
+ normal[0] = dx;normal[1] = dy;normal[2] = dz;
+ VectorNormalize(normal);
+ dot = DotProduct(normal, light);
+ if (dot > 0.5) // interior reflection
+ f += ((dot * 2) - 1);
+ else if (dot < -0.5) // exterior reflection
+ f += ((dot * -2) - 1);
+ // front side
+ normal[0] = dx;normal[1] = dy;normal[2] = -dz;
+ VectorNormalize(normal);
+ dot = DotProduct(normal, light);
+ if (dot > 0.5) // interior reflection
+ f += ((dot * 2) - 1);
+ else if (dot < -0.5) // exterior reflection
+ f += ((dot * -2) - 1);
+ f *= 128;
+ f += 16; // just to give it a haze so you can see the outline
+ f = bound(0, f, 255);
+ return (qbyte) f;
+ }
+ else
+ return 0;
+}
+
+static void setuptex(int texnum, qbyte *data, qbyte *particletexturedata)
+{
+ int basex, basey, y;
+ basex = ((texnum >> 0) & 7) * 32;
+ basey = ((texnum >> 3) & 7) * 32;
+ particletexture[texnum].s1 = (basex + 1) / 256.0f;
+ particletexture[texnum].t1 = (basey + 1) / 256.0f;
+ particletexture[texnum].s2 = (basex + 31) / 256.0f;
+ particletexture[texnum].t2 = (basey + 31) / 256.0f;
+ for (y = 0;y < 32;y++)
+ memcpy(particletexturedata + ((basey + y) * 256 + basex) * 4, data + y * 32 * 4, 32 * 4);
+}
+
+void particletextureblotch(qbyte *data, float radius, float red, float green, float blue, float alpha)
+{
+ int x, y;
+ float cx, cy, dx, dy, f, iradius;
+ qbyte *d;
+ cx = lhrandom(radius + 1, 30 - radius);
+ cy = lhrandom(radius + 1, 30 - radius);
+ iradius = 1.0f / radius;
+ alpha *= (1.0f / 255.0f);
+ for (y = 0;y < 32;y++)
+ {
+ for (x = 0;x < 32;x++)
+ {
+ dx = (x - cx);
+ dy = (y - cy);
+ f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
+ if (f > 0)
+ {
+ d = data + (y * 32 + x) * 4;
+ d[0] += f * (red - d[0]);
+ d[1] += f * (green - d[1]);
+ d[2] += f * (blue - d[2]);
+ }
+ }
+ }
+}
+
+void particletextureclamp(qbyte *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
+{
+ int i;
+ for (i = 0;i < 32*32;i++, data += 4)
+ {
+ data[0] = bound(minr, data[0], maxr);
+ data[1] = bound(ming, data[1], maxg);
+ data[2] = bound(minb, data[2], maxb);
+ }
+}
+
+void particletextureinvert(qbyte *data)
+{
+ int i;
+ for (i = 0;i < 32*32;i++, data += 4)
+ {
+ data[0] = 255 - data[0];
+ data[1] = 255 - data[1];
+ data[2] = 255 - data[2];
+ }
+}
+
+static void R_InitParticleTexture (void)
+{
+ int x, y, d, i, j, k, m;
+ float dx, dy, radius, f, f2;
+ qbyte data[32][32][4], noise1[64][64], noise2[64][64], data2[64][16][4];
+ vec3_t light;
+ qbyte particletexturedata[256*256*4];
+
+ // a note: decals need to modulate (multiply) the background color to
+ // properly darken it (stain), and they need to be able to alpha fade,
+ // this is a very difficult challenge because it means fading to white
+ // (no change to background) rather than black (darkening everything
+ // behind the whole decal polygon), and to accomplish this the texture is
+ // inverted (dark red blood on white background becomes brilliant cyan
+ // and white on black background) so we can alpha fade it to black, then
+ // we invert it again during the blendfunc to make it work...
+
+ memset(particletexturedata, 255, sizeof(particletexturedata));
+
+ // smoke
+ for (i = 0;i < 8;i++)
+ {
+ memset(&data[0][0][0], 255, sizeof(data));
+ do
+ {
+ fractalnoise(&noise1[0][0], 64, 4);
+ fractalnoise(&noise2[0][0], 64, 8);
+ m = 0;
+ for (y = 0;y < 32;y++)
+ {
+ dy = y - 16;
+ for (x = 0;x < 32;x++)
+ {
+ dx = x - 16;
+ d = (noise2[y][x] - 128) * 3 + 192;
+ if (d > 0)
+ d = d * (256 - (int) (dx*dx+dy*dy)) / 256;
+ d = (d * noise1[y][x]) >> 7;
+ d = bound(0, d, 255);
+ data[y][x][3] = (qbyte) d;
+ if (m < d)
+ m = d;
+ }
+ }
+ }
+ while (m < 224);
+ setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
+ }
+
+ // rain splash
+ for (i = 0;i < 16;i++)
+ {
+ memset(&data[0][0][0], 255, sizeof(data));
+ radius = i * 3.0f / 16.0f;
+ f2 = 255.0f * ((15.0f - i) / 15.0f);
+ for (y = 0;y < 32;y++)
+ {
+ dy = (y - 16) * 0.25f;
+ for (x = 0;x < 32;x++)
+ {
+ dx = (x - 16) * 0.25f;
+ f = (1.0 - fabs(radius - sqrt(dx*dx+dy*dy))) * f2;
+ data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
+ }
+ }
+ setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
+ }
+
+ // normal particle
+ memset(&data[0][0][0], 255, sizeof(data));
+ for (y = 0;y < 32;y++)
+ {
+ dy = y - 16;
+ for (x = 0;x < 32;x++)
+ {
+ dx = x - 16;
+ d = (256 - (dx*dx+dy*dy));
+ d = bound(0, d, 255);
+ data[y][x][3] = (qbyte) d;
+ }
+ }
+ setuptex(tex_particle, &data[0][0][0], particletexturedata);
+
+ // rain
+ memset(&data[0][0][0], 255, sizeof(data));
+ light[0] = 1;light[1] = 1;light[2] = 1;
+ VectorNormalize(light);
+ for (y = 0;y < 32;y++)
+ for (x = 0;x < 32;x++)
+ 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);
+ setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
+
+ // bubble
+ memset(&data[0][0][0], 255, sizeof(data));
+ light[0] = 1;light[1] = 1;light[2] = 1;
+ VectorNormalize(light);
+ for (y = 0;y < 32;y++)
+ for (x = 0;x < 32;x++)
+ data[y][x][3] = shadebubble((x - 16) * (1.0 / 16.0), (y - 16) * (1.0 / 16.0), light);
+ setuptex(tex_bubble, &data[0][0][0], particletexturedata);
+
+ // blood particles
+ for (i = 0;i < 8;i++)
+ {
+ memset(&data[0][0][0], 255, sizeof(data));
+ for (k = 0;k < 24;k++)
+ particletextureblotch(&data[0][0][0], 2, 96, 0, 0, 160);
+ //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
+ particletextureinvert(&data[0][0][0]);
+ setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
+ }
+
+ // blood decals
+ for (i = 0;i < 8;i++)
+ {
+ memset(&data[0][0][0], 255, sizeof(data));
+ for (k = 0;k < 24;k++)
+ particletextureblotch(&data[0][0][0], 2, 96, 0, 0, 96);
+ for (j = 3;j < 7;j++)
+ for (k = 0, m = rand() % 12;k < m;k++)
+ particletextureblotch(&data[0][0][0], j, 96, 0, 0, 192);
+ //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
+ particletextureinvert(&data[0][0][0]);
+ setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
+ }
+
+ // bullet decals
+ for (i = 0;i < 8;i++)
+ {
+ memset(&data[0][0][0], 255, sizeof(data));
+ for (k = 0;k < 12;k++)
+ particletextureblotch(&data[0][0][0], 2, 0, 0, 0, 128);
+ for (k = 0;k < 3;k++)
+ particletextureblotch(&data[0][0][0], 14, 0, 0, 0, 160);
+ //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
+ particletextureinvert(&data[0][0][0]);
+ setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
+ }
+
+#if WORKINGLQUAKE
+ glBindTexture(GL_TEXTURE_2D, (particlefonttexture = gl_extension_number++));
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+#else
+ particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", 256, 256, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
+ for (i = 0;i < MAX_PARTICLETEXTURES;i++)
+ particletexture[i].texture = particlefonttexture;
+
+ // beam
+ fractalnoise(&noise1[0][0], 64, 4);
+ m = 0;
+ for (y = 0;y < 64;y++)
+ {
+ for (x = 0;x < 16;x++)
+ {
+ if (x < 8)
+ d = x;
+ else
+ d = (15 - x);
+ d = d * d * noise1[y][x] / (7 * 7);
+ data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (qbyte) bound(0, d, 255);
+ data2[y][x][3] = 255;
+ }
+ }
+
+ particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "beam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
+ particletexture[tex_beam].s1 = 0;
+ particletexture[tex_beam].t1 = 0;
+ particletexture[tex_beam].s2 = 1;
+ particletexture[tex_beam].t2 = 1;
+#endif
+}
+
+static void r_part_start(void)
+{
+ particletexturepool = R_AllocTexturePool();
+ R_InitParticleTexture ();
+}
+
+static void r_part_shutdown(void)
+{
+ R_FreeTexturePool(&particletexturepool);
+}
+
+static void r_part_newmap(void)
+{
+ cl_numparticles = 0;
+}
+
+void R_Particles_Init (void)
+{
+ Cvar_RegisterVariable(&r_drawparticles);
+#ifdef WORKINGLQUAKE
+ r_part_start();
+#else
+ R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
+#endif
+}
+
+#ifdef WORKINGLQUAKE
+void R_InitParticles(void)
+{
+ CL_Particles_Init();
+ R_Particles_Init();
+}
+#endif
+
+float particle_vertex3f[12], particle_texcoord2f[8];
+
+#ifdef WORKINGLQUAKE
+void R_DrawParticle(particle_t *p)
+{
+#else
+void R_DrawParticleCallback(const void *calldata1, int calldata2)
+{
+ const particle_t *p = calldata1;
+ rmeshstate_t m;
+#endif
+ float org[3], up2[3], v[3], right[3], up[3], fog, ifog, fogvec[3], cr, cg, cb, ca;
+ particletexture_t *tex;
+
+ VectorCopy(p->org, org);
+
+ tex = &particletexture[p->texnum];
+ cr = p->color[0] * (1.0f / 255.0f);
+ cg = p->color[1] * (1.0f / 255.0f);
+ cb = p->color[2] * (1.0f / 255.0f);
+ ca = p->alpha * (1.0f / 255.0f);
+ if (p->blendmode == PBLEND_MOD)
+ {
+ cr *= ca;
+ cg *= ca;
+ cb *= ca;
+ cr = min(cr, 1);
+ cg = min(cg, 1);
+ cb = min(cb, 1);
+ ca = 1;
+ }
+
+#ifndef WORKINGLQUAKE
+ if (fogenabled && p->blendmode != PBLEND_MOD)
+ {
+ VectorSubtract(org, r_vieworigin, fogvec);
+ fog = exp(fogdensity/DotProduct(fogvec,fogvec));
+ ifog = 1 - fog;
+ cr = cr * ifog;
+ cg = cg * ifog;
+ cb = cb * ifog;
+ if (p->blendmode == 0)
+ {
+ cr += fogcolor[0] * fog;
+ cg += fogcolor[1] * fog;
+ cb += fogcolor[2] * fog;
+ }
+ }
+
+ R_Mesh_Matrix(&r_identitymatrix);
+
+ memset(&m, 0, sizeof(m));
+ m.tex[0] = R_GetTexture(tex->texture);
+ m.pointer_texcoord[0] = particle_texcoord2f;
+ m.pointer_vertex = particle_vertex3f;
+ R_Mesh_State(&m);
+
+ GL_Color(cr, cg, cb, ca);
+
+ if (p->blendmode == 0)
+ GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ else if (p->blendmode == 1)
+ GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
+ else
+ GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+ GL_DepthMask(false);
+ GL_DepthTest(true);
+#endif
+ if (p->orientation == PARTICLE_BILLBOARD || p->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
+ {
+ if (p->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
+ {
+ // double-sided
+ if (DotProduct(p->vel2, r_vieworigin) > DotProduct(p->vel2, org))
+ {
+ VectorNegate(p->vel2, v);
+ VectorVectors(v, right, up);
+ }
+ else
+ VectorVectors(p->vel2, right, up);
+ VectorScale(right, p->scalex, right);
+ VectorScale(up, p->scaley, up);
+ }
+ else
+ {
+ VectorScale(r_viewleft, -p->scalex, right);
+ VectorScale(r_viewup, p->scaley, up);
+ }
+ particle_vertex3f[ 0] = org[0] - right[0] - up[0];
+ particle_vertex3f[ 1] = org[1] - right[1] - up[1];
+ particle_vertex3f[ 2] = org[2] - right[2] - up[2];
+ particle_vertex3f[ 3] = org[0] - right[0] + up[0];
+ particle_vertex3f[ 4] = org[1] - right[1] + up[1];
+ particle_vertex3f[ 5] = org[2] - right[2] + up[2];
+ particle_vertex3f[ 6] = org[0] + right[0] + up[0];
+ particle_vertex3f[ 7] = org[1] + right[1] + up[1];
+ particle_vertex3f[ 8] = org[2] + right[2] + up[2];
+ particle_vertex3f[ 9] = org[0] + right[0] - up[0];
+ particle_vertex3f[10] = org[1] + right[1] - up[1];
+ particle_vertex3f[11] = org[2] + right[2] - up[2];
+ particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
+ particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
+ particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
+ particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
+ }
+ else if (p->orientation == PARTICLE_SPARK)
+ {
+ VectorMA(p->org, -p->scaley, p->vel, v);
+ VectorMA(p->org, p->scaley, p->vel, up2);
+ R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, p->scalex);
+ particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
+ particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
+ particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
+ particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
+ }
+ else if (p->orientation == PARTICLE_BEAM)
+ {
+ R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel2, p->scalex);
+ VectorSubtract(p->vel2, p->org, up);
+ VectorNormalizeFast(up);
+ v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) - cl.time * 0.25;
+ v[1] = DotProduct(p->vel2, up) * (1.0f / 64.0f) - cl.time * 0.25;
+ particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
+ particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
+ particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
+ particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
+ }
+ else
+ Host_Error("R_DrawParticles: unknown particle orientation %i\n", p->orientation);
+
+#if WORKINGLQUAKE
+ if (p->blendmode == 0)
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ else if (p->blendmode == 1)
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+ else
+ glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+ glColor4f(cr, cg, cb, ca);
+ glBegin(GL_QUADS);
+ glTexCoord2f(particle_texcoord2f[0], particle_texcoord2f[1]);glVertex3f(particle_vertex3f[ 0], particle_vertex3f[ 1], particle_vertex3f[ 2]);
+ glTexCoord2f(particle_texcoord2f[2], particle_texcoord2f[3]);glVertex3f(particle_vertex3f[ 3], particle_vertex3f[ 4], particle_vertex3f[ 5]);
+ glTexCoord2f(particle_texcoord2f[4], particle_texcoord2f[5]);glVertex3f(particle_vertex3f[ 6], particle_vertex3f[ 7], particle_vertex3f[ 8]);
+ glTexCoord2f(particle_texcoord2f[6], particle_texcoord2f[7]);glVertex3f(particle_vertex3f[ 9], particle_vertex3f[10], particle_vertex3f[11]);
+ glEnd();
+#else
+ R_Mesh_Draw(4, 2, polygonelements);
+#endif
+}
+
+void R_DrawParticles (void)
+{
+ int i;
+ float minparticledist;
+ particle_t *p;
+
+#ifdef WORKINGLQUAKE
+ CL_MoveParticles();
+#endif
+
+ // LordHavoc: early out conditions
+ if ((!cl_numparticles) || (!r_drawparticles.integer))
+ return;
+
+ minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
+
+#ifdef WORKINGLQUAKE
+ glBindTexture(GL_TEXTURE_2D, particlefonttexture);
+ glEnable(GL_BLEND);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+ glDepthMask(0);
+ // LordHavoc: only render if not too close
+ for (i = 0, p = particles;i < cl_numparticles;i++, p++)
+ if (DotProduct(p->org, r_viewforward) >= minparticledist)
+ R_DrawParticle(p);
+ glDepthMask(1);
+ glDisable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#else
+ // LordHavoc: only render if not too close
+ c_particles += cl_numparticles;
+ for (i = 0, p = particles;i < cl_numparticles;i++, p++)
+ if (DotProduct(p->org, r_viewforward) >= minparticledist || p->orientation == PARTICLE_BEAM)
+ R_MeshQueue_AddTransparent(p->org, R_DrawParticleCallback, p, 0);
+#endif
+}
+