Have to give credit to metlslime here; the warp functions have their ultimate source in his framebuffer water texture update code. They've come a long and winding road since then, but that's where they began. Elements of it also came from DarkPlaces' skysphere code (OpenGL geek pun not intended but it's actually quite appropriate...)
If you don't know what to do with this you probably shouldn't be trying to do it!!!
Code: Select all
// globals
int underwatertexture = 0;
int underwatertexturew = 0;
int underwatertextureh = 0;
Code: Select all
if (r_viewleaf->contents == CONTENTS_EMPTY ||
r_viewleaf->contents == CONTENTS_SKY ||
r_viewleaf->contents == CONTENTS_SOLID)
{
x = r_refdef.vrect.x * glwidth / vid.width;
x2 = (r_refdef.vrect.x + r_refdef.vrect.width) * glwidth / vid.width;
y = (vid.height - r_refdef.vrect.y) * glheight / vid.height;
y2 = (vid.height - (r_refdef.vrect.y + r_refdef.vrect.height)) * glheight / vid.height;
// fudge around because of frac screen scale
if (x > 0) x--;
if (x2 < glwidth) x2++;
if (y2 < 0) y2--;
if (y < glheight) y++;
w = x2 - x;
h = y - y2;
}
else
{
glx = gly = 0;
x = 0;
y2 = sb_lines;
w = underwatertexturew;
h = underwatertextureh;
}
glViewport (glx + x, gly + y2, w, h);
Code: Select all
void R_BeginUnderwaterWarp (void)
{
static int oldwidth = -1;
static int oldheight = -1;
if (r_viewleaf->contents == CONTENTS_EMPTY ||
r_viewleaf->contents == CONTENTS_SKY ||
r_viewleaf->contents == CONTENTS_SOLID) return;
// this will create the texture for us the first time we go underwater, which will give a temporary stall.
// to avoid that, we should pre-create the texture and then just check for size changes. it's just done this
// way for simplicity and demonstration purposes.
if (!underwatertexture || oldwidth != glwidth || oldheight != glheight)
{
int maxsize;
// GLQuake doesn't get the max texture size correctly so we need to get it here
// you can remove this part if you've modded your engine to get the correct size,
// but you should still check the size of the underwater update texture against it.
glGetIntegerv (GL_MAX_TEXTURE_SIZE, &maxsize);
if (!underwatertexture)
{
underwatertexture = texture_extension_number;
texture_extension_number++;
}
// pick a power of 2 equal to or above the screen res
for (underwatertexturew = 1; underwatertexturew < glwidth; underwatertexturew <<= 1);
for (underwatertextureh = 1; underwatertextureh < glheight; underwatertextureh <<= 1);
// take it down one level to save fillrate
// (we probably don't need to do this if we blend the texture with the polyblend colour)
underwatertexturew >>= 1;
underwatertextureh >>= 1;
// clamp to max supported size
if (underwatertexturew > maxsize) underwatertexturew = maxsize;
if (underwatertextureh > maxsize) underwatertextureh = maxsize;
// create the texture with no pixels
// http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml
// In GL version 1.1 or greater, data may be a null pointer. In this case, texture memory is allocated to
// accommodate a texture of width width and height height. You can then download subtextures to initialize
// this texture memory.
GL_Bind (underwatertexture);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, underwatertexturew, underwatertextureh, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// store back
oldwidth = glwidth;
oldheight = glheight;
}
}
#define WARP_TESS 16
typedef struct warpverts_s
{
float xy[2];
float st[2];
} warpverts_t;
warpverts_t *warpverts = NULL;
unsigned short *warpindexes = NULL;
void R_EndUnderwaterWarp (void)
{
int x, y;
warpverts_t *dst;
float hscale;
float textessw, textessh;
float invtess;
float rdt;
// this can run when we're disconnected so make sure it doesn't as viewleaf/etc will be invalid
// see GL_Set2D in gl_draw.c
if (!r_viewleaf) return;
if (cls.state != ca_connected) return;
if (r_viewleaf->contents == CONTENTS_EMPTY ||
r_viewleaf->contents == CONTENTS_SKY ||
r_viewleaf->contents == CONTENTS_SOLID) return;
// sanity
if (!underwatertexture) return;
// copy back from framebuffer to texture
GL_Bind (underwatertexture);
glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 0, sb_lines, underwatertexturew, underwatertextureh);
// we need to generate the actual vertex data each frame as the viewsize might change
if (!warpverts) warpverts = (warpverts_t *) malloc ((WARP_TESS + 1) * (WARP_TESS + 1) * sizeof (warpverts_t));
// indexes never change and just need to be generated once
if (!warpindexes)
{
int i;
int ndx;
warpindexes = (unsigned short *) malloc (WARP_TESS * WARP_TESS * 6 * sizeof (unsigned short));
for (y = 0, ndx = 0, i = 0; y < WARP_TESS; y++, i++)
{
for (x = 0; x < WARP_TESS; x++, i++, ndx += 6)
{
warpindexes[ndx + 0] = i;
warpindexes[ndx + 1] = i + 1;
warpindexes[ndx + 2] = i + WARP_TESS + 2;
warpindexes[ndx + 3] = i;
warpindexes[ndx + 4] = i + WARP_TESS + 2;
warpindexes[ndx + 5] = i + WARP_TESS + 1;
}
}
}
hscale = (float) (vid.height - sb_lines) / (float) vid.height;
textessw = 32.0f;
textessh = 32.0f * hscale;
invtess = 1.0f / WARP_TESS;
rdt = cl.time * 2.0f;
for (y = 0, dst = warpverts; y <= WARP_TESS; y++)
{
for (x = 0; x <= WARP_TESS; x++, dst++)
{
float s = invtess * x * textessw;
float t = (invtess * y) * textessh;
dst->xy[0] = (float) (x * vid.width / WARP_TESS);
dst->xy[1] = (float) ((y * vid.height / WARP_TESS) * hscale);
if (x == 0 || x == WARP_TESS)
dst->st[0] = (float) dst->xy[0] / (float) vid.width;
else dst->st[0] = (s + sin (t + rdt) * 0.125f) * 0.03125f;
if (y == 0 || y == WARP_TESS)
dst->st[1] = (float) dst->xy[1] / (float) vid.height;
else dst->st[1] = (t + sin (s + rdt) * 0.125f) * 0.03125f;
dst->xy[1] += sb_lines;
}
}
glMatrixMode (GL_PROJECTION);
glPushMatrix ();
glLoadIdentity ();
glOrtho (0, vid.width, 0, vid.height, -99999, 99999);
// triangle strip weenies can go to hell
glEnableClientState (GL_VERTEX_ARRAY);
glVertexPointer (2, GL_FLOAT, sizeof (warpverts_t), warpverts->xy);
glEnableClientState (GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer (2, GL_FLOAT, sizeof (warpverts_t), warpverts->st);
glDrawElements (GL_TRIANGLES, WARP_TESS * WARP_TESS * 6, GL_UNSIGNED_SHORT, warpindexes);
glDisableClientState (GL_VERTEX_ARRAY);
glDisableClientState (GL_VERTEX_ARRAY);
glPopMatrix ();
glMatrixMode (GL_MODELVIEW);
}
/*
================
R_RenderScene
r_refdef must be set before the first call
================
*/
void R_RenderScene (void)
{
R_SetupFrame ();
R_SetFrustum ();
R_BeginUnderwaterWarp ();
R_SetupGL ();
R_MarkLeaves (); // done here so we know if we're in water
R_DrawWorld (); // adds static entities to the list
S_ExtraUpdate (); // don't let sound get messed up if going slow
R_DrawEntitiesOnList ();
R_RenderDlights ();
R_DrawParticles ();
#ifdef GLTEST
Test_Draw ();
#endif
}
Code: Select all
void GL_Set2D (void)
{
glViewport (glx, gly, glwidth, glheight);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (0, vid.width, vid.height, 0, -99999, 99999);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glDisable (GL_DEPTH_TEST);
glDisable (GL_CULL_FACE);
glDisable (GL_BLEND);
glEnable (GL_ALPHA_TEST);
// glDisable (GL_ALPHA_TEST);
glColor4f (1, 1, 1, 1);
R_EndUnderwaterWarp ();
}