/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Decals, from egl, orginally from qfusion? */ #include "gl_local.h" #define DF_SHADE 0x00000400 /* 1024 */ #define DF_NOTIMESCALE 0x00000800 /* 2048 */ #define INSTANT_DECAL -10000.0 #define MAX_DECALS 256 #define MAX_DECAL_VERTS 64 #define MAX_DECAL_FRAGMENTS 64 #define DECAL_BHOLE 1 #define DECAL_BLOOD 2 typedef struct cdecal_t { struct cdecal_t *prev, *next; float time; int numverts; vec3_t verts[MAX_DECAL_VERTS]; vec2_t stcoords[MAX_DECAL_VERTS]; mnode_t *node; vec3_t direction; vec4_t color; vec3_t org; int type; int flags; } cdecal_t; typedef struct { int firstvert; int numverts; mnode_t *node; msurface_t *surf; } fragment_t; static cdecal_t decals[MAX_DECALS]; static cdecal_t active_decals, *free_decals; static int R_GetClippedFragments(vec3_t origin, float radius, mat3_t axis, int maxfverts, vec3_t * fverts, int maxfragments, fragment_t * fragments); /* * void R_DrawDecal (int numverts, vec3_t *verts, vec2_t *stcoords, vec4_t * color, int type, int flags); */ /* * ================= CG_ClearDecals ================= */ void GL_ClearDecals(void) { int i; memset(decals, 0, sizeof(decals)); /* link decals */ free_decals = decals; active_decals.prev = &active_decals; active_decals.next = &active_decals; for (i = 0; i < MAX_DECALS - 1; i++) decals[i].next = &decals[i + 1]; } /* * ================= CG_AllocDecal * * Returns either a free decal or the oldest one ================= */ static cdecal_t * GL_AllocDecal(void) { cdecal_t *dl; if (free_decals) { /* take a free decal if possible */ dl = free_decals; free_decals = dl->next; } else { /* grab the oldest one otherwise */ dl = active_decals.prev; dl->prev->next = dl->next; dl->next->prev = dl->prev; } /* put the decal at the start of the list */ dl->prev = &active_decals; dl->next = active_decals.next; dl->next->prev = dl; dl->prev->next = dl; return dl; } /* * ================= CG_FreeDecal ================= */ static void GL_FreeDecal(cdecal_t * dl) { if (!dl->prev) return; /* remove from linked active list */ dl->prev->next = dl->next; dl->next->prev = dl->prev; /* insert into linked free list */ dl->next = free_decals; free_decals = dl; } /* * =============== makeDecal =============== */ void R_AddDecal(vec3_t origin, vec3_t dir, float red, float green, float blue, float alpha, float size, int type, int flags, float angle) { int i, j, numfragments; vec3_t verts[MAX_DECAL_VERTS], shade; fragment_t *fr, fragments[MAX_DECAL_FRAGMENTS]; mat3_t axis; cdecal_t *d; if (!gl_decals->value) return; /* invalid decal */ if (size <= 0 || VectorCompare(dir, vec3_origin)) return; /* calculate orientation matrix */ VectorNormalize2(dir, axis[0]); PerpendicularVector(axis[1], axis[0]); RotatePointAroundVector(axis[2], axis[0], axis[1], angle); CrossProduct(axis[0], axis[2], axis[1]); numfragments = R_GetClippedFragments(origin, size, axis, /* clip it */ MAX_DECAL_VERTS, verts, MAX_DECAL_FRAGMENTS, fragments); /* no valid fragments */ if (!numfragments) return; VectorScale(axis[1], 0.5 / size, axis[1]); VectorScale(axis[2], 0.5 / size, axis[2]); for (i = 0, fr = fragments; i < numfragments; i++, fr++) { if (fr->numverts > MAX_DECAL_VERTS) fr->numverts = MAX_DECAL_VERTS; else if (fr->numverts <= 0) continue; d = GL_AllocDecal(); d->time = r_refdef.time; d->numverts = fr->numverts; d->node = fr->node; VectorCopy(fr->surf->plane->normal, d->direction); /* reverse direction */ if (!(fr->surf->flags & SURF_PLANEBACK)) VectorNegate(d->direction, d->direction); Vector4Set(d->color, red, green, blue, alpha); VectorCopy(origin, d->org); if (flags & DF_SHADE) { R_LightPoint(origin, shade); for (j = 0; j < 3; j++) d->color[j] = (d->color[j] * shade[j] * 0.6) + (d->color[j] * 0.4); } d->type = type; d->flags = flags; for (j = 0; j < fr->numverts; j++) { vec3_t v; VectorCopy(verts[fr->firstvert + j], d->verts[j]); VectorSubtract(d->verts[j], origin, v); d->stcoords[j][0] = DotProduct(v, axis[1]) + 0.5; d->stcoords[j][1] = DotProduct(v, axis[2]) + 0.5; } } } /* * =============== CL_AddDecals =============== */ extern int r_visframecount; void R_AddDecals(void) { cdecal_t *dl, *next, *active; float mindist, time; int i, r_numdecals = 0; vec3_t v; vec4_t color; if (!gl_decals->value) return; active = &active_decals; mindist = DotProduct(r_origin, vpn) + 4.0; qglEnable(GL_POLYGON_OFFSET_FILL); qglPolygonOffset(-1, -2); qglDepthMask(GL_FALSE); qglEnable(GL_BLEND); GL_TexEnv(GL_MODULATE); GL_Bind(r_bholetexture->texnum); for (dl = active->next; dl != active; dl = next) { next = dl->next; if (dl->time + gl_decals_time->value <= r_refdef.time) { GL_FreeDecal(dl); continue; } if (dl->node == NULL || dl->node->visframe != r_visframecount) continue; /* do not render if the decal is behind the view */ if (DotProduct(dl->org, vpn) < mindist) continue; /* do not render if the view origin is behind the decal */ VectorSubtract(dl->org, r_origin, v); if (DotProduct(dl->direction, v) < 0) continue; Vector4Copy(dl->color, color); time = dl->time + gl_decals_time->value - r_refdef.time; if (time < 1.5) color[3] *= time / 1.5; /* Draw it */ qglColor4fv(color); qglBegin(GL_TRIANGLE_FAN); for (i = 0; i < dl->numverts; i++) { qglTexCoord2fv(dl->stcoords[i]); qglVertex3fv(dl->verts[i]); } qglEnd(); r_numdecals++; if (r_numdecals >= MAX_DECALS) break; } GL_TexEnv(GL_REPLACE); qglDisable(GL_BLEND); qglColor4f(1, 1, 1, 1); qglDepthMask(GL_TRUE); qglDisable(GL_POLYGON_OFFSET_FILL); } #define ON_EPSILON 0.1 /* point on plane side * epsilon */ #define BACKFACE_EPSILON 0.01 static int numFragmentVerts; static int maxFragmentVerts; static vec3_t *fragmentVerts; static int numClippedFragments; static int maxClippedFragments; static fragment_t *clippedFragments; static int fragmentFrame; static cplane_t fragmentPlanes[6]; /* * ================= R_ClipPoly ================= */ static void R_ClipPoly(int nump, vec4_t vecs, int stage, fragment_t * fr) { cplane_t *plane; qboolean front, back; vec4_t newv[MAX_DECAL_VERTS]; float *v, d, dists[MAX_DECAL_VERTS]; int newc, i, j, sides[MAX_DECAL_VERTS]; if (nump > MAX_DECAL_VERTS - 2) Com_Printf("R_ClipPoly: MAX_DECAL_VERTS"); if (stage == 6) { /* fully clipped */ if (nump > 2) { fr->numverts = nump; fr->firstvert = numFragmentVerts; if (numFragmentVerts + nump >= maxFragmentVerts) nump = maxFragmentVerts - numFragmentVerts; for (i = 0, v = vecs; i < nump; i++, v += 4) VectorCopy(v, fragmentVerts[numFragmentVerts + i]); numFragmentVerts += nump; } return; } front = back = false; plane = &fragmentPlanes[stage]; for (i = 0, v = vecs; i < nump; i++, v += 4) { d = PlaneDiff(v, plane); if (d > ON_EPSILON) { front = true; sides[i] = SIDE_FRONT; } else if (d < -ON_EPSILON) { back = true; sides[i] = SIDE_BACK; } else { sides[i] = SIDE_ON; } dists[i] = d; } if (!front) return; /* clip it */ sides[i] = sides[0]; dists[i] = dists[0]; VectorCopy(vecs, (vecs + (i * 4))); newc = 0; for (i = 0, v = vecs; i < nump; i++, v += 4) { switch (sides[i]) { case SIDE_FRONT: VectorCopy(v, newv[newc]); newc++; break; case SIDE_BACK: break; case SIDE_ON: VectorCopy(v, newv[newc]); newc++; break; } if (sides[i] == SIDE_ON || sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i]) continue; d = dists[i] / (dists[i] - dists[i + 1]); for (j = 0; j < 3; j++) newv[newc][j] = v[j] + d * (v[j + 4] - v[j]); newc++; } /* continue */ R_ClipPoly(newc, newv[0], stage + 1, fr); } /* * ================= R_PlanarSurfClipFragment ================= */ static void R_PlanarSurfClipFragment(mnode_t * node, msurface_t * surf, vec3_t normal) { int i; float *v, *v2, *v3; fragment_t *fr; vec4_t verts[MAX_DECAL_VERTS]; /* bogus face */ if (surf->numedges < 3) return; /* greater than 60 degrees */ if (surf->flags & SURF_PLANEBACK) { if (-DotProduct(normal, surf->plane->normal) < 0.5) return; } else { if (DotProduct(normal, surf->plane->normal) < 0.5) return; } v = surf->polys->verts[0]; /* copy vertex data and clip to each triangle */ for (i = 0; i < surf->polys->numverts - 2; i++) { fr = &clippedFragments[numClippedFragments]; fr->numverts = 0; fr->node = node; fr->surf = surf; v2 = surf->polys->verts[0] + (i + 1) * VERTEXSIZE; v3 = surf->polys->verts[0] + (i + 2) * VERTEXSIZE; VectorCopy(v, verts[0]); VectorCopy(v2, verts[1]); VectorCopy(v3, verts[2]); R_ClipPoly(3, verts[0], 0, fr); if (fr->numverts) { numClippedFragments++; if ((numFragmentVerts >= maxFragmentVerts) || (numClippedFragments >= maxClippedFragments)) { return; } } } } /* * ================= R_RecursiveFragmentNode ================= */ static void R_RecursiveFragmentNode(mnode_t * node, vec3_t origin, float radius, vec3_t normal) { float dist; cplane_t *plane; mark0: if ((numFragmentVerts >= maxFragmentVerts) || (numClippedFragments >= maxClippedFragments)) return; /* already reached the limit somewhere else */ if (node->contents == CONTENTS_SOLID) return; if (node->contents != CONTENTS_NODE) { /* leaf */ int c; mleaf_t *leaf; msurface_t *surf, **mark; leaf = (mleaf_t *) node; if (!(c = leaf->nummarksurfaces)) return; mark = leaf->firstmarksurface; do { if ((numFragmentVerts >= maxFragmentVerts) || (numClippedFragments >= maxClippedFragments)) return; surf = *mark++; if (surf->fragmentframe == fragmentFrame) continue; surf->fragmentframe = fragmentFrame; if (!(surf->texinfo->flags & (SURF_SKY | SURF_WARP | SURF_TRANS33 | SURF_TRANS66 | SURF_FLOWING | SURF_NODRAW))) { R_PlanarSurfClipFragment(node, surf, normal); } } while (--c); return; } plane = node->plane; dist = PlaneDiff(origin, plane); if (dist > radius) { node = node->children[0]; goto mark0; } if (dist < -radius) { node = node->children[1]; goto mark0; } R_RecursiveFragmentNode(node->children[0], origin, radius, normal); R_RecursiveFragmentNode(node->children[1], origin, radius, normal); } /* * ================= R_GetClippedFragments ================= */ static int R_GetClippedFragments(vec3_t origin, float radius, mat3_t axis, int maxfverts, vec3_t * fverts, int maxfragments, fragment_t * fragments) { int i; float d; fragmentFrame++; /* initialize fragments */ numFragmentVerts = 0; maxFragmentVerts = maxfverts; fragmentVerts = fverts; numClippedFragments = 0; maxClippedFragments = maxfragments; clippedFragments = fragments; /* calculate clipping planes */ for (i = 0; i < 3; i++) { d = DotProduct(origin, axis[i]); VectorCopy(axis[i], fragmentPlanes[i * 2].normal); fragmentPlanes[i * 2].dist = d - radius; fragmentPlanes[i * 2].type = PlaneTypeForNormal(fragmentPlanes[i * 2].normal); VectorNegate(axis[i], fragmentPlanes[i * 2 + 1].normal); fragmentPlanes[i * 2 + 1].dist = -d - radius; fragmentPlanes[i * 2 + 1].type = PlaneTypeForNormal(fragmentPlanes[i * 2 + 1].normal); } R_RecursiveFragmentNode(r_worldmodel->nodes, origin, radius, axis[0]); return numClippedFragments; }