/* Copyright (C) 1996-1997 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. */ // gl_warp.c -- sky and water polygons #include "quakedef.h" #ifdef RGLQUAKE #include "glquake.h" #ifdef Q3SHADERS #include "shader.h" #endif #include extern void GL_DrawAliasMesh (mesh_t *mesh, int texnum); void R_DrawSkySphere (msurface_t *fa); extern model_t *loadmodel; int skytexturenum; int solidskytexture; int alphaskytexture; float speedscale; // for top sky and bottom sky qboolean usingskybox; msurface_t *warpface; extern cvar_t gl_skyboxname; extern cvar_t gl_skyboxdist; extern cvar_t r_fastsky; extern cvar_t r_fastskycolour; char defaultskybox[MAX_QPATH]; qboolean reloadskybox; int skyboxtex[6]; void R_DrawSkyBox (msurface_t *s); void BoundPoly (int numverts, float *verts, vec3_t mins, vec3_t maxs) { int i, j; float *v; mins[0] = mins[1] = mins[2] = 9999; maxs[0] = maxs[1] = maxs[2] = -9999; v = verts; for (i=0 ; i maxs[j]) maxs[j] = *v; } } //========================================================= /* // speed up sin calculations - Ed float turbsin[] = { #include "gl_warp_sin.h" }; #define TURBSCALE (256.0 / (2 * M_PI)) */ /* ============= EmitWaterPolys Does a water warp on the pre-fragmented glpoly_t chain ============= */ void EmitWaterPolys (msurface_t *fa, float basealpha) { float a; int l; extern cvar_t r_waterlayers; //the code prior to april 2005 gave a nicer result, but was incompatable with meshes and required poly lists instead //the new code uses vertex arrays but sacrifises the warping. We're left only with scaling. //The default settings still look nicer than origional quake but not pre-april. //in the plus side, you can never see the junction glitches of the old warping. :) #ifdef Q3SHADERS if (fa->texinfo->texture->shader) { meshbuffer_t mb; mb.sortkey = 0; mb.infokey = 0; mb.dlightbits = 0; mb.entity = &r_worldentity; mb.shader = fa->texinfo->texture->shader; mb.fog = NULL; mb.mesh = fa->mesh; R_RenderMeshBuffer(&mb, false); return; } #endif if (r_waterlayers.value>=1) { qglEnable(GL_BLEND); //to ensure. qglMatrixMode(GL_TEXTURE); fa->mesh->colors_array=NULL; for (a=basealpha,l = 0; l < r_waterlayers.value; l++,a=a*4/6) { qglPushMatrix(); qglColor4f(1, 1, 1, a); qglTranslatef (sin(cl.time+l*4) * 0.04f+cos(cl.time/2+l)*0.02f+cl.time/(64+l*8), cos(cl.time+l*4) * 0.06f+sin(cl.time/2+l)*0.02f+cl.time/(16+l*2), 0); GL_DrawAliasMesh(fa->mesh, fa->texinfo->texture->gl_texturenum); qglPopMatrix(); } qglMatrixMode(GL_MODELVIEW); qglDisable(GL_BLEND); //to ensure. } else //dull (fast) single player { qglColor4f(1, 1, 1, 1); qglMatrixMode(GL_TEXTURE); qglPushMatrix(); qglTranslatef (sin(cl.time) * 0.4f, cos(cl.time) * 0.06f, 0); GL_DrawAliasMesh(fa->mesh, fa->texinfo->texture->gl_texturenum); qglPopMatrix(); qglMatrixMode(GL_MODELVIEW); } } /* ============= EmitSkyPolys ============= */ void EmitSkyPolys (msurface_t *fa) { } /* =============== EmitBothSkyLayers Does a sky warp on the pre-fragmented glpoly_t chain This will be called for brushmodels, the world will have them chained together. =============== */ void EmitBothSkyLayers (msurface_t *fa) { GL_DisableMultitexture(); GL_Bind (solidskytexture); speedscale = cl.gametime*8; speedscale -= (int)speedscale & ~127 ; EmitSkyPolys (fa); qglEnable (GL_BLEND); GL_Bind (alphaskytexture); speedscale = cl.gametime*16; speedscale -= (int)speedscale & ~127 ; EmitSkyPolys (fa); qglDisable (GL_BLEND); } /* ================= R_DrawSkyChain ================= */ void R_DrawSkyBoxChain (msurface_t *s); void R_DrawSkyChain (msurface_t *s) { msurface_t *fa; GL_DisableMultitexture(); #ifdef Q3SHADERS if (!solidskytexture&&!usingskybox) { int i; if (s->texinfo->texture->shader && s->texinfo->texture->shader->skydome) for (i = 0; i < 6; i++) skyboxtex[i] = s->texinfo->texture->shader->skydome->farbox_textures[i]; } #endif if (r_fastsky.value||(!solidskytexture&&!usingskybox)) //this is for visability only... we'd otherwise not stoop this low (and this IS low) { int fc; qbyte *pal; fc = r_fastskycolour.value; if (fc > 255) fc = 255; if (fc < 0) fc = 0; pal = host_basepal+fc*3; qglDisable(GL_TEXTURE_2D); qglColor3f(pal[0]/255.0f, pal[1]/255.0f, pal[2]/255.0f); qglDisableClientState( GL_COLOR_ARRAY ); for (fa=s ; fa ; fa=fa->texturechain) { qglVertexPointer(3, GL_FLOAT, 0, fa->mesh->xyz_array); qglDrawElements(GL_TRIANGLES, fa->mesh->numindexes, GL_UNSIGNED_INT, fa->mesh->indexes); } R_IBrokeTheArrays(); qglColor3f(1, 1, 1); qglEnable(GL_TEXTURE_2D); return; } if (usingskybox) { R_DrawSkyBoxChain(s); return; } // if (usingskydome) { R_DrawSkySphere(s); return; } // used when gl_texsort is on GL_Bind(solidskytexture); speedscale = cl.gametime; speedscale += realtime - cl.gametimemark; speedscale*=8; speedscale -= (int)speedscale & ~127 ; for (fa=s ; fa ; fa=fa->texturechain) EmitSkyPolys (fa); qglEnable (GL_BLEND); GL_Bind (alphaskytexture); speedscale = cl.gametime; speedscale += realtime - cl.gametimemark; speedscale*=16; speedscale -= (int)speedscale & ~127 ; for (fa=s ; fa ; fa=fa->texturechain) EmitSkyPolys (fa); qglDisable (GL_BLEND); } /* ================================================================= Quake 2 environment sky ================================================================= */ /* ================== R_LoadSkys ================== */ static char *skyname_suffix[][6] = { {"px", "py", "nx", "ny", "pz", "nz"}, {"posx", "posy", "negx", "negy", "posz", "negz"}, {"rt", "bk", "lf", "ft", "up", "dn"} }; static char *skyname_pattern[] = { "%s_%s", "%s%s", "env/%s%s", "gfx/env/%s%s" }; void R_LoadSkys (void) { int i; char name[MAX_QPATH]; char *boxname; int p, s; if (*gl_skyboxname.string) boxname = gl_skyboxname.string; //user forced else boxname = defaultskybox; if (!*boxname) { //wipe the box for (i=0 ; i<6 ; i++) skyboxtex[i] = 0; } else { for(;;) { for (i=0 ; i<6 ; i++) { for (p = 0; p < sizeof(skyname_pattern)/sizeof(skyname_pattern[0]); p++) { for (s = 0; s < sizeof(skyname_suffix)/sizeof(skyname_suffix[0]); s++) { _snprintf (name, sizeof(name), skyname_pattern[p], boxname, skyname_suffix[s][i]); skyboxtex[i] = Mod_LoadHiResTexture(name, NULL, false, false, true); } } if (!skyboxtex[i]) break; qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } if (boxname != defaultskybox && i < 6) { boxname = defaultskybox; continue; } break; } } reloadskybox = false; } qboolean GLR_CheckSky() { return true; } void GLR_SetSky(char *name, float rotate, vec3_t axis) //called from the client code, once per level { Q_strncpyz(defaultskybox, name, sizeof(defaultskybox)); if (!*gl_skyboxname.string) //don't override a user's settings { reloadskybox = true; } } vec3_t skyclip[6] = { {1,1,0}, {1,-1,0}, {0,-1,1}, {0,1,1}, {1,0,1}, {-1,0,1} }; int c_sky; // 1 = s, 2 = t, 3 = 2048 int st_to_vec[6][3] = { {3,-1,2}, {-3,1,2}, {1,3,2}, {-1,-3,2}, {-2,-1,3}, // 0 degrees yaw, look straight up {2,-1,-3} // look straight down // {-1,2,3}, // {1,2,-3} }; // s = [0]/[2], t = [1]/[2] int vec_to_st[6][3] = { {-2,3,1}, {2,3,-1}, {1,3,2}, {-1,3,-2}, {-2,-1,3}, {-2,1,-3} // {-1,2,3}, // {1,2,-3} }; float skymins[2][6], skymaxs[2][6]; void DrawSkyPolygon (int nump, vec3_t vecs) { int i,j; vec3_t v, av; float s, t, dv; int axis; float *vp; c_sky++; // decide which face it maps to VectorCopy (vec3_origin, v); for (i=0, vp=vecs ; i 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 0) dv = vecs[j - 1]; else dv = -vecs[-j - 1]; if (dv < 0.001) continue; // don't divide by zero 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; } } #define MAX_CLIP_VERTS 64 void ClipSkyPolygon (int nump, vec3_t vecs, int stage) { float *norm; float *v; qboolean front, back; float d, e; float dists[MAX_CLIP_VERTS]; int sides[MAX_CLIP_VERTS]; vec3_t newv[2][MAX_CLIP_VERTS]; int newc[2]; int i, j; if (nump > MAX_CLIP_VERTS-2) Sys_Error ("ClipSkyPolygon: MAX_CLIP_VERTS"); if (stage == 6) { // fully clipped, so draw it DrawSkyPolygon (nump, vecs); return; } front = back = false; norm = skyclip[stage]; for (i=0, v = vecs ; i 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 ClipSkyPolygon (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 ; itexturechain) { //triangulate for (i=2 ; imesh->numvertexes ; i++) { VectorSubtract (fa->mesh->xyz_array[0], r_origin, verts[0]); VectorSubtract (fa->mesh->xyz_array[i-1], r_origin, verts[1]); VectorSubtract (fa->mesh->xyz_array[i], r_origin, verts[2]); ClipSkyPolygon (3, verts[0], 0); } } R_DrawSkyBox (s); } #define skygridx 32 #define skygridx1 (skygridx + 1) #define skygridxrecip (1.0f / (skygridx)) #define skygridy 32 #define skygridy1 (skygridy + 1) #define skygridyrecip (1.0f / (skygridy)) #define skysphere_numverts (skygridx1 * skygridy1) #define skysphere_numtriangles (skygridx * skygridy * 2) static float skysphere_vertex3f[skysphere_numverts * 3]; static float skysphere_texcoord2f[skysphere_numverts * 2]; static int skysphere_element3i[skysphere_numtriangles * 3]; mesh_t skymesh; int skymade; static void skyspherecalc(int skytype) { //yes, this is basically stolen from DarkPlaces int i, j, *e; float a, b, x, ax, ay, v[3], length, *vertex3f, *texcoord2f; float dx, dy, dz; float texscale; if (skymade == skytype) return; skymade = skytype; if (skymade == 2) texscale = 1/16.0f; else texscale = 1/1.5f; texscale*=3; skymesh.indexes = skysphere_element3i; skymesh.st_array = (void*)skysphere_texcoord2f; skymesh.lmst_array = (void*)skysphere_texcoord2f; skymesh.xyz_array = (void*)skysphere_vertex3f; skymesh.numindexes = skysphere_numtriangles * 3; skymesh.numvertexes = skysphere_numverts; dx = 16; dy = 16; dz = 16 / 3; vertex3f = skysphere_vertex3f; texcoord2f = skysphere_texcoord2f; for (j = 0;j <= skygridy;j++) { a = j * skygridyrecip; ax = cos(a * M_PI * 2); ay = -sin(a * M_PI * 2); for (i = 0;i <= skygridx;i++) { b = i * skygridxrecip; x = cos((b + 0.5) * M_PI); v[0] = ax*x * dx; v[1] = ay*x * dy; v[2] = -sin((b + 0.5) * M_PI) * dz; length = texscale / sqrt(v[0]*v[0]+v[1]*v[1]+(v[2]*v[2]*9)); *texcoord2f++ = v[0] * length; *texcoord2f++ = v[1] * length; *vertex3f++ = v[0]; *vertex3f++ = v[1]; *vertex3f++ = v[2]; } } e = skysphere_element3i; for (j = 0;j < skygridy;j++) { for (i = 0;i < skygridx;i++) { *e++ = j * skygridx1 + i; *e++ = j * skygridx1 + i + 1; *e++ = (j + 1) * skygridx1 + i; *e++ = j * skygridx1 + i + 1; *e++ = (j + 1) * skygridx1 + i + 1; *e++ = (j + 1) * skygridx1 + i; } } } void R_DrawSkySphere (msurface_t *fa) { extern cvar_t gl_maxdist; float time = cl.gametime+realtime-cl.gametimemark; float skydist = gl_maxdist.value; if (skydist<1) skydist=gl_skyboxdist.value; skydist/=16; //scale sky sphere and place around view origin. qglPushMatrix(); qglTranslatef(r_refdef.vieworg[0], r_refdef.vieworg[1], r_refdef.vieworg[2]); qglScalef(skydist, skydist, skydist); //draw in bulk? this is eeevil //FIXME: We should use the skybox clipping code and split the sphere into 6 sides. #ifdef Q3SHADERS if (fa->texinfo->texture->shader) { //the shader route. meshbuffer_t mb; skyspherecalc(2); mb.sortkey = 0; mb.infokey = -1; mb.dlightbits = 0; mb.entity = &r_worldentity; mb.shader = fa->texinfo->texture->shader; mb.fog = NULL; mb.mesh = &skymesh; R_PushMesh(mb.mesh, mb.shader->features); R_RenderMeshBuffer(&mb, false); } else #endif { //the boring route. skyspherecalc(1); qglMatrixMode(GL_TEXTURE); qglPushMatrix(); qglTranslatef(time*8/128, time*8/128, 0); GL_DrawAliasMesh(&skymesh, solidskytexture); qglColor4f(1,1,1,0.5); qglEnable(GL_BLEND); qglTranslatef(time*8/128, time*8/128, 0); GL_DrawAliasMesh(&skymesh, alphaskytexture); qglDisable(GL_BLEND); qglPopMatrix(); qglMatrixMode(GL_MODELVIEW); } qglPopMatrix(); if (!cls.allow_skyboxes) //allow a little extra fps. {//Draw the texture chain to only the depth buffer. qglColorMask(0,0,0,0); for (; fa; fa = fa->texturechain) { GL_DrawAliasMesh(fa->mesh, 0); } qglColorMask(1,1,1,1); } } /* ============== R_ClearSkyBox ============== */ void R_ClearSkyBox (void) { int i; if (!cl.worldmodel) //allow skyboxes on non quake1 maps. (expect them even) { usingskybox = false; return; } if (gl_skyboxname.modified) { gl_skyboxname.modified = false; reloadskybox = true; } if (reloadskybox) R_LoadSkys(); if (!skyboxtex[0] || !skyboxtex[1] || !skyboxtex[2] || !skyboxtex[3] || !skyboxtex[4] || !skyboxtex[5]) { usingskybox = false; return; } usingskybox = true; for (i=0 ; i<6 ; i++) { skymins[0][i] = skymins[1][i] = 9999; skymaxs[0][i] = skymaxs[1][i] = -9999; } } void MakeSkyVec (float s, float t, int axis) { vec3_t v, b; int j, k; float skydist = gl_skyboxdist.value; if (r_shadows.value) //because r_shadows comes with an infinate depth perspective. skydist*=20; //so we can put the distance at whatever distance needed. b[0] = s*skydist; b[1] = t*skydist; b[2] = skydist; 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]; } // avoid bilerp seam s = (s+1)*0.5; t = (t+1)*0.5; if (s < 1.0/512) s = 1.0/512; else if (s > 511.0/512) s = 511.0/512; if (t < 1.0/512) t = 1.0/512; else if (t > 511.0/512) t = 511.0/512; t = 1.0 - t; qglTexCoord2f (s, t); qglVertex3fv (v); } /* ============== R_DrawSkyBox ============== */ int skytexorder[6] = {0,2,1,3,4,5}; void R_DrawSkyBox (msurface_t *s) { msurface_t *fa; int i; if (!usingskybox) return; for (i=0 ; i<6 ; i++) { if (skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i]) continue; GL_Bind (skyboxtex[skytexorder[i]]); qglBegin (GL_QUADS); MakeSkyVec (skymins[0][i], skymins[1][i], i); MakeSkyVec (skymins[0][i], skymaxs[1][i], i); MakeSkyVec (skymaxs[0][i], skymaxs[1][i], i); MakeSkyVec (skymaxs[0][i], skymins[1][i], i); qglEnd (); } if (!cls.allow_skyboxes && s) //allow a little extra fps. { //write the depth correctly qglColorMask(0, 0, 0, 0); //depth only. for (fa = s; fa; fa = fa->texturechain) GL_DrawAliasMesh(fa->mesh, 1); qglColorMask(1, 1, 1, 1); } } //=============================================================== /* ============= R_InitSky A sky texture is 256*128, with the right side being a masked overlay ============== */ void GLR_InitSky (texture_t *mt) { int i, j, p; qbyte *src; unsigned trans[128*128]; unsigned transpix; int r, g, b; unsigned *rgba; char name[MAX_QPATH]; src = (qbyte *)mt + mt->offsets[0]; // make an average value for the back to avoid // a fringe on the top level r = g = b = 0; for (i=0 ; i<128 ; i++) for (j=0 ; j<128 ; j++) { p = src[i*256 + j + 128]; rgba = &d_8to24rgbtable[p]; trans[(i*128) + j] = *rgba; r += ((qbyte *)rgba)[0]; g += ((qbyte *)rgba)[1]; b += ((qbyte *)rgba)[2]; } ((qbyte *)&transpix)[0] = r/(128*128); ((qbyte *)&transpix)[1] = g/(128*128); ((qbyte *)&transpix)[2] = b/(128*128); ((qbyte *)&transpix)[3] = 0; sprintf(name, "%s_solid", mt->name); Q_strlwr(name); solidskytexture = Mod_LoadReplacementTexture(name, NULL, true, false, true); if (!solidskytexture) solidskytexture = GL_LoadTexture32(name, 128, 128, trans, true, false); /* if (!solidskytexture) solidskytexture = texture_extension_number++; GL_Bind (solidskytexture ); glTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, trans); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); */ for (i=0 ; i<128 ; i++) for (j=0 ; j<128 ; j++) { p = src[i*256 + j]; if (p == 0) trans[(i*128) + j] = transpix; else trans[(i*128) + j] = d_8to24rgbtable[p]; } sprintf(name, "%s_trans", mt->name); Q_strlwr(name); alphaskytexture = Mod_LoadReplacementTexture(name, NULL, true, true, true); if (!alphaskytexture) alphaskytexture = GL_LoadTexture32(name, 128, 128, trans, true, true); /* if (!alphaskytexture) alphaskytexture = texture_extension_number++; GL_Bind(alphaskytexture); glTexImage2D (GL_TEXTURE_2D, 0, gl_alpha_format, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, trans); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); */ } #endif