Code dump - Fixed GLQuake Underwater Warp

Post tutorials on how to do certain tasks within game or engine code here.
Post Reply
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Code dump - Fixed GLQuake Underwater Warp

Post by mh »

Make of this what you will. There's likely a lot still to be worked out - correct operation with different viewsizes and optimization of those sin functions with a lookup table springs to mind.

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 ();
}
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Post Reply