/* * Copyright(c) 1997-2001 Id Software, Inc. * Copyright(c) 2002 The Quakeforge Project. * Copyright(c) 2006 Quetoo. * * 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. */ #include "video.h" static vec3_t modelorg; // relative to viewpoint msurface_t *r_alpha_surfaces; #define LIGHTMAP_BYTES 4 #define BLOCK_WIDTH 128 #define BLOCK_HEIGHT 128 #define MAX_LIGHTMAPS 128 int c_visible_lightmaps; int c_visible_textures; typedef struct { int internal_format; int current_lightmap_texture; msurface_t *lightmap_surfaces[MAX_LIGHTMAPS]; int allocated[BLOCK_WIDTH]; // the lightmap texture data needs to be kept in // main memory so texsubimage can update properly byte lightmap_buffer[LIGHTMAP_BYTES * BLOCK_WIDTH * BLOCK_HEIGHT]; } gllightmapstate_t; static gllightmapstate_t gl_lms; static void LM_InitBlock(void); static void LM_UploadBlock(void); static qboolean LM_AllocBlock(int w, int h, int *x, int *y); extern void GL_BuildLightMap(msurface_t *surf, byte *dest, int stride); /* BRUSH MODELS */ /* GL_TextureAnimation Returns the proper texture for a given time and base texture */ image_t *GL_TextureAnimation(mtexinfo_t *tex){ int c; if(!tex->next) return tex->image; c = currententity->frame % tex->numframes; while(c){ tex = tex->next; c--; } return tex->image; } /* DrawGLPoly */ void DrawGLPoly(glpoly_t *p){ int i; float *v; qglBegin(GL_POLYGON); v = p->verts[0]; for(i = 0; i < p->numverts; i++, v += VERTEXSIZE){ qglTexCoord2f(v[3], v[4]); qglVertex3fv(v); } qglEnd(); } /* DrawGLFlowingPoly -- version of DrawGLPoly that handles scrolling texture */ void DrawGLFlowingPoly(msurface_t *fa){ int i; float *v; glpoly_t *p; float scroll; p = fa->polys; scroll = -64 *((r_viewdef.time / 40.0) -(int)(r_viewdef.time / 40.0)); if(scroll == 0.0) scroll = -64.0; qglBegin(GL_POLYGON); v = p->verts[0]; for(i = 0; i < p->numverts; i++, v += VERTEXSIZE){ qglTexCoord2f((v[3] + scroll), v[4]); qglVertex3fv(v); } qglEnd(); } /* DrawGLPolyChain */ void DrawGLPolyChain(glpoly_t *p, float soffset, float toffset){ if(soffset == 0 && toffset == 0){ for(; p != 0; p = p->chain){ float * v; int j; qglBegin(GL_POLYGON); v = p->verts[0]; for(j = 0; j < p->numverts; j++, v += VERTEXSIZE){ qglTexCoord2f(v[5], v[6]); qglVertex3fv(v); } qglEnd(); } } else { for(; p != 0; p = p->chain){ float * v; int j; qglBegin(GL_POLYGON); v = p->verts[0]; for(j = 0; j < p->numverts; j++, v += VERTEXSIZE){ qglTexCoord2f(v[5] - soffset, v[6] - toffset); qglVertex3fv(v); } qglEnd(); } } } /* * GL_BlendLightMaps Blend lightmaps to their respective surfaces for cards not supporting multitexture. */ void GL_BlendLightmaps(void){ int i; msurface_t *surf; // don't bother if we're set to fullbright if(r_fullbright->value) return; if(!r_worldmodel->lightdata) return; // don't bother writing Z qglDepthMask(0); // set appropriate blending mode if(!gl_lightmap->value){ qglBlendFunc(GL_ZERO, GL_SRC_COLOR); qglEnable(GL_BLEND); } if(currentmodel == r_worldmodel) c_visible_lightmaps = 0; // render lightmaps for(i = 1; i < MAX_LIGHTMAPS; i++){ if(gl_lms.lightmap_surfaces[i]){ if(currentmodel == r_worldmodel) c_visible_lightmaps++; GL_Bind(gl_state.lightmap_textures + i); for(surf = gl_lms.lightmap_surfaces[i]; surf != 0; surf = surf->lightmapchain){ if(surf->polys) DrawGLPolyChain(surf->polys, 0, 0); } } } // restore blending state if(!gl_lightmap->value){ qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); qglDisable(GL_BLEND); } qglDepthMask(1); } /* GL_RenderBrushPoly */ void GL_RenderBrushPoly(msurface_t *fa){ image_t *image; c_brush_polys++; image = GL_TextureAnimation(fa->texinfo); if(fa->flags & SURF_DRAWTURB){ GL_Bind(image->texnum); // warp texture, no lightmaps GL_TexEnv(GL_MODULATE); qglColor4f(gl_state.inverse_intensity, gl_state.inverse_intensity, gl_state.inverse_intensity, 1.0F); EmitWaterPolys(fa); GL_TexEnv(GL_REPLACE); return; } GL_Bind(image->texnum); GL_TexEnv(GL_REPLACE); if(fa->texinfo->flags & SURF_FLOWING) DrawGLFlowingPoly(fa); else DrawGLPoly(fa->polys); fa->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum]; gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa; } /* GL_DrawAlphaSurfaces Draw water surfaces and windows. The BSP tree is walled front to back, so unwinding the chain of alpha_surfaces will draw back to front, giving proper ordering. */ void GL_DrawAlphaSurfaces(void){ msurface_t *s; float intens; // go back to the world matrix qglLoadMatrixf(r_world_matrix); qglEnable(GL_BLEND); GL_TexEnv(GL_MODULATE); // the textures are prescaled up for a better lighting range, // so scale it back down intens = gl_state.inverse_intensity; for(s = r_alpha_surfaces; s; s = s->texturechain){ GL_Bind(s->texinfo->image->texnum); c_brush_polys++; if(s->texinfo->flags & SURF_TRANS33) qglColor4f(intens, intens, intens, 0.33); else if(s->texinfo->flags & SURF_TRANS66) qglColor4f(intens, intens, intens, 0.66); else qglColor4f(intens, intens, intens, 1); if(s->flags & SURF_DRAWTURB) EmitWaterPolys(s); else if(s->texinfo->flags & SURF_FLOWING) DrawGLFlowingPoly(s); else DrawGLPoly(s->polys); } GL_TexEnv(GL_REPLACE); qglColor4ubv(color_white); qglDisable(GL_BLEND); r_alpha_surfaces = NULL; } /* DrawTextureChains */ void DrawTextureChains(void){ int i; msurface_t *s; image_t *image; c_visible_textures = 0; if(!qglActiveTextureARB){ for(i = 0, image = gltextures; i < numgltextures; i++, image++){ if(!image->registration_sequence) continue; s = image->texturechain; if(!s) continue; c_visible_textures++; for(; s; s = s->texturechain) GL_RenderBrushPoly(s); image->texturechain = NULL; } } else { for(i = 0, image = gltextures; i < numgltextures; i++, image++){ if(!image->registration_sequence) continue; if(!image->texturechain) continue; c_visible_textures++; for(s = image->texturechain; s; s = s->texturechain){ if(!(s->flags & SURF_DRAWTURB)) GL_RenderBrushPoly(s); } } for(i = 0, image = gltextures; i < numgltextures; i++, image++){ if(!image->registration_sequence) continue; s = image->texturechain; if(!s) continue; for(; s; s = s->texturechain){ if(s->flags & SURF_DRAWTURB) GL_RenderBrushPoly(s); } image->texturechain = NULL; } } GL_TexEnv(GL_REPLACE); } static void GL_RenderLightmappedPoly(msurface_t *surf){ int i, nv = surf->polys->numverts; float *v; image_t *image = GL_TextureAnimation(surf->texinfo); unsigned lmtex = surf->lightmaptexturenum; glpoly_t *p; c_brush_polys++; GL_MBind(QGL_TEXTURE0, image->texnum); GL_MBind(QGL_TEXTURE1, gl_state.lightmap_textures + lmtex); if(surf->texinfo->flags & SURF_FLOWING){ float scroll; scroll = -64 *((r_viewdef.time / 40.0) -(int)(r_viewdef.time / 40.0)); if(scroll == 0.0) scroll = -64.0; for(p = surf->polys; p; p = p->chain){ v = p->verts[0]; qglBegin(GL_POLYGON); for(i = 0; i < nv; i++, v += VERTEXSIZE){ qglMultiTexCoord2fARB(QGL_TEXTURE0,(v[3] + scroll), v[4]); qglMultiTexCoord2fARB(QGL_TEXTURE1, v[5], v[6]); qglVertex3fv(v); } qglEnd(); } } else { for(p = surf->polys; p; p = p->chain){ v = p->verts[0]; qglBegin(GL_POLYGON); for(i = 0; i < nv; i++, v += VERTEXSIZE){ qglMultiTexCoord2fARB(QGL_TEXTURE0, v[3], v[4]); qglMultiTexCoord2fARB(QGL_TEXTURE1, v[5], v[6]); qglVertex3fv(v); } qglEnd(); } } } /* GL_DrawInlineBModel */ void GL_DrawInlineBModel(void){ int i; cplane_t *pplane; float dot; msurface_t *psurf; psurf = ¤tmodel->surfaces[currentmodel->firstmodelsurface]; if(currententity->flags & RF_TRANSLUCENT){ qglEnable(GL_BLEND); color_white[3] = 64; qglColor4ubv(color_white); color_white[3] = 255; GL_TexEnv(GL_MODULATE); } // draw texture for(i = 0; i < currentmodel->nummodelsurfaces; i++, psurf++){ // find which side of the node we are on pplane = psurf->plane; dot = DotProduct(modelorg, pplane->normal) - pplane->dist; // draw the polygon if(((psurf->flags & SURF_PLANEBACK) &&(dot < -BACKFACE_EPSILON)) || (!(psurf->flags & SURF_PLANEBACK) &&(dot > BACKFACE_EPSILON))){ if(psurf->texinfo->flags &(SURF_TRANS33 | SURF_TRANS66)){ // add to the translucent chain psurf->texturechain = r_alpha_surfaces; r_alpha_surfaces = psurf; } else if(qglMultiTexCoord2fARB && !(psurf->flags & SURF_DRAWTURB)){ GL_RenderLightmappedPoly(psurf); } else { GL_EnableMultitexture(false); GL_RenderBrushPoly(psurf); GL_EnableMultitexture(true); } } } if(!(currententity->flags & RF_TRANSLUCENT)){ if(!qglMultiTexCoord2fARB) GL_BlendLightmaps(); } else { qglDisable(GL_BLEND); qglColor4ubv(color_white); GL_TexEnv(GL_REPLACE); } } /* GL_DrawBrushModel */ void GL_DrawBrushModel(entity_t *e){ vec3_t mins, maxs; int i; qboolean rotated; if(currentmodel->nummodelsurfaces == 0) return; currententity = e; gl_state.currenttextures[0] = gl_state.currenttextures[1] = -1; if(e->angles[0] || e->angles[1] || e->angles[2]){ rotated = true; for(i = 0; i < 3; i++){ mins[i] = e->origin[i] - currentmodel->radius; maxs[i] = e->origin[i] + currentmodel->radius; } } else { rotated = false; VectorAdd(e->origin, currentmodel->mins, mins); VectorAdd(e->origin, currentmodel->maxs, maxs); } if(GL_CullBox(mins, maxs)) return; qglColor3ubv(color_white); memset(gl_lms.lightmap_surfaces, 0, sizeof(gl_lms.lightmap_surfaces)); VectorSubtract(r_viewdef.vieworg, e->origin, modelorg); if(rotated){ vec3_t temp; vec3_t forward, right, up; VectorCopy(modelorg, temp); AngleVectors(e->angles, forward, right, up); modelorg[0] = DotProduct(temp, forward); modelorg[1] = -DotProduct(temp, right); modelorg[2] = DotProduct(temp, up); } qglPushMatrix(); e->angles[0] = -e->angles[0]; // stupid quake bug e->angles[2] = -e->angles[2]; // stupid quake bug GL_RotateForEntity(e); e->angles[0] = -e->angles[0]; // stupid quake bug e->angles[2] = -e->angles[2]; // stupid quake bug GL_EnableMultitexture(true); GL_SelectTexture(QGL_TEXTURE0); GL_TexEnv(GL_REPLACE); GL_SelectTexture(QGL_TEXTURE1); GL_TexEnv(GL_MODULATE); GL_DrawInlineBModel(); GL_EnableMultitexture(false); qglPopMatrix(); } /* WORLD MODEL */ /* GL_RecursiveWorldNode */ void GL_RecursiveWorldNode(mnode_t *node){ int c, side, sidebit; cplane_t *plane; msurface_t *surf, **mark; mleaf_t *pleaf; float dot; image_t *image; if(node->contents == CONTENTS_SOLID) return; // solid if(node->visframe != r_visframecount) return; if(GL_CullBox(node->minmaxs, node->minmaxs + 3)) return; // if a leaf node, draw stuff if(node->contents != -1){ pleaf = (mleaf_t *)node; // check for door connected areas if(r_viewdef.areabits){ if(!(r_viewdef.areabits[pleaf->area >> 3] &(1 <<(pleaf->area&7)))) return; // not visible } mark = pleaf->firstmarksurface; c = pleaf->nummarksurfaces; if(c){ do { (*mark)->visframe = r_visframecount; mark++; } while(--c); } return; } // node is just a decision point, so go down the apropriate sides // find which side of the node we are on plane = node->plane; switch(plane->type){ case PLANE_X: dot = modelorg[0] - plane->dist; break; case PLANE_Y: dot = modelorg[1] - plane->dist; break; case PLANE_Z: dot = modelorg[2] - plane->dist; break; default: dot = DotProduct(modelorg, plane->normal) - plane->dist; break; } if(dot >= 0){ side = 0; sidebit = 0; } else { side = 1; sidebit = SURF_PLANEBACK; } // recurse down the children, front side first GL_RecursiveWorldNode(node->children[side]); // draw stuff for(c = node->numsurfaces, surf = r_worldmodel->surfaces + node->firstsurface; c; c--, surf++){ if(surf->visframe != r_visframecount) continue; if((surf->flags & SURF_PLANEBACK) != sidebit) continue; // wrong side if(surf->texinfo->flags & SURF_SKY){ // just adds to visible sky bounds GL_AddSkySurface(surf); } else if(surf->texinfo->flags &(SURF_TRANS33 | SURF_TRANS66)){ // add to the translucent chain surf->texturechain = r_alpha_surfaces; r_alpha_surfaces = surf; } else { if(qglMultiTexCoord2fARB && !(surf->flags & SURF_DRAWTURB)){ GL_RenderLightmappedPoly(surf); } else { // the polygon is visible, so add it to the texture // sorted chain // FIXME: this is a hack for animation image = GL_TextureAnimation(surf->texinfo); surf->texturechain = image->texturechain; image->texturechain = surf; } } } // recurse down the back side GL_RecursiveWorldNode(node->children[!side]); } /* GL_DrawWorld */ void GL_DrawWorld(void){ entity_t ent; if(r_viewdef.rdflags & RDF_NOWORLDMODEL) return; currentmodel = r_worldmodel; VectorCopy(r_viewdef.vieworg, modelorg); // auto cycle the world frame for texture animation memset(&ent, 0, sizeof(ent)); ent.frame =(int)(r_viewdef.time * 2); currententity = &ent; gl_state.currenttextures[0] = gl_state.currenttextures[1] = -1; qglColor3ubv(color_white); memset(gl_lms.lightmap_surfaces, 0, sizeof(gl_lms.lightmap_surfaces)); GL_ClearSkyBox(); if(qglMultiTexCoord2fARB){ GL_EnableMultitexture(true); GL_SelectTexture(QGL_TEXTURE0); GL_TexEnv(GL_REPLACE); GL_SelectTexture(QGL_TEXTURE1); if(gl_lightmap->value) GL_TexEnv(GL_REPLACE); else GL_TexEnv(GL_MODULATE); GL_RecursiveWorldNode(r_worldmodel->nodes); GL_EnableMultitexture(false); } else { GL_RecursiveWorldNode(r_worldmodel->nodes); } // nothing should happen here if multitexture is enabled DrawTextureChains(); GL_BlendLightmaps(); GL_DrawSkyBox(); } /* GL_MarkLeafs Mark the leafs and nodes that are in the PVS for the current cluster */ void GL_MarkLeafs(void){ byte *vis; byte fatvis[MAX_MAP_LEAFS / 8]; mnode_t *node; int i, c; mleaf_t *leaf; int cluster; if(r_oldviewcluster == r_viewcluster && r_oldviewcluster2 == r_viewcluster2 && !r_novis->value && r_viewcluster != -1) return; // development aid to let you run around and see exactly where // the pvs ends if(gl_lockpvs->value) return; r_visframecount++; r_oldviewcluster = r_viewcluster; r_oldviewcluster2 = r_viewcluster2; if(r_novis->value || r_viewcluster == -1 || !r_worldmodel->vis){ // mark everything for(i = 0; i < r_worldmodel->numleafs; i++) r_worldmodel->leafs[i].visframe = r_visframecount; for(i = 0; i < r_worldmodel->numnodes; i++) r_worldmodel->nodes[i].visframe = r_visframecount; return; } vis = Mod_ClusterPVS(r_viewcluster, r_worldmodel); // may have to combine two clusters because of solid water boundaries if(r_viewcluster2 != r_viewcluster){ memcpy(fatvis, vis,(r_worldmodel->numleafs + 7) / 8); vis = Mod_ClusterPVS(r_viewcluster2, r_worldmodel); c =(r_worldmodel->numleafs + 31) / 32; for(i = 0; i < c; i++) ((int *)fatvis)[i] |=((int *)vis)[i]; vis = fatvis; } for(i = 0, leaf = r_worldmodel->leafs; i < r_worldmodel->numleafs; i++, leaf++){ cluster = leaf->cluster; if(cluster == -1) continue; if(vis[cluster >> 3] &(1 <<(cluster&7))){ node =(mnode_t *)leaf; do { if(node->visframe == r_visframecount) break; node->visframe = r_visframecount; node = node->parent; } while(node); } } } /* LIGHTMAP ALLOCATION */ static void LM_InitBlock(void){ memset(gl_lms.allocated, 0, sizeof(gl_lms.allocated)); } static void LM_UploadBlock(){ int texture = gl_lms.current_lightmap_texture; GL_Bind(gl_state.lightmap_textures + texture); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); qglTexImage2D(GL_TEXTURE_2D, 0, gl_lms.internal_format, BLOCK_WIDTH, BLOCK_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer); if(++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS) Com_Error(ERR_DROP, "LM_UploadBlock() - MAX_LIGHTMAPS exceeded\n"); } // returns a texture number and the position inside it static qboolean LM_AllocBlock(int w, int h, int *x, int *y){ int i, j; int best, best2; best = BLOCK_HEIGHT; for(i = 0; i < BLOCK_WIDTH - w; i++){ best2 = 0; for(j = 0; j < w; j++){ if(gl_lms.allocated[i + j] >= best) break; if(gl_lms.allocated[i + j] > best2) best2 = gl_lms.allocated[i + j]; } if(j == w){ // this is a valid spot *x = i; *y = best = best2; } } if(best + h > BLOCK_HEIGHT) return false; for(i = 0; i < w; i++) gl_lms.allocated[*x + i] = best + h; return true; } /* GL_BuildPolygonFromSurface */ void GL_BuildPolygonFromSurface(msurface_t *fa){ int i, lindex, lnumverts; medge_t *pedges, *r_pedge; int vertpage; float *vec; float s, t; glpoly_t *poly; vec3_t total; // reconstruct the polygon pedges = currentmodel->edges; lnumverts = fa->numedges; vertpage = 0; VectorClear(total); // // draw texture // poly = Hunk_Alloc(sizeof(glpoly_t) +(lnumverts - 4) * VERTEXSIZE * sizeof(float)); poly->next = fa->polys; fa->polys = poly; poly->numverts = lnumverts; for(i = 0; i < lnumverts; i++){ lindex = currentmodel->surfedges[fa->firstedge + i]; if(lindex > 0){ r_pedge = &pedges[lindex]; vec = currentmodel->vertexes[r_pedge->v[0]].position; } else { r_pedge = &pedges[ -lindex]; vec = currentmodel->vertexes[r_pedge->v[1]].position; } s = DotProduct(vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3]; s /= fa->texinfo->image->width; t = DotProduct(vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3]; t /= fa->texinfo->image->height; VectorAdd(total, vec, total); VectorCopy(vec, poly->verts[i]); poly->verts[i][3] = s; poly->verts[i][4] = t; // // lightmap texture coordinates // s = DotProduct(vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3]; s -= fa->texturemins[0]; s += fa->light_s * 16; s += 8; s /= BLOCK_WIDTH * 16; //fa->texinfo->texture->width; t = DotProduct(vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3]; t -= fa->texturemins[1]; t += fa->light_t * 16; t += 8; t /= BLOCK_HEIGHT * 16; //fa->texinfo->texture->height; poly->verts[i][5] = s; poly->verts[i][6] = t; } poly->numverts = lnumverts; } /* GL_CreateSurfaceLightmap */ void GL_CreateSurfaceLightmap(msurface_t *surf){ int smax, tmax; byte *base; if(surf->flags &(SURF_DRAWSKY | SURF_DRAWTURB)) return; smax = (surf->extents[0] >> 4) + 1; tmax = (surf->extents[1] >> 4) + 1; if(!LM_AllocBlock(smax, tmax, &surf->light_s, &surf->light_t)){ LM_UploadBlock(); LM_InitBlock(); if(!LM_AllocBlock(smax, tmax, &surf->light_s, &surf->light_t)){ Com_Error(ERR_DROP, "Consecutive calls to LM_AllocBlock(%d,%d) failed\n", smax, tmax); } } surf->lightmaptexturenum = gl_lms.current_lightmap_texture; base = gl_lms.lightmap_buffer; base += (surf->light_t * BLOCK_WIDTH + surf->light_s) * LIGHTMAP_BYTES; GL_BuildLightMap(surf, base, BLOCK_WIDTH * LIGHTMAP_BYTES); } /* GL_BeginBuildingLightmaps */ void GL_BeginBuildingLightmaps(model_t *m){ memset(gl_lms.allocated, 0, sizeof(gl_lms.allocated)); GL_EnableMultitexture(true); GL_SelectTexture(QGL_TEXTURE1); if(!gl_state.lightmap_textures){ gl_state.lightmap_textures = TEXNUM_LIGHTMAPS; } gl_lms.current_lightmap_texture = 1; gl_lms.internal_format = gl_tex_solid_format; } /* GL_EndBuildingLightmaps */ void GL_EndBuildingLightmaps(void){ LM_UploadBlock(); GL_EnableMultitexture(false); }