/* 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" #include "neh.h" extern model_t *loadmodel; static int sky_width, sky_height; int solidskytexture; int alphaskytexture; float speedscale; // for top sky and bottom sky static msurface_t *warpface; static 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; } } static unsigned RecursLevel; static void SubdividePolygon (int numverts, float *verts) { int i, j, k; vec3_t mins, maxs; float m; float *v; vec3_t front[64], back[64]; int f, b; float dist[64]; float frac; glpoly_t *poly; float s, t, subdivide; if (++RecursLevel > 128) // 16 seems enough and 512 might create stack overflow Sys_Error ("SubdividePolygon: excessive tree depth"); if (numverts > 60) Sys_Error ("SubdividePolygon: excessive numverts %i", numverts); subdivide = gl_subdivide_size.value; if (subdivide < 32) subdivide = 32; // Avoid low subdivide values BoundPoly (numverts, verts, mins, maxs); for (i=0 ; i<3 ; i++) { m = (mins[i] + maxs[i]) * 0.5; m = subdivide * floor (m/subdivide + 0.5); if (maxs[i] - m < 8) continue; if (m - mins[i] < 8) continue; // cut it v = verts + i; for (j=0 ; j= 0) { VectorCopy (v, front[f]); f++; } if (dist[j] <= 0) { VectorCopy (v, back[b]); b++; } if (dist[j] == 0 || dist[j+1] == 0) continue; if ( (dist[j] > 0) != (dist[j+1] > 0) ) { // clip point frac = dist[j] / (dist[j] - dist[j+1]); for (k=0 ; k<3 ; k++) front[f][k] = back[b][k] = v[k] + frac*(v[3+k] - v[k]); f++; b++; } } SubdividePolygon (f, front[0]); SubdividePolygon (b, back[0]); --RecursLevel; return; } poly = Hunk_Alloc (sizeof(glpoly_t) + (numverts-4) * VERTEXSIZE*sizeof(float)); poly->next = warpface->polys; warpface->polys = poly; poly->numverts = numverts; for (i=0 ; iverts[i]); s = DotProduct (verts, warpface->texinfo->vecs[0]); t = DotProduct (verts, warpface->texinfo->vecs[1]); poly->verts[i][3] = s; poly->verts[i][4] = t; } --RecursLevel; } /* ================ GL_SubdivideSurface Breaks a polygon up along axial 64 unit boundaries so that turbulent and sky warps can be done reasonably. ================ */ void GL_SubdivideSurface (msurface_t *fa) { vec3_t verts[64]; int numverts; int i; int lindex; float *vec; texture_t *t; warpface = fa; // // convert edges back to a normal polygon // numverts = 0; for (i=0 ; inumedges ; i++) { lindex = loadmodel->surfedges[fa->firstedge + i]; if (lindex > 0) vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position; else vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position; if (numverts >= 64) Sys_Error ("GL_SubdivideSurface: excessive numverts %i", numverts); VectorCopy (vec, verts[numverts]); numverts++; } RecursLevel = 0; SubdividePolygon (numverts, verts[0]); } //========================================================= // speed up sin calculations - Ed float turbsin[] = { #include "gl_warp_sin.h" }; #define TURBSCALE (256.0 / (2 * M_PI)) static void EmitFlatPoly (msurface_t *fa) { glpoly_t *p; float *v; int i; for (p = fa->polys ; p ; p = p->next) { glBegin (GL_POLYGON); for (i = 0, v = p->verts[0] ; i < p->numverts ; i++, v += VERTEXSIZE) glVertex3fv (v); glEnd (); } } /* ============= EmitWaterPolys Does a water warp on the pre-fragmented glpoly_t chain ============= */ void EmitWaterPolys (msurface_t *fa) { glpoly_t *p; float *v; int i; float s, t, os, ot; vec3_t nv; for (p=fa->polys ; p ; p=p->next) { glBegin (GL_POLYGON); for (i=0,v=p->verts[0] ; inumverts ; i++, v+=VERTEXSIZE) { os = v[3]; ot = v[4]; s = os + turbsin[(int)((ot*0.125+realtime) * TURBSCALE) & 255]; s *= (1.0/64); t = ot + turbsin[(int)((os*0.125+realtime) * TURBSCALE) & 255]; t *= (1.0/64); glTexCoord2f (s, t); if(r_waterripple.value) { nv[0] = v[0]; nv[1] = v[1]; nv[2] = v[2] + r_waterripple.value*sin(v[0]*0.05+realtime*3)*sin(v[2]*0.05+realtime*3); glVertex3fv (nv); } else { glVertex3fv (v); } } glEnd (); } } /* ============= EmitSkyPolys ============= */ void EmitSkyPolys (msurface_t *fa) { glpoly_t *p; float *v; int i; float s, t; vec3_t dir; float length; for (p=fa->polys ; p ; p=p->next) { glBegin (GL_POLYGON); for (i=0,v=p->verts[0] ; inumverts ; i++, v+=VERTEXSIZE) { 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; dir[0] *= length; dir[1] *= length; s = (speedscale + dir[0]) * (1.0/128); t = (speedscale + dir[1]) * (1.0/128); glTexCoord2f (s, t); glVertex3fv (v); } glEnd (); } } /* =============== 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) { int i; int lindex; float *vec; GL_DisableMultitexture(); GL_Bind (solidskytexture); speedscale = realtime*8; speedscale -= (int)speedscale & ~127 ; EmitSkyPolys (fa); glEnable (GL_BLEND); GL_Bind (alphaskytexture); speedscale = realtime*16; speedscale -= (int)speedscale & ~127 ; EmitSkyPolys (fa); glDisable (GL_BLEND); } /* ================================================================= PCX Loading ================================================================= */ typedef struct { char manufacturer; char version; char encoding; char bits_per_pixel; unsigned short xmin,ymin,xmax,ymax; unsigned short hres,vres; unsigned char palette[48]; char reserved; char color_planes; unsigned short bytes_per_line; unsigned short palette_type; char filler[58]; unsigned data; // unbounded } pcx_t; static byte *pcx_rgb; /* ============ LoadPCX ============ */ static void LoadPCX (FILE *f) { pcx_t *pcx, pcxbuf; byte palette[768]; byte *pix; int x, y; int dataByte, runLength; int count; // // parse the PCX file // fread (&pcxbuf, 1, sizeof(pcxbuf), f); pcx = &pcxbuf; if (pcx->manufacturer != 0x0a || pcx->version != 5 || pcx->encoding != 1 || pcx->bits_per_pixel != 8 || pcx->xmax >= 320 || pcx->ymax >= 256) Sys_Error ("LoadPCX: Bad pcx file"); // seek to palette fseek (f, -768, SEEK_END); fread (palette, 1, 768, f); fseek (f, sizeof(pcxbuf) - 4, SEEK_SET); count = (pcx->xmax+1) * (pcx->ymax+1); pcx_rgb = malloc( count * 4); for (y=0 ; y<=pcx->ymax ; y++) { pix = pcx_rgb + 4*y*(pcx->xmax+1); for (x=0 ; x<=pcx->ymax ; ) { dataByte = fgetc(f); if((dataByte & 0xC0) == 0xC0) { runLength = dataByte & 0x3F; dataByte = fgetc(f); } else runLength = 1; while(runLength-- > 0) { pix[0] = palette[dataByte*3]; pix[1] = palette[dataByte*3+1]; pix[2] = palette[dataByte*3+2]; pix[3] = 255; pix += 4; x++; } } } sky_width = (int)(pcx->xmax) + 1; sky_height = (int)(pcx->ymax) + 1; } /* ========================================================= TARGA LOADING ========================================================= */ typedef struct _TargaHeader { unsigned char id_length, colormap_type, image_type; unsigned short colormap_index, colormap_length; unsigned char colormap_size; unsigned short x_origin, y_origin, width, height; unsigned char pixel_size, attributes; } TargaHeader; static TargaHeader targa_header; byte *targa_rgba; static int fgetLittleShort (FILE *f) { byte b1, b2; b1 = fgetc(f); b2 = fgetc(f); return (short)(b1 + b2*256); } static int fgetLittleLong (FILE *f) { byte b1, b2, b3, b4; b1 = fgetc(f); b2 = fgetc(f); b3 = fgetc(f); b4 = fgetc(f); return b1 + (b2<<8) + (b3<<16) + (b4<<24); } /* ============= LoadTGA ============= */ void LoadTGA (FILE *fin) { int columns, rows, numPixels; byte *pixbuf; int row, column; targa_header.id_length = fgetc(fin); targa_header.colormap_type = fgetc(fin); targa_header.image_type = fgetc(fin); targa_header.colormap_index = fgetLittleShort(fin); targa_header.colormap_length = fgetLittleShort(fin); targa_header.colormap_size = fgetc(fin); targa_header.x_origin = fgetLittleShort(fin); targa_header.y_origin = fgetLittleShort(fin); targa_header.width = fgetLittleShort(fin); targa_header.height = fgetLittleShort(fin); targa_header.pixel_size = fgetc(fin); targa_header.attributes = fgetc(fin); if (targa_header.image_type!=2 && targa_header.image_type!=10) Sys_Error ("LoadTGA: Only type 2 and 10 targa RGB images supported"); if (targa_header.colormap_type !=0 || (targa_header.pixel_size!=32 && targa_header.pixel_size!=24)) Sys_Error ("Texture_LoadTGA: Only 32 or 24 bit images supported (no colormaps)"); columns = targa_header.width; rows = targa_header.height; numPixels = columns * rows; targa_rgba = malloc (numPixels*4); if (targa_header.id_length != 0) fseek(fin, targa_header.id_length, SEEK_CUR); // skip TARGA image comment if (targa_header.image_type==2) { // Uncompressed, RGB images for(row=rows-1; row>=0; row--) { pixbuf = targa_rgba + row*columns*4; for(column=0; column=0; row--) { pixbuf = targa_rgba + row*columns*4; for(column=0; column0) row--; else goto breakOut; pixbuf = targa_rgba + row*columns*4; } } } else { // non run-length packet for(j=0;j0) row--; else goto breakOut; pixbuf = targa_rgba + row*columns*4; } } } } breakOut:; } } fclose(fin); sky_width = targa_header.width; sky_height = targa_header.height; } /* ================== RotateImage ================== */ static void RotateImage (int *Img, int width, int height, qboolean Left) { int i, j, Size, *Rot; if (width != height) { Con_Printf ("RotateImage: image not symmetrical (%dx%d)\n", width, height); return; } Size = width * height * sizeof(int); if (!(Rot = malloc (Size))) { Con_Printf ("RotateImage: not enough memory for %dk\n", Size / 1024); return; } for (i = 0; i < height; ++i) { for (j = 0; j < width; ++j) { if (Left) Rot[(height - 1 - j) * width + i] = Img[i * width + j]; else Rot[j * width + (width - 1 - i)] = Img[i * width + j]; } } memcpy (Img, Rot, Size); free (Rot); } /* ================== R_LoadSkys ================== */ static char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; static int suf_ord1[6] = {0, 1, 2, 3, 4, 5}; // Q2/VRML order static int suf_ord2[6] = {3, 0, 1, 2, 4, 5}; // Rotated 90 degrees left static char skybox_name[MAX_QPATH] = ""; // name of current skybox void R_LoadSkys (char *skybox) { int i, j, *suf_ord; FILE *f; char name[MAX_QPATH]; byte *data[6]; qboolean RotateSkyBox = false; static qboolean OldRotated = false; if (cl.worldmodel->name != NULL) { name[0] = '\0'; COM_StripExtension (cl.worldmodel->name + 5, name); if (!stricmp(name, "invein")) RotateSkyBox = true; // Invein's skybox is invalidly rotated 90 degrees right } if (RotateSkyBox == OldRotated && !strcmp(skybox, skybox_name)) //no change { Cvar_SetValue ("r_oldsky", skybox_name[0] == 0); return; } OldRotated = RotateSkyBox; // Make sure horizontal images are rotated correctly suf_ord = RotateSkyBox ? suf_ord2 : suf_ord1; for (i=0 ; i<6 ; i++) { for (j = 0; j < 2; ++j) { sprintf (name, "gfx/env/%s%s.%s", skybox, suf[suf_ord[i]], j == 0 ? "tga" : "pcx"); COM_FOpenFile (name, &f); if (!f) continue; if (j == 0) { LoadTGA (f); data[i] = targa_rgba; } else { LoadPCX (f); data[i] = pcx_rgb; } // If entire skybox is rotated, up/down images must be rotated right/left if (i >= 4 && RotateSkyBox) RotateImage ((int *)data[i], sky_width, sky_height, i == 5); break; } if (j == 2) { Con_Printf ("Couldn't load %s\n", name); break; } } if (i == 6) { for (j = 0; j < 6; ++j) { GL_Bind (skyboxtextures + j); sprintf (name, "gfx/env/%s%s", skybox, suf[suf_ord[j]]); GL_Upload32 (data[j], sky_width, sky_height, false, false, name); } } for (j = 0; j < i; ++j) free (data[j]); if (i == 6) { strcpy (skybox_name, skybox); Con_DPrintf ("skybox set to %s%s\n", skybox, RotateSkyBox ? " and rotated" : ""); } // Enable/disable skybox Cvar_SetValue ("r_oldsky", skybox_name[0] == 0); } /* ================= R_Sky_NewMap ================= */ void R_Sky_NewMap (void) { char key[MAX_KEY], value[MAX_VALUE]; char *data; // initially no skybox Cvar_SetValue ("r_oldsky", 1); data = cl.worldmodel->entities; if (!data) return; 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) && value[0]) R_LoadSkys (value); } } /* ================= R_SkyCommand_f ================= */ void R_SkyCommand_f (void) { switch (Cmd_Argc()) { case 1: Con_Printf("\"sky\" is \"%s\"\n", skybox_name); break; case 2: R_LoadSkys (Cmd_Argv(1)); break; default: Con_Printf("usage: [load]sky \n"); } } static vec3_t skyclip[6] = { {1,1,0}, {1,-1,0}, {0,-1,1}, {0,1,1}, {1,0,1}, {-1,0,1} }; // 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; // 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]; 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) EmitSkyPolys (fa); /* for (fa=s, i=0 ; fa ; fa=fa->texturechain, ++i) { if (i == 1000) Sys_Error ("EmitSkyPolys runaway loop 1000"); if (i > 0 && fa == s) Sys_Error ("EmitSkyPolys runaway loop fa==s"); EmitSkyPolys (fa); }*/ glEnable (GL_BLEND); GL_Bind (alphaskytexture); speedscale = realtime*16; speedscale -= (int)speedscale & ~127 ; for (fa=s ; fa ; fa=fa->texturechain) EmitSkyPolys (fa); glDisable (GL_BLEND); } else { msurface_t *fa; int i; vec3_t verts[MAX_CLIP_VERTS], origin; glpoly_t *p; // calculate vertex values for sky box VectorCopy (r_origin, origin); origin[0] += 0.001; // Offset slightly horizontally to avoid initial top side distortion, kludge for (fa=s ; fa ; fa=fa->texturechain) { for (p=fa->polys ; p ; p=p->next) { for (i=0 ; inumverts ; i++) VectorSubtract (p->verts[i], origin, verts[i]); ClipSkyPolygon (p->numverts, verts[0], 0); } } } } /* ============== R_ClearSkyBox ============== */ void R_ClearSkyBox (void) { int i; if (r_oldsky.value) return; for (i=0 ; i<6 ; i++) { skymins[0][i] = skymins[1][i] = 9999; skymaxs[0][i] = skymaxs[1][i] = -9999; } } static void MakeSkyVec (float s, float t, int axis) { vec3_t v, b; int j, k, farclip; float w, h; // Compromise; big enough to avoid sky covering world for the Nehahra maps, // but small enough to avoid hiding skyboxes in fog farclip = gl_fogenable.value && !gl_fogdisable.value ? gl_skyclip.value : GL_FARCLIP; if (farclip > GL_FARCLIP) farclip = GL_FARCLIP; b[0] = s * farclip / 2; b[1] = t * farclip / 2; b[2] = farclip / 2; 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; w = sky_width; h = sky_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); } /* ============== R_DrawSkyBox ============== */ static int skytexorder[6] = {0,2,1,3,4,5}; void R_DrawSkyBox (void) { int i, j, k; vec3_t v; float s, t; msurface_t *fa; if (r_oldsky.value) return; if (gl_do_skyfix) { // Non-transparent skybox only works in multitexture for now if (!skychain) return; R_ClearSkyBox (); R_DrawSkyChain (skychain); GL_DisableMultitexture (); } for (i=0 ; i<6 ; i++) { if (skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i]) continue; GL_Bind (skyboxtextures + skytexorder[i]); glBegin (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); glEnd (); } if (!gl_do_skyfix) return; // Non-transparent skybox only works in multitexture for now glDisable (GL_TEXTURE_2D); glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glEnable (GL_BLEND); glBlendFunc (GL_ZERO, GL_ONE); for (fa = skychain ; fa ; fa = fa->texturechain) EmitFlatPoly (fa); glEnable (GL_TEXTURE_2D); glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDisable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); skychain = NULL; } //=============================================================== /* ============= R_InitSky A sky texture is 256*128, with the right side being a masked overlay ============== */ void R_InitSky (texture_t *mt) { int i, j, p; byte *src; unsigned trans[128*128]; unsigned transpix; int r, g, b; unsigned *rgba; src = (byte *)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_8to24table[p]; trans[(i*128) + j] = *rgba; r += ((byte *)rgba)[0]; g += ((byte *)rgba)[1]; b += ((byte *)rgba)[2]; } ((byte *)&transpix)[0] = r/(128*128); ((byte *)&transpix)[1] = g/(128*128); ((byte *)&transpix)[2] = b/(128*128); ((byte *)&transpix)[3] = 0; 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_8to24table[p]; } 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); }