/* Copyright (C) 1996-2001 Id Software, Inc. Copyright (C) 2002-2009 John Fitzgibbons and others Copyright (C) 2007-2008 Kristian Duske Copyright (C) 2010-2014 QuakeSpasm developers 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. */ //gl_sky.c #include "q_defs.h" #define MAX_CLIP_VERTS 64 float Fog_GetDensity(void); float *Fog_GetColor(void); extern qmodel_t *loadmodel; extern int32_t rs_skypolys; //for r_speeds readout extern int32_t rs_skypasses; //for r_speeds readout float skyflatcolor[3]; float skymins[2][6], skymaxs[2][6]; char skybox_name[32] = ""; //name of current skybox, or "" if no skybox gltexture_t *skybox_textures[6]; gltexture_t *solidskytexture, *alphaskytexture; extern cvar_t gl_farclip; cvar_t r_fastsky = {"r_fastsky", "0", CVAR_NONE}; cvar_t r_sky_quality = {"r_sky_quality", "12", CVAR_NONE}; cvar_t r_skyalpha = {"r_skyalpha", "1", CVAR_NONE}; cvar_t r_skyfog = {"r_skyfog", "0.5", CVAR_NONE}; int32_t skytexorder[6] = {0, 2, 1, 3, 4, 5}; //for skybox vec3_t skyclip[6] = { {1, 1, 0}, {1, -1, 0}, {0, -1, 1}, {0, 1, 1}, {1, 0, 1}, {-1, 0, 1} }; int32_t st_to_vec[6][3] = { {3, -1, 2}, {-3, 1, 2}, {1, 3, 2}, {-1, -3, 2}, {-2, -1, 3}, // straight up {2, -1, -3} // straight down }; int32_t vec_to_st[6][3] = { {-2, 3, 1}, {2, 3, -1}, {1, 3, 2}, {-1, 3, -2}, {-2, -1, 3}, {-2, 1, -3} }; float skyfog; // ericw //============================================================================== // // INIT // //============================================================================== /* ============= Sky_LoadTexture A sky texture is 256*128, with the left side being a masked overlay ============== */ void Sky_LoadTexture(texture_t *mt) { char texturename[64]; int32_t i, j, p, r, g, b, count; byte *src; static byte front_data[128 * 128]; //FIXME: Hunk_Alloc static byte back_data[128 * 128]; //FIXME: Hunk_Alloc unsigned *rgba; src = (byte *)mt + mt->offsets[0]; // extract back layer and upload for(i = 0 ; i < 128 ; i++) for(j = 0 ; j < 128 ; j++) back_data[(i * 128) + j] = src[i * 256 + j + 128]; q_snprintf(texturename, sizeof(texturename), "%s:%s_back", loadmodel->name, mt->name); solidskytexture = TexMgr_LoadImage(loadmodel, texturename, 128, 128, SRC_INDEXED, back_data, "", (src_offset_t)back_data, TEXPREF_NONE); // extract front layer and upload for(i = 0 ; i < 128 ; i++) for(j = 0 ; j < 128 ; j++) { front_data[(i * 128) + j] = src[i * 256 + j]; if(front_data[(i * 128) + j] == 0) front_data[(i * 128) + j] = 255; } q_snprintf(texturename, sizeof(texturename), "%s:%s_front", loadmodel->name, mt->name); alphaskytexture = TexMgr_LoadImage(loadmodel, texturename, 128, 128, SRC_INDEXED, front_data, "", (src_offset_t)front_data, TEXPREF_ALPHA); // calculate r_fastsky color based on average of all opaque foreground colors r = g = b = count = 0; for(i = 0 ; i < 128 ; i++) for(j = 0 ; j < 128 ; j++) { p = src[i * 256 + j]; if(p != 0) { rgba = &d_8to24table[p]; r += ((byte *)rgba)[0]; g += ((byte *)rgba)[1]; b += ((byte *)rgba)[2]; count++; } } skyflatcolor[0] = (float)r / (count * 255); skyflatcolor[1] = (float)g / (count * 255); skyflatcolor[2] = (float)b / (count * 255); } /* ================== Sky_LoadSkyBox ================== */ const char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; void Sky_LoadSkyBox(const char *name) { int32_t i, mark, width, height; char filename[MAX_OSPATH]; byte *data; bool nonefound = true; if(strcmp(skybox_name, name) == 0) return; //no change //purge old textures for(i = 0; i < 6; i++) { if(skybox_textures[i] && skybox_textures[i] != notexture) TexMgr_FreeTexture(skybox_textures[i]); skybox_textures[i] = NULL; } //turn off skybox if sky is set to "" if(name[0] == 0) { skybox_name[0] = 0; return; } //load textures for(i = 0; i < 6; i++) { mark = Hunk_LowMark(); q_snprintf(filename, sizeof(filename), "gfx/env/%s%s", name, suf[i]); data = Image_LoadImage(filename, &width, &height); if(data) { skybox_textures[i] = TexMgr_LoadImage(cl.worldmodel, filename, width, height, SRC_RGBA, data, filename, 0, TEXPREF_NONE); nonefound = false; } else { Con_Printf("Couldn't load %s\n", filename); skybox_textures[i] = notexture; } Hunk_FreeToLowMark(mark); } if(nonefound) // go back to scrolling sky if skybox is totally missing { for(i = 0; i < 6; i++) { if(skybox_textures[i] && skybox_textures[i] != notexture) TexMgr_FreeTexture(skybox_textures[i]); skybox_textures[i] = NULL; } skybox_name[0] = 0; return; } strcpy(skybox_name, name); } /* ================= Sky_NewMap ================= */ void Sky_NewMap(void) { char key[128], value[4096]; const char *data; int32_t i; // // initially no sky // skybox_name[0] = 0; for(i = 0; i < 6; i++) skybox_textures[i] = NULL; skyfog = r_skyfog.value; // // read worldspawn (this is so ugly, and shouldn't it be done on the server?) // data = cl.worldmodel->entities; if(!data) return; //FIXME: how could this possibly ever happen? -- if there's no // worldspawn then the sever wouldn't send the loadmap message to the client data = COM_Parse(data); if(!data) //should never happen return; // error if(com_token[0] != '{') //should never happen return; // error while(1) { data = COM_Parse(data); if(!data) return; // error if(com_token[0] == '}') break; // end of worldspawn if(com_token[0] == '_') strcpy(key, com_token + 1); else strcpy(key, com_token); while(key[strlen(key) - 1] == ' ') // remove trailing spaces key[strlen(key) - 1] = 0; data = COM_Parse(data); if(!data) return; // error strcpy(value, com_token); if(!strcmp("sky", key)) Sky_LoadSkyBox(value); if(!strcmp("skyfog", key)) skyfog = atof(value); #if 1 //also accept non-standard keys else if(!strcmp("skyname", key)) //half-life Sky_LoadSkyBox(value); else if(!strcmp("qlsky", key)) //quake lives Sky_LoadSkyBox(value); #endif } } /* ================= Sky_SkyCommand_f ================= */ void Sky_SkyCommand_f(void) { switch(Cmd_Argc()) { case 1: Con_Printf("\"sky\" is \"%s\"\n", skybox_name); break; case 2: Sky_LoadSkyBox(Cmd_Argv(1)); break; default: Con_Printf("usage: sky \n"); } } /* ==================== R_SetSkyfog_f -- ericw ==================== */ static void R_SetSkyfog_f(cvar_t *var) { // clear any skyfog setting from worldspawn skyfog = var->value; } /* ============= Sky_Init ============= */ void Sky_Init(void) { int32_t i; Cvar_RegisterVariable(&r_fastsky); Cvar_RegisterVariable(&r_sky_quality); Cvar_RegisterVariable(&r_skyalpha); Cvar_RegisterVariable(&r_skyfog); Cvar_SetCallback(&r_skyfog, R_SetSkyfog_f); Cmd_AddCommand("sky", Sky_SkyCommand_f); for(i = 0; i < 6; i++) skybox_textures[i] = NULL; } //============================================================================== // // PROCESS SKY SURFS // //============================================================================== /* ================= Sky_ProjectPoly update sky bounds ================= */ void Sky_ProjectPoly(int32_t nump, vec3_t vecs) { int32_t i, j; vec3_t v, av; float s, t, dv; int32_t axis; float *vp; // decide which face it maps to VectorCopy(vec3_origin, v); for(i = 0, vp = vecs ; i < nump ; i++, vp += 3) { VectorAdd(vp, v, v); } av[0] = fabs(v[0]); av[1] = fabs(v[1]); av[2] = fabs(v[2]); if(av[0] > av[1] && av[0] > av[2]) { if(v[0] < 0) axis = 1; else axis = 0; } else if(av[1] > av[2] && av[1] > av[0]) { if(v[1] < 0) axis = 3; else axis = 2; } else { if(v[2] < 0) axis = 5; else axis = 4; } // project new texture coords for(i = 0 ; i < nump ; i++, vecs += 3) { j = vec_to_st[axis][2]; if(j > 0) dv = vecs[j - 1]; else dv = -vecs[-j - 1]; j = vec_to_st[axis][0]; if(j < 0) s = -vecs[-j - 1] / dv; else s = vecs[j - 1] / dv; j = vec_to_st[axis][1]; if(j < 0) t = -vecs[-j - 1] / dv; else t = vecs[j - 1] / dv; if(s < skymins[0][axis]) skymins[0][axis] = s; if(t < skymins[1][axis]) skymins[1][axis] = t; if(s > skymaxs[0][axis]) skymaxs[0][axis] = s; if(t > skymaxs[1][axis]) skymaxs[1][axis] = t; } } /* ================= Sky_ClipPoly ================= */ void Sky_ClipPoly(int32_t nump, vec3_t vecs, int32_t stage) { float *norm; float *v; bool front, back; float d, e; float dists[MAX_CLIP_VERTS]; int32_t sides[MAX_CLIP_VERTS]; vec3_t newv[2][MAX_CLIP_VERTS]; int32_t newc[2]; int32_t i, j; if(nump > MAX_CLIP_VERTS - 2) Sys_Error("Sky_ClipPoly: MAX_CLIP_VERTS"); if(stage == 6) // fully clipped { Sky_ProjectPoly(nump, vecs); return; } front = back = false; norm = skyclip[stage]; for(i = 0, v = vecs ; i < nump ; i++, v += 3) { d = DotProduct(v, norm); 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 || !back) { // not clipped Sky_ClipPoly(nump, vecs, stage + 1); return; } // clip it sides[i] = sides[0]; dists[i] = dists[0]; VectorCopy(vecs, (vecs + (i * 3))); newc[0] = newc[1] = 0; for(i = 0, v = vecs ; i < nump ; i++, v += 3) { switch(sides[i]) { case SIDE_FRONT: VectorCopy(v, newv[0][newc[0]]); newc[0]++; break; case SIDE_BACK: VectorCopy(v, newv[1][newc[1]]); newc[1]++; break; case SIDE_ON: VectorCopy(v, newv[0][newc[0]]); newc[0]++; VectorCopy(v, newv[1][newc[1]]); newc[1]++; 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++) { e = v[j] + d * (v[j + 3] - v[j]); newv[0][newc[0]][j] = e; newv[1][newc[1]][j] = e; } newc[0]++; newc[1]++; } // continue Sky_ClipPoly(newc[0], newv[0][0], stage + 1); Sky_ClipPoly(newc[1], newv[1][0], stage + 1); } /* ================ Sky_ProcessPoly ================ */ void Sky_ProcessPoly(glpoly_t *p) { int32_t i; vec3_t verts[MAX_CLIP_VERTS]; //draw it DrawGLPoly(p); rs_brushpasses++; //update sky bounds if(!r_fastsky.value) { for(i = 0 ; i < p->numverts ; i++) VectorSubtract(p->verts[i], r_origin, verts[i]); Sky_ClipPoly(p->numverts, verts[0], 0); } } /* ================ Sky_ProcessTextureChains -- handles sky polys in world model ================ */ void Sky_ProcessTextureChains(void) { int32_t i; msurface_t *s; texture_t *t; if(!r_drawworld_cheatsafe) return; for(i = 0 ; i < cl.worldmodel->numtextures ; i++) { t = cl.worldmodel->textures[i]; if(!t || !t->texturechains[chain_world] || !(t->texturechains[chain_world]->flags & SURF_DRAWSKY)) continue; for(s = t->texturechains[chain_world]; s; s = s->texturechain) if(!s->culled) Sky_ProcessPoly(s->polys); } } /* ================ Sky_ProcessEntities -- handles sky polys on brush models ================ */ void Sky_ProcessEntities(void) { entity_t *e; msurface_t *s; glpoly_t *p; int32_t i, j, k, mark; float dot; bool rotated; vec3_t temp, forward, right, up; if(!r_drawentities.value) return; for(i = 0 ; i < cl_numvisedicts ; i++) { e = cl_visedicts[i]; if(e->model->type != mod_brush) continue; if(R_CullModelForEntity(e)) continue; if(e->alpha == ENTALPHA_ZERO) continue; VectorSubtract(r_refdef.vieworg, e->origin, modelorg); if(e->angles[0] || e->angles[1] || e->angles[2]) { rotated = true; AngleVectors(e->angles, forward, right, up); VectorCopy(modelorg, temp); modelorg[0] = DotProduct(temp, forward); modelorg[1] = -DotProduct(temp, right); modelorg[2] = DotProduct(temp, up); } else rotated = false; s = &e->model->surfaces[e->model->firstmodelsurface]; for(j = 0 ; j < e->model->nummodelsurfaces ; j++, s++) { if(s->flags & SURF_DRAWSKY) { dot = DotProduct(modelorg, s->plane->normal) - s->plane->dist; if(((s->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || (!(s->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) { //copy the polygon and translate manually, since Sky_ProcessPoly needs it to be in world space mark = Hunk_LowMark(); p = (glpoly_t *) Hunk_Alloc(sizeof(*s->polys)); //FIXME: don't allocate for each poly p->numverts = s->polys->numverts; for(k = 0; k < p->numverts; k++) { if(rotated) { p->verts[k][0] = e->origin[0] + s->polys->verts[k][0] * forward[0] - s->polys->verts[k][1] * right[0] + s->polys->verts[k][2] * up[0]; p->verts[k][1] = e->origin[1] + s->polys->verts[k][0] * forward[1] - s->polys->verts[k][1] * right[1] + s->polys->verts[k][2] * up[1]; p->verts[k][2] = e->origin[2] + s->polys->verts[k][0] * forward[2] - s->polys->verts[k][1] * right[2] + s->polys->verts[k][2] * up[2]; } else VectorAdd(s->polys->verts[k], e->origin, p->verts[k]); } Sky_ProcessPoly(p); Hunk_FreeToLowMark(mark); } } } } } //============================================================================== // // RENDER SKYBOX // //============================================================================== /* ============== Sky_EmitSkyBoxVertex ============== */ void Sky_EmitSkyBoxVertex(float s, float t, int32_t axis) { vec3_t v, b; int32_t j, k; float w, h; b[0] = s * gl_farclip.value / sqrt(3.0); b[1] = t * gl_farclip.value / sqrt(3.0); b[2] = gl_farclip.value / sqrt(3.0); for(j = 0 ; j < 3 ; j++) { k = st_to_vec[axis][j]; if(k < 0) v[j] = -b[-k - 1]; else v[j] = b[k - 1]; v[j] += r_origin[j]; } // convert from range [-1,1] to [0,1] s = (s + 1) * 0.5; t = (t + 1) * 0.5; // avoid bilerp seam w = skybox_textures[skytexorder[axis]]->width; h = skybox_textures[skytexorder[axis]]->height; s = s * (w - 1) / w + 0.5 / w; t = t * (h - 1) / h + 0.5 / h; t = 1.0 - t; glTexCoord2f(s, t); glVertex3fv(v); } /* ============== Sky_DrawSkyBox FIXME: eliminate cracks by adding an extra vert on tjuncs ============== */ void Sky_DrawSkyBox(void) { int32_t i; for(i = 0 ; i < 6 ; i++) { if(skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i]) continue; GL_Bind(skybox_textures[skytexorder[i]]); #if 1 //FIXME: this is to avoid tjunctions until i can do it the right way skymins[0][i] = -1; skymins[1][i] = -1; skymaxs[0][i] = 1; skymaxs[1][i] = 1; #endif glBegin(GL_QUADS); Sky_EmitSkyBoxVertex(skymins[0][i], skymins[1][i], i); Sky_EmitSkyBoxVertex(skymins[0][i], skymaxs[1][i], i); Sky_EmitSkyBoxVertex(skymaxs[0][i], skymaxs[1][i], i); Sky_EmitSkyBoxVertex(skymaxs[0][i], skymins[1][i], i); glEnd(); rs_skypolys++; rs_skypasses++; if(Fog_GetDensity() > 0 && skyfog > 0) { float *c; c = Fog_GetColor(); glEnable(GL_BLEND); glDisable(GL_TEXTURE_2D); glColor4f(c[0], c[1], c[2], CLAMP(0.0, skyfog, 1.0)); glBegin(GL_QUADS); Sky_EmitSkyBoxVertex(skymins[0][i], skymins[1][i], i); Sky_EmitSkyBoxVertex(skymins[0][i], skymaxs[1][i], i); Sky_EmitSkyBoxVertex(skymaxs[0][i], skymaxs[1][i], i); Sky_EmitSkyBoxVertex(skymaxs[0][i], skymins[1][i], i); glEnd(); glColor3f(1, 1, 1); glEnable(GL_TEXTURE_2D); glDisable(GL_BLEND); rs_skypasses++; } } } //============================================================================== // // RENDER CLOUDS // //============================================================================== /* ============== Sky_SetBoxVert ============== */ void Sky_SetBoxVert(float s, float t, int32_t axis, vec3_t v) { vec3_t b; int32_t j, k; b[0] = s * gl_farclip.value / sqrt(3.0); b[1] = t * gl_farclip.value / sqrt(3.0); b[2] = gl_farclip.value / sqrt(3.0); for(j = 0 ; j < 3 ; j++) { k = st_to_vec[axis][j]; if(k < 0) v[j] = -b[-k - 1]; else v[j] = b[k - 1]; v[j] += r_origin[j]; } } /* ============= Sky_GetTexCoord ============= */ void Sky_GetTexCoord(vec3_t v, float speed, float *s, float *t) { vec3_t dir; float length, scroll; VectorSubtract(v, r_origin, dir); dir[2] *= 3; // flatten the sphere length = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]; length = sqrt(length); length = 6 * 63 / length; scroll = cl.time * speed; scroll -= (int32_t)scroll & ~127; *s = (scroll + dir[0] * length) * (1.0 / 128); *t = (scroll + dir[1] * length) * (1.0 / 128); } /* =============== Sky_DrawFaceQuad =============== */ void Sky_DrawFaceQuad(glpoly_t *p) { float s, t; float *v; int32_t i; if(gl_mtexable && r_skyalpha.value >= 1.0) { GL_Bind(solidskytexture); GL_EnableMultitexture(); GL_Bind(alphaskytexture); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glBegin(GL_QUADS); for(i = 0, v = p->verts[0] ; i < 4 ; i++, v += VERTEXSIZE) { Sky_GetTexCoord(v, 8, &s, &t); GL_MTexCoord2fFunc(GL_TEXTURE0_ARB, s, t); Sky_GetTexCoord(v, 16, &s, &t); GL_MTexCoord2fFunc(GL_TEXTURE1_ARB, s, t); glVertex3fv(v); } glEnd(); GL_DisableMultitexture(); rs_skypolys++; rs_skypasses++; } else { GL_Bind(solidskytexture); if(r_skyalpha.value < 1.0) glColor3f(1, 1, 1); glBegin(GL_QUADS); for(i = 0, v = p->verts[0] ; i < 4 ; i++, v += VERTEXSIZE) { Sky_GetTexCoord(v, 8, &s, &t); glTexCoord2f(s, t); glVertex3fv(v); } glEnd(); GL_Bind(alphaskytexture); glEnable(GL_BLEND); if(r_skyalpha.value < 1.0) glColor4f(1, 1, 1, r_skyalpha.value); glBegin(GL_QUADS); for(i = 0, v = p->verts[0] ; i < 4 ; i++, v += VERTEXSIZE) { Sky_GetTexCoord(v, 16, &s, &t); glTexCoord2f(s, t); glVertex3fv(v); } glEnd(); glDisable(GL_BLEND); rs_skypolys++; rs_skypasses += 2; } if(Fog_GetDensity() > 0 && skyfog > 0) { float *c; c = Fog_GetColor(); glEnable(GL_BLEND); glDisable(GL_TEXTURE_2D); glColor4f(c[0], c[1], c[2], CLAMP(0.0, skyfog, 1.0)); glBegin(GL_QUADS); for(i = 0, v = p->verts[0] ; i < 4 ; i++, v += VERTEXSIZE) glVertex3fv(v); glEnd(); glColor3f(1, 1, 1); glEnable(GL_TEXTURE_2D); glDisable(GL_BLEND); rs_skypasses++; } } /* ============== Sky_DrawFace ============== */ void Sky_DrawFace(int32_t axis) { glpoly_t *p; vec3_t verts[4]; int32_t i, j, start; float di, qi, dj, qj; vec3_t vup, vright, temp, temp2; Sky_SetBoxVert(-1.0, -1.0, axis, verts[0]); Sky_SetBoxVert(-1.0, 1.0, axis, verts[1]); Sky_SetBoxVert(1.0, 1.0, axis, verts[2]); Sky_SetBoxVert(1.0, -1.0, axis, verts[3]); start = Hunk_LowMark(); p = (glpoly_t *) Hunk_Alloc(sizeof(glpoly_t)); VectorSubtract(verts[2], verts[3], vup); VectorSubtract(verts[2], verts[1], vright); di = q_max((int32_t)r_sky_quality.value, 1); qi = 1.0 / di; dj = (axis < 4) ? di * 2 : di; //subdivide vertically more than horizontally on skybox sides qj = 1.0 / dj; for(i = 0; i < di; i++) { for(j = 0; j < dj; j++) { if(i * qi < skymins[0][axis] / 2 + 0.5 - qi || i * qi > skymaxs[0][axis] / 2 + 0.5 || j * qj < skymins[1][axis] / 2 + 0.5 - qj || j * qj > skymaxs[1][axis] / 2 + 0.5) continue; //if (i&1 ^ j&1) continue; //checkerboard test VectorScale(vright, qi * i, temp); VectorScale(vup, qj * j, temp2); VectorAdd(temp, temp2, temp); VectorAdd(verts[0], temp, p->verts[0]); VectorScale(vup, qj, temp); VectorAdd(p->verts[0], temp, p->verts[1]); VectorScale(vright, qi, temp); VectorAdd(p->verts[1], temp, p->verts[2]); VectorAdd(p->verts[0], temp, p->verts[3]); Sky_DrawFaceQuad(p); } } Hunk_FreeToLowMark(start); } /* ============== Sky_DrawSkyLayers draws the old-style scrolling cloud layers ============== */ void Sky_DrawSkyLayers(void) { int32_t i; if(r_skyalpha.value < 1.0) glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); for(i = 0 ; i < 6 ; i++) if(skymins[0][i] < skymaxs[0][i] && skymins[1][i] < skymaxs[1][i]) Sky_DrawFace(i); if(r_skyalpha.value < 1.0) glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); } /* ============== Sky_DrawSky called once per frame before drawing anything else ============== */ void Sky_DrawSky(void) { int32_t i; //in these special render modes, the sky faces are handled in the normal world/brush renderer if(r_drawflat_cheatsafe || r_lightmap_cheatsafe) return; // // reset sky bounds // for(i = 0 ; i < 6 ; i++) { skymins[0][i] = skymins[1][i] = 9999; skymaxs[0][i] = skymaxs[1][i] = -9999; } // // process world and bmodels: draw flat-shaded sky surfs, and update skybounds // Fog_DisableGFog(); glDisable(GL_TEXTURE_2D); if(Fog_GetDensity() > 0) glColor3fv(Fog_GetColor()); else glColor3fv(skyflatcolor); Sky_ProcessTextureChains(); Sky_ProcessEntities(); glColor3f(1, 1, 1); glEnable(GL_TEXTURE_2D); // // render slow sky: cloud layers or skybox // if(!r_fastsky.value && !(Fog_GetDensity() > 0 && skyfog >= 1)) { glDepthFunc(GL_GEQUAL); glDepthMask(0); if(skybox_name[0]) Sky_DrawSkyBox(); else Sky_DrawSkyLayers(); glDepthMask(1); glDepthFunc(GL_LEQUAL); } Fog_EnableGFog(); }