/* 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" #if defined(GLQUAKE) || defined(D3DQUAKE) #include "glquake.h" #include "shader.h" #include static void R_CalcSkyChainBounds (msurface_t *s); static void GL_DrawSkyGrid (texture_t *tex); static void GL_DrawSkySphere (msurface_t *fa); static void GL_SkyForceDepth(msurface_t *fa); static void GL_DrawSkyBox (texid_t *texnums, msurface_t *s); //static int skytexturenum; static float speedscale; // for top sky and bottom sky //static float skyrotate; //static vec3_t skyaxis; //static qboolean usingskybox; //static msurface_t *warpface; //extern cvar_t r_skyboxname; extern cvar_t gl_skyboxdist; extern cvar_t r_fastsky; extern cvar_t r_fastskycolour; //static char defaultskybox[MAX_QPATH]; //static int skyprogram; //static int skyprogram_time; //static int skyprogram_eyepos; //static int waterprogram; //static int waterprogram_time; static qboolean overrideskybox; static texid_t overrideskyboxtex[6]; //static vec3_t glskycolor; //========================================================= /* ================= GL_DrawSkyChain ================= */ #ifdef GLQUAKE static void R_DrawSkyBoxChain (msurface_t *s); void R_DrawSkyChain (msurface_t *s) { texid_t *skyboxtex; skyboxtex = s->texinfo->texture->shader->skydome->farbox_textures; R_CalcSkyChainBounds(s); #ifdef GLQUAKE if (qrenderer == QR_OPENGL) { GL_DrawSkyBox (skyboxtex, s); GL_SkyForceDepth(s); return; } #endif if (*r_fastsky.string) { GL_DrawSkyGrid(s->texinfo->texture); GL_SkyForceDepth(s); } else { GL_DrawSkySphere(s); GL_SkyForceDepth(s); } R_IBrokeTheArrays(); } #endif /* ================================================================= Quake 2 environment sky ================================================================= */ static vec3_t skyclip[6] = { {1,1,0}, {1,-1,0}, {0,-1,1}, {0,1,1}, {1,0,1}, {-1,0,1} }; static int c_sky; // 1 = s, 2 = t, 3 = 2048 static 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] static 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} }; static float skymins[2][6], skymaxs[2][6]; static 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 VectorClear (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 static 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); } } } #define skygridx 16 #define skygridx1 (skygridx + 1) #define skygridxrecip (1.0f / (skygridx)) #define skygridy 16 #define skygridy1 (skygridy + 1) #define skygridyrecip (1.0f / (skygridy)) #define skysphere_numverts (skygridx1 * skygridy1) #define skysphere_numtriangles (skygridx * skygridy * 2) static int skymade; static index_t skysphere_element3i[skysphere_numtriangles * 3]; static float skysphere_texcoord2f[skysphere_numverts * 2]; #ifdef D3DQUAKE static float skysphere_d3dvertex[skysphere_numverts * 5]; static d3d_animateskysphere(float time) { int i; float *d3dvert, *texcoord2f; d3dvert = skysphere_d3dvertex; texcoord2f = skysphere_texcoord2f; for (i = 0; i < skysphere_numverts; i++) { d3dvert[3] = time+*texcoord2f++; d3dvert[4] = time+*texcoord2f++; d3dvert+=5; } } static void d3d_skyspherecalc(int skytype) { //yes, this is basically stolen from DarkPlaces int i, j; index_t *e; float a, b, x, ax, ay, v[3], length, *d3dvert, *texcoord2f; float dx, dy, dz; float texscale; if (skymade == skytype+500) return; skymade = skytype+500; if (skytype == 2) texscale = 1/16.0f; else texscale = 1/1.5f; texscale*=3; dx = 16; dy = 16; dz = 16 / 3; d3dvert = skysphere_d3dvertex; 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)); *d3dvert++ = v[0]*1000; *d3dvert++ = v[1]*1000; *d3dvert++ = v[2]*1000; d3dvert+=2; *texcoord2f++ = v[0] * length; *texcoord2f++ = v[1] * length; } } 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; } } } #endif #ifdef GLQUAKE static vecV_t skysphere_vertex3f[skysphere_numverts]; static mesh_t skymesh; static void gl_skyspherecalc(int skytype) { //yes, this is basically stolen from DarkPlaces int i, j; index_t *e; float a, b, x, ax, ay, v[3], length, *texcoord2f; vecV_t* vertex; 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; vertex = 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; (*vertex)[0] = v[0]; (*vertex)[1] = v[1]; (*vertex)[2] = v[2]; vertex++; } } 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; } } } static void GL_SkyForceDepth(msurface_t *fa) { vbo_t *v; mesh_t *m; if (!cls.allow_skyboxes) //allow a little extra fps. {//Draw the texture chain to only the depth buffer. v = &fa->texinfo->texture->vbo; qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, v->vboe); qglBindBufferARB(GL_ARRAY_BUFFER_ARB, v->vbocoord); qglVertexPointer(3, GL_FLOAT, 0, v->coord); qglDisable(GL_TEXTURE_2D); if (qglColorMask) qglColorMask(0,0,0,0); for (; fa; fa = fa->texturechain) { m = fa->mesh; qglDrawRangeElements(GL_TRIANGLES, m->vbofirstvert, m->vbofirstvert+m->numvertexes, m->numindexes, GL_INDEX_TYPE, v->indicies+m->vbofirstelement); } if (qglColorMask) qglColorMask(1,1,1,1); qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); R_IBrokeTheArrays(); } } static void FTE_DEPRECATED GL_DrawAliasMesh (mesh_t *mesh, texid_t texnum) { shader_t shader; memset(&shader, 0, sizeof(shader)); shader.numpasses = 1; shader.passes[0].numMergedPasses = 1; shader.passes[0].anim_frames[0] = texnum; shader.passes[0].rgbgen = RGB_GEN_IDENTITY; shader.passes[0].alphagen = ALPHA_GEN_IDENTITY; shader.passes[0].shaderbits |= SBITS_MISC_DEPTHWRITE; shader.passes[0].blendmode = GL_MODULATE; shader.passes[0].texgen = T_GEN_SINGLEMAP; BE_DrawMeshChain(&shader, mesh, NULL, NULL); } static void GL_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. if (fa->texinfo->texture->shader) { //the shader route. meshbuffer_t mb; gl_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 { //the boring route. gl_skyspherecalc(1); qglMatrixMode(GL_TEXTURE); qglPushMatrix(); qglTranslatef(time*8/128, time*8/128, 0); GL_DrawAliasMesh(&skymesh, fa->texinfo->texture->shader->defaulttextures.base); qglColor4f(1,1,1,0.5); qglEnable(GL_BLEND); qglTranslatef(time*8/128, time*8/128, 0); GL_DrawAliasMesh(&skymesh, fa->texinfo->texture->shader->defaulttextures.fullbright); qglDisable(GL_BLEND); qglPopMatrix(); qglMatrixMode(GL_MODELVIEW); } qglPopMatrix(); } #endif #ifdef GLQUAKE static void GL_MakeSkyVec (float s, float t, int axis) { vec3_t v, b; int j, k; float skydist = gl_skyboxdist.value; extern cvar_t gl_maxdist; if (!skydist) { // if (r_shadows.value || !gl_maxdist.value) // skydist = 1000000; //inifite distance // else skydist = gl_maxdist.value * 0.577; } 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]; } // 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); } static void EmitSkyGridVert (vec3_t v) { vec3_t dir; float s, t; float length; VectorSubtract (v, r_origin, dir); dir[2] *= 3; // flatten the sphere length = VectorLength (dir); length = 6*63/length; dir[0] *= length; dir[1] *= length; s = (speedscale + dir[0]) * (1.0/128); t = (speedscale + dir[1]) * (1.0/128); qglTexCoord2f (s, t); qglVertex3fv (v); } // s and t range from -1 to 1 static void MakeSkyGridVec2 (float s, float t, int axis, vec3_t v) { vec3_t b; int j, k; float skydist = gl_skyboxdist.value; extern cvar_t gl_maxdist; if (!skydist) { // if (r_shadows.value || !gl_maxdist.value) // skydist = 1000000; //inifite distance // else skydist = gl_maxdist.value * 0.577; } 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]; } } #define SUBDIVISIONS 10 static void GL_DrawSkyGridFace (int axis) { int i, j; vec3_t vecs[4]; float s, t; float fstep = 2.0 / SUBDIVISIONS; qglBegin (GL_QUADS); for (i = 0; i < SUBDIVISIONS; i++) { s = (float)(i*2 - SUBDIVISIONS) / SUBDIVISIONS; if (s + fstep < skymins[0][axis] || s > skymaxs[0][axis]) continue; for (j = 0; j < SUBDIVISIONS; j++) { t = (float)(j*2 - SUBDIVISIONS) / SUBDIVISIONS; if (t + fstep < skymins[1][axis] || t > skymaxs[1][axis]) continue; MakeSkyGridVec2 (s, t, axis, vecs[0]); MakeSkyGridVec2 (s, t + fstep, axis, vecs[1]); MakeSkyGridVec2 (s + fstep, t + fstep, axis, vecs[2]); MakeSkyGridVec2 (s + fstep, t, axis, vecs[3]); EmitSkyGridVert (vecs[0]); EmitSkyGridVert (vecs[1]); EmitSkyGridVert (vecs[2]); EmitSkyGridVert (vecs[3]); } } qglEnd (); } static void GL_DrawSkyGrid (texture_t *tex) { int i; float time = cl.gametime+realtime-cl.gametimemark; PPL_RevertToKnownState(); GL_Bind (tex->shader->defaulttextures.base); speedscale = time*8; speedscale -= (int)speedscale & ~127; for (i = 0; i < 6; i++) { if ((skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i])) continue; GL_DrawSkyGridFace (i); } qglEnable (GL_BLEND); GL_Bind (tex->shader->defaulttextures.fullbright); speedscale = time*16; speedscale -= (int)speedscale & ~127; for (i = 0; i < 6; i++) { if ((skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i])) continue; GL_DrawSkyGridFace (i); } qglDisable (GL_BLEND); } #endif /* ============== R_DrawSkyBox ============== */ static int skytexorder[6] = {0,2,1,3,4,5}; #ifdef GLQUAKE static void GL_DrawSkyBox (texid_t *texnums, msurface_t *s) { int i; if (cl.skyrotate) { for (i=0 ; i<6 ; i++) { if (skymins[0][i] < skymaxs[0][i] && skymins[1][i] < skymaxs[1][i]) break; skymins[0][i] = -1; //fully visible skymins[1][i] = -1; skymaxs[0][i] = 1; skymaxs[1][i] = 1; } if (i == 6) return; //can't see anything for ( ; i<6 ; i++) { skymins[0][i] = -1; skymins[1][i] = -1; skymaxs[0][i] = 1; skymaxs[1][i] = 1; } } qglPushMatrix (); qglTranslatef (r_origin[0], r_origin[1], r_origin[2]); if (cl.skyrotate) qglRotatef (cl.time * cl.skyrotate, cl.skyaxis[0], cl.skyaxis[1], cl.skyaxis[2]); for (i=0 ; i<6 ; i++) { if (skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i]) continue; GL_Bind (texnums[skytexorder[i]]); qglBegin (GL_QUADS); GL_MakeSkyVec (skymins[0][i], skymins[1][i], i); GL_MakeSkyVec (skymins[0][i], skymaxs[1][i], i); GL_MakeSkyVec (skymaxs[0][i], skymaxs[1][i], i); GL_MakeSkyVec (skymaxs[0][i], skymins[1][i], i); qglEnd (); } qglPopMatrix (); } #endif //=============================================================== /* ============= R_InitSky A sky texture is 256*128, with the right side being a masked overlay ============== */ texnums_t R_InitSky (texture_t *mt) { int i, j, p; qbyte *src; unsigned trans[128*128]; unsigned transpix, alphamask; int r, g, b; unsigned *rgba; char name[MAX_QPATH]; texnums_t tn; memset(&tn, 0, sizeof(tn)); 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; Q_snprintfz(name, sizeof(name), "%s_solid", mt->name); Q_strlwr(name); tn.base = R_LoadReplacementTexture(name, NULL, IF_NOALPHA); if (!TEXVALID(tn.base)) tn.base = R_LoadTexture32(name, 128, 128, trans, IF_NOALPHA|IF_NOGAMMA); alphamask = LittleLong(0x7fffffff); 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] & alphamask; } Q_snprintfz(name, sizeof(name), "%s_trans", mt->name); Q_strlwr(name); tn.fullbright = R_LoadReplacementTexture(name, NULL, 0); if (!TEXVALID(tn.fullbright)) tn.fullbright = R_LoadTexture32(name, 128, 128, trans, IF_NOGAMMA); return tn; } #endif