Extending Quake Limits - Part 3: Alias Models

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

Extending Quake Limits - Part 3: Alias Models

Post by mh »

This one's for GLQuake only I'm afraid; I might study the software Quake code and see if something can be done there too at a later stage.

Some true evil lives in the alias model code: the format is horrible, the loader is disgusting, the renderer is awkward to work with. Every time you use the alias model code a homeless baby puppy dog dies somewhere.

Concept

We're going to accomplish a few things here. Firstly we're going to remove all limits on the number of vertexes and triangles in an alias model (unfortunately the number of frames requires a protocol change). Secondly we're going to give ourselves a much cleaner in-memory format (the on-disk format is beyond redemption I'm afraid). Thirdly we're going to have cleaner (and therefore more maintainable) elements in the loader. Fourthly we'll be doing the same for the renderer. Fifthly we're going to save on memory by only storing a single copy of each vertex.

For the sake of clarity and keeping things short I've omitted bbox correction, anything to do with coloured light or interpolation, and shadows. It should be obvious what's needed from reading this code.

I also need to give a heads-up that this is not the most efficient way to render an alias model (although it might be more efficient than GLQuake, depending on your hardware). The most efficient way involves vertex arrays and we might get a look at that as an addendum to this tutorial at some time. There's enough going on here as it is already!

The Code

Quite a bit of code here, so let's take it one step at a time. I might comment briefly after each one if the changes made require it.

Firstly we're going to gl_model.h and replacing our entire aliashdr_t struct with these two:

Code: Select all

typedef struct aliasmesh_s
{
	float s;
	float t;
	unsigned short vertindex;
} aliasmesh_t;

typedef struct aliashdr_s
{
	vec3_t		scale;
	vec3_t		scale_origin;

	float		boundingradius;
	synctype_t	synctype;
	int			flags;
	float		size;

	int			nummeshframes;
	int			numtris;
	int			numverts;

	int			vertsperframe;
	int			numframes;

	int			meshverts;
	int			vertexes;
	int			framevertexsize;

	int			skinwidth;
	int			skinheight;
	int			numskins;

	int					gl_texturenum[MAX_SKINS][4];
	int					texels[MAX_SKINS];	// only for player skins
	maliasframedesc_t	frames[1];
} aliashdr_t;
This is our new in-memory alias model format. Before we can get to use it we need to load the alias model, so in gl_model.c we go to the "ALIAS MODELS" comment header and replace all the horrible global variables with this:

Code: Select all

aliashdr_t *pheader;

void Mod_LoadFrameVerts (trivertx_t *verts)
{
	int i;
	int mark = Hunk_LowMark ();
	trivertx_t *vertexes = (trivertx_t *) Hunk_Alloc (pheader->vertsperframe * sizeof (trivertx_t));

	// this should only be done once and stores an offset to the first set of verts for the frame
	if (!pheader->vertexes) pheader->vertexes = (int) vertexes - (int) pheader;

	for (i = 0; i < pheader->vertsperframe; i++, vertexes++, verts++)
	{
		vertexes->lightnormalindex = verts->lightnormalindex;
		vertexes->v[0] = verts->v[0];
		vertexes->v[1] = verts->v[1];
		vertexes->v[2] = verts->v[2];
	}

	// hunk tags will mean that pheader->vertsperframe * sizeof (trivertx_t) is invalid for this
	// we only need to do this once but no harm in doing it every frame
	pheader->framevertexsize = Hunk_LowMark () - mark;

	pheader->nummeshframes++;
}
All this is doing is loading in each vertex individually and one-time only. We're also setting some info in the header - and note the trap I fell into while writing this!

Here's our new Mod_LoadAliasFrame:

Code: Select all

void *Mod_LoadAliasFrame (void *pin, maliasframedesc_t *frame)
{
	int				i;
	trivertx_t		*verts;
	daliasframe_t	*pdaliasframe;
	
	pdaliasframe = (daliasframe_t *) pin;

	strncpy (frame->name, pdaliasframe->name, 16);
	frame->firstpose = pheader->nummeshframes;
	frame->numposes = 1;

	for (i = 0; i < 3; i++)
	{
		frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i];
		frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i];
	}

	verts = (trivertx_t *) (pdaliasframe + 1);

	// load the frame vertexes
	Mod_LoadFrameVerts (verts);
	verts += pheader->vertsperframe;

	return (void *) verts;
}
And our new Mod_LoadAliasGroup:

Code: Select all

void *Mod_LoadAliasGroup (void *pin,  maliasframedesc_t *frame)
{
	daliasgroup_t		*pingroup;
	int					i, numframes;
	daliasinterval_t	*pin_intervals;
	void				*ptemp;

	pingroup = (daliasgroup_t *) pin;
	numframes = LittleLong (pingroup->numframes);
	frame->firstpose = pheader->nummeshframes;
	frame->numposes = numframes;

	for (i = 0; i < 3; i++)
	{
		frame->bboxmin.v[i] = pingroup->bboxmin.v[i];
		frame->bboxmax.v[i] = pingroup->bboxmax.v[i];
	}

	pin_intervals = (daliasinterval_t *) (pingroup + 1);
	frame->interval = LittleFloat (pin_intervals->interval);
	pin_intervals += numframes;
	ptemp = (void *) pin_intervals;

	for (i = 0; i < numframes; i++)
	{
		Mod_LoadFrameVerts ((trivertx_t *) ((daliasframe_t *) ptemp + 1));
		ptemp = (trivertx_t *) ((daliasframe_t *) ptemp + 1) + pheader->vertsperframe;
	}

	return ptemp;
}
Nothing much different here aside from calling Mod_LoadFrameVerts where required. Now we need to load the model itself, and as the format has changed the old Mod_LoadAliasModel also needs a few small changes. Rather than going through it line-by-line which would take forever, I'll just give the full function:

Code: Select all

void Mod_LoadAliasModel (model_t *mod, void *buffer)
{
	int					i, j;
	mdl_t				*pinmodel;
	stvert_t			*pinstverts;
	dtriangle_t			*pintriangles;
	int					version, numframes, numskins;
	int					size;
	daliasframetype_t	*pframetype;
	daliasskintype_t	*pskintype;
	int					start, end, total;

	start = Hunk_LowMark ();

	pinmodel = (mdl_t *) buffer;

	version = LittleLong (pinmodel->version);

	if (version != ALIAS_VERSION) Sys_Error ("%s has wrong version number (%i should be %i)", mod->name, version, ALIAS_VERSION);

	// allocate space for a working header
	pheader = Hunk_AllocName (sizeof (aliashdr_t) + LittleLong (pinmodel->numframes) * sizeof (maliasframedesc_t), loadname);

	mod->flags = LittleLong (pinmodel->flags);
	mod->type = mod_alias;

	// endian-adjust and copy the data, starting with the alias model header
	pheader->boundingradius = LittleFloat (pinmodel->boundingradius);
	pheader->numskins = LittleLong (pinmodel->numskins);
	pheader->skinwidth = LittleLong (pinmodel->skinwidth);
	pheader->skinheight = LittleLong (pinmodel->skinheight);
	pheader->vertsperframe = LittleLong (pinmodel->numverts);
	pheader->numtris = LittleLong (pinmodel->numtris);
	pheader->numframes = LittleLong (pinmodel->numframes);
	pheader->numverts = pheader->numtris * 3;

	// validate the setup
	// Sys_Error seems a little harsh here...
	if (pheader->numframes < 1) Host_Error ("Mod_LoadAliasModel: Invalid # of frames: %d\n", pheader->numframes);
	if (pheader->numtris <= 0) Host_Error ("model %s has no triangles", mod->name);
	if (pheader->vertsperframe <= 0) Host_Error ("model %s has no vertices", mod->name);

	pheader->size = LittleFloat (pinmodel->size) * ALIAS_BASE_SIZE_RATIO;
	mod->synctype = (synctype_t) LittleLong (pinmodel->synctype);
	mod->numframes = pheader->numframes;

	for (i = 0; i < 3; i++)
	{
		pheader->scale[i] = LittleFloat (pinmodel->scale[i]);
		pheader->scale_origin[i] = LittleFloat (pinmodel->scale_origin[i]);
	}

	// load the skins
	pskintype = (daliasskintype_t *) &pinmodel[1];
	pskintype = Mod_LoadAllSkins (pheader->numskins, pskintype);

	// load base s and t vertices
	pinstverts = (stvert_t *) pskintype;

	for (i = 0; i < pheader->vertsperframe; i++)
	{
		pinstverts[i].onseam = LittleLong (pinstverts[i].onseam);
		pinstverts[i].s = LittleLong (pinstverts[i].s);
		pinstverts[i].t = LittleLong (pinstverts[i].t);
	}

	// load triangle lists
	pintriangles = (dtriangle_t *) &pinstverts[pheader->vertsperframe];

	for (i = 0; i < pheader->numtris; i++)
	{
		pintriangles[i].facesfront = LittleLong (pintriangles[i].facesfront);

		pintriangles[i].vertindex[0] = LittleLong (pintriangles[i].vertindex[0]);
		pintriangles[i].vertindex[1] = LittleLong (pintriangles[i].vertindex[1]);
		pintriangles[i].vertindex[2] = LittleLong (pintriangles[i].vertindex[2]);
	}

	// load the frames
	pheader->nummeshframes = 0;
	pheader->vertexes = 0;
	pheader->framevertexsize = 0;
	pframetype = (daliasframetype_t *) &pintriangles[pheader->numtris];

	for (i = 0; i < pheader->numframes; i++)
	{
		aliasframetype_t frametype = LittleLong (pframetype->type);

		if (frametype == ALIAS_SINGLE)
			pframetype = (daliasframetype_t *) Mod_LoadAliasFrame (pframetype + 1, &pheader->frames[i]);
		else pframetype = (daliasframetype_t *) Mod_LoadAliasGroup (pframetype + 1, &pheader->frames[i]);
	}

	// FIXME: do this right
	mod->mins[0] = mod->mins[1] = mod->mins[2] = -16;
	mod->maxs[0] = mod->maxs[1] = mod->maxs[2] = 16;

	// build the draw lists
	GL_MakeAliasModelDisplayLists (pinstverts, pintriangles);

	// move the complete, relocatable alias model to the cache
	end = Hunk_LowMark ();
	total = end - start;
	Cache_Alloc (&mod->cache, total, loadname);
	if (!mod->cache.data) return;
	memcpy (mod->cache.data, pheader, total);
	Hunk_FreeToLowMark (start);
}
The real changes here are that instead of filling temporary buffers with stverts and triangles we're taking them directly from the disk version. Note also that we're feeding them into GL_MakeAliasModelDisplayLists, and setting up a few extra alias header members along the way.

Speaking of GL_MakeAliasModelDisplayLists, here's the entirety of our new gl_mesh.c file:

Code: Select all

#include "quakedef.h"


void GL_MakeAliasModelDisplayLists (stvert_t *stverts, dtriangle_t *triangles)
{
	int i, j;

	aliasmesh_t *mesh = (aliasmesh_t *) Hunk_Alloc (sizeof (aliasmesh_t) * pheader->numverts);
	pheader->meshverts = (int) mesh - (int) pheader;

	for (i = 0; i < pheader->numtris; i++)
	{
		for (j = 0; j < 3; j++, mesh++)
		{
			// emit s/t coords into the vert
			mesh->s = stverts[triangles[i].vertindex[j]].s;
			mesh->t = stverts[triangles[i].vertindex[j]].t;

			// check for back side and adjust texcoord s
			if (!triangles[i].facesfront && stverts[triangles[i].vertindex[j]].onseam) mesh->s += pheader->skinwidth / 2;

			// final s and t
			mesh->s = (mesh->s + 0.5) / pheader->skinwidth;
			mesh->t = (mesh->t + 0.5) / pheader->skinheight;

			// index into hdr->vertexes
			mesh->vertindex = triangles[i].vertindex[j];
		}
	}
}
At this stage we're definitely moving far away from the original code and getting a lot nicer. The in-memory format is now complete, so it's time to put them on-screen. This is all you need:

Code: Select all

void GL_DrawAliasFrame (aliashdr_t *paliashdr, int posenum)
{
	int i;
	float l;
	aliasmesh_t *mesh = (aliasmesh_t *) ((byte *) paliashdr + paliashdr->meshverts);
	trivertx_t *vert = (trivertx_t *) ((byte *) paliashdr + paliashdr->vertexes + paliashdr->framevertexsize * posenum);

	lastposenum = posenum;

	glBegin (GL_TRIANGLES);

	for (i = 0; i < paliashdr->numverts; i++, mesh++)
	{
		trivertx_t *trivert = &vert[mesh->vertindex];
		float l = shadedots[trivert->lightnormalindex] * shadelight;

		glTexCoord2f (mesh->s, mesh->t);
		glColor3f (l, l, l);
		glVertex3f (trivert->v[0], trivert->v[1], trivert->v[2]);
	}

	glEnd ();
}
And that is roundabout it. If anyone has any specific questions feel free to ask.

Cleaning Up

Nothing really to be done this time, although you may wish to get rid of the defines for the old maximums.

As I mentioned, popping this data into a vertex array would be more efficient, and I'm going to post the code required for that later on.
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
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

OK, I mentioned something about vertex arrays so here's the changes required to the above code. Using this method we can reduce the number of vertexes in the player model from 1224 to 317. Unfortunately though we're taking a 2 byte overhead on each one of those vertexes, but it's still almost a quarter of the vertex submission as before.

To clear up a misconception about vertex arrays:

If you go by the GLQuake source code you might think that vertex arrays are an extension that you need to check for, make header file changes for, use wglGetProcAddress on, and call the EXT versions of the functions. They're not. Vertex arrays are a core OpenGL 1.1 feature that are required to be supported and you don't need to do any of those things. If you can call glBindTexture without doing those, you can also call glVertexPointer.

So here's our new GL_MakeAliasModelDisplayLists:

Code: Select all

void GL_MakeAliasModelDisplayLists (stvert_t *stverts, dtriangle_t *triangles)
{
	int i, j;
	aliasmesh_t *hunkmesh;

	// there can never be more than this number of verts
	aliasmesh_t *mesh = (aliasmesh_t *) malloc (sizeof (aliasmesh_t) * pheader->numverts);

	// there will always be this number of indexes
	unsigned short *indexes = (unsigned short *) Hunk_Alloc (sizeof (unsigned short) * pheader->numtris * 3);

	pheader->indexes = (int) indexes - (int) pheader;
	pheader->numindexes = 0;
	pheader->numverts = 0;

	for (i = 0; i < pheader->numtris; i++)
	{
		for (j = 0; j < 3; j++)
		{
			int v;

			// index into hdr->vertexes
			unsigned short vertindex = triangles[i].vertindex[j];

			// basic s/t coords
			int s = stverts[vertindex].s;
			int t = stverts[vertindex].t;

			// check for back side and adjust texcoord s
			if (!triangles[i].facesfront && stverts[vertindex].onseam) s += pheader->skinwidth / 2;

			// see does this vert already exist
			for (v = 0; v < pheader->numverts; v++)
			{
				// it could use the same xyz but have different s and t
				if (mesh[v].vertindex == vertindex && (int) mesh[v].st[0] == s && (int) mesh[v].st[1] == t)
				{
					// exists; emit an index for it
					indexes[pheader->numindexes++] = v;

					// no need to check any more
					break;
				}
			}

			if (v == pheader->numverts)
			{
				// doesn't exist; emit a new vert and index
				indexes[pheader->numindexes++] = pheader->numverts;

				mesh[pheader->numverts].vertindex = vertindex;
				mesh[pheader->numverts].st[0] = s;
				mesh[pheader->numverts++].st[1] = t;
			}
		}
	}

	// create a hunk buffer for the final mesh we'll actually use
	hunkmesh = (aliasmesh_t *) Hunk_Alloc (sizeof (aliasmesh_t) * pheader->numverts);
	pheader->meshverts = (int) hunkmesh - (int) pheader;

	// tidy up the verts by calculating final s and t and copying out to the hunk
	for (i = 0; i < pheader->numverts; i++)
	{
		hunkmesh[i].vertindex = mesh[i].vertindex;
		hunkmesh[i].st[0] = ((float) mesh[i].st[0] + 0.5f) / (float) pheader->skinwidth;
		hunkmesh[i].st[1] = ((float) mesh[i].st[1] + 0.5f) / (float) pheader->skinheight;
	}

	// don't forget!!!
	free (mesh);
}
And here's our new GL_DrawAliasFrame:

Code: Select all

void GL_DrawAliasFrame (aliashdr_t *paliashdr, int posenum)
{
	int i;
	float l;
	aliasmesh_t *mesh = (aliasmesh_t *) ((byte *) paliashdr + paliashdr->meshverts);
	trivertx_t *vert = (trivertx_t *) ((byte *) paliashdr + paliashdr->vertexes + paliashdr->framevertexsize * posenum);

	lastposenum = posenum;

	glEnableClientState (GL_VERTEX_ARRAY);
	glEnableClientState (GL_COLOR_ARRAY);
	glEnableClientState (GL_TEXTURE_COORD_ARRAY);

	glVertexPointer (3, GL_FLOAT, sizeof (aliasmesh_t), mesh->v);
	glColorPointer (3, GL_FLOAT, sizeof (aliasmesh_t), mesh->color);
	glTexCoordPointer (2, GL_FLOAT, sizeof (aliasmesh_t), mesh->st);

	for (i = 0; i < paliashdr->numverts; i++, mesh++)
	{
		trivertx_t *trivert = &vert[mesh->vertindex];

		mesh->color[0] = mesh->color[1] = mesh->color[2] = shadedots[trivert->lightnormalindex] * shadelight;
		mesh->v[0] = trivert->v[0];
		mesh->v[1] = trivert->v[1];
		mesh->v[2] = trivert->v[2];
	}

	glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, (unsigned short *) ((byte *) paliashdr + paliashdr->indexes));

	glDisableClientState (GL_VERTEX_ARRAY);
	glDisableClientState (GL_COLOR_ARRAY);
	glDisableClientState (GL_TEXTURE_COORD_ARRAY);
}
And here's the new aliasmesh_t struct:

Code: Select all

typedef struct aliasmesh_s
{
	float v[3];
	float color[3];
	float st[2];
	unsigned short vertindex;
} aliasmesh_t;
And these two get added to aliashdr_t (anywhere you want except after frames!):

Code: Select all

	int			indexes;
	int			numindexes;
Some points:
  • We're using 16 bit indexes here so this reduces the max triangles in an alias model from unlimited to 21845. There's nothing to stop us from using 32 bit indexes, but they're not supported on all hardware and OpenGL might put your code through a software emulation path.
  • The method used to check for reused vertexes is a little brute-force, but it shouldn't cause any trouble with loading times.
  • numindexes will always be the same as numtris * 3 so you could in theory do away with it.
  • Notice once again that I'm using offsets rather than pointers; we can't use pointers here because when the model is moved to the cache it's pointer values will become invalid (they'll still be pointing at the old hunk location).
  • I need to malloc a temporary buffer for holding verts because I don't know in adavnce how big it's going to be (I do know it's max possible size though).
  • aliasmesh_t has been bloated by 6 floats but it's still more memory-efficient than GLQuake was.
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
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

the max verts is actually numverts*2 due to the facesfront thing.
that or numtris*3. whichever is the smaller.
just numverts is not correct.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

I set numverts to numtris * 3 in the loader so it's actually correct going in there. :wink:
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
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

theres a freshly compiled standard gltochris with these changes on my ftp server.

works like a champ good job mh ;)
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

ouch actually ran into one problem after the succes i tried adding lit support to tochris gl port well so far no luck multicolored dots all over and the tut from original qsg site is missing bits and pieces it seems :(

i tried adding in the rest by studying qip quake and others but somethings a mess :lol:

also not sure my own hackery is sound so ill leave my modified section of it here comments are velcome.

Code: Select all

/*
=============================================================

  ALIAS MODELS modified with mh code ;)

=============================================================
*/

vec3_t	shadevector;
vec3_t	shadelight;

// precalculated dot products for quantized angles
#define SHADEDOT_QUANT 16
float	r_avertexnormal_dots[SHADEDOT_QUANT][256] =
#include "anorm_dots.h"
;

float	*shadedots = r_avertexnormal_dots[0];

/*
=============
GL_DrawAliasFrame

Comments ?
=============
*/
void GL_DrawAliasFrame (aliashdr_t *paliashdr, int pose1, int pose2) 
{ 
	int			i, j; 
	aliasmesh_t *mesh = (aliasmesh_t *) ((byte *) paliashdr + paliashdr->meshverts); 
	trivertx_t	*vert = (trivertx_t *) ((byte *) paliashdr + paliashdr->vertexes + paliashdr->framevertexsize * pose1); 
	trivertx_t	*lvert = (trivertx_t *) ((byte *) paliashdr + paliashdr->vertexes + paliashdr->framevertexsize * pose2); 

	glEnableClientState (GL_VERTEX_ARRAY); 
	glEnableClientState (GL_COLOR_ARRAY); 
	glEnableClientState (GL_TEXTURE_COORD_ARRAY); 

	glVertexPointer (3, GL_FLOAT, sizeof (aliasmesh_t), mesh->v); 
	glColorPointer (3, GL_FLOAT, sizeof (aliasmesh_t), mesh->color); 
	glTexCoordPointer (2, GL_FLOAT, sizeof (aliasmesh_t), mesh->st); 

	for (i = 0; i < paliashdr->numverts; i++, mesh++) 
	{ 
		trivertx_t *trivert = &vert[mesh->vertindex];
		trivertx_t *ltrivert = &lvert[mesh->vertindex]; 

		for (j=0; j<3; j++)
		{
			mesh->color[j] = shadedots[trivert->lightnormalindex] + (shadedots[ltrivert->lightnormalindex] - shadedots[trivert->lightnormalindex]) * r_framelerp;		
		}
		bound(0, mesh->color[0] * shadelight[0], 1);
		bound(0, mesh->color[1] * shadelight[1], 1);
		bound(0, mesh->color[2] * shadelight[2], 1);

		mesh->v[0] = (trivert->v[0] + (ltrivert->v[0] - trivert->v[0]) * r_framelerp) * paliashdr->scale[0] + paliashdr->scale_origin[0];
		mesh->v[1] = (trivert->v[1] + (ltrivert->v[1] - trivert->v[1]) * r_framelerp) * paliashdr->scale[1] + paliashdr->scale_origin[1];
		mesh->v[2] = (trivert->v[2] + (ltrivert->v[2] - trivert->v[2]) * r_framelerp) * paliashdr->scale[2] + paliashdr->scale_origin[2];
	}
	glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, (unsigned short *) ((byte *) paliashdr + paliashdr->indexes)); 

	glDisableClientState (GL_VERTEX_ARRAY); 
	glDisableClientState (GL_COLOR_ARRAY); 
	glDisableClientState (GL_TEXTURE_COORD_ARRAY); 
}

/*
=============
GL_DrawAliasShadow

Well ill be damned worked on the first try :P
=============
*/
extern	vec3_t			lightspot;

void GL_DrawAliasShadow (aliashdr_t *paliashdr, int pose1, int pose2)
{
	int			i;
	vec3_t		end;
	trace_t		tr;
	aliasmesh_t *mesh = (aliasmesh_t *) ((byte *) paliashdr + paliashdr->meshverts); 
	trivertx_t	*vert = (trivertx_t *) ((byte *) paliashdr + paliashdr->vertexes + paliashdr->framevertexsize * pose1); 
	trivertx_t	*lvert = (trivertx_t *) ((byte *) paliashdr + paliashdr->vertexes + paliashdr->framevertexsize * pose2); 
	float		height, lheight;

	VectorSet (end, currententity->origin[0], currententity->origin[1], currententity->origin[2] - 4096);

	tr = CL_TraceLine (currententity->origin, vec3_origin, vec3_origin, end, -1);

	if (tr.fraction == 1.0f) return;

	lheight = currententity->origin[2] - tr.endpos[2];

	height = 0;

	glEnableClientState (GL_VERTEX_ARRAY); 
	glEnableClientState (GL_TEXTURE_COORD_ARRAY); 

	glVertexPointer (3, GL_FLOAT, sizeof (aliasmesh_t), mesh->v); 
	glTexCoordPointer (2, GL_FLOAT, sizeof (aliasmesh_t), mesh->st); 

	height = -lheight + 0.1;

	for (i = 0; i < paliashdr->numverts; i++, mesh++) 
	{
		trivertx_t *trivert = &vert[mesh->vertindex];
		trivertx_t *ltrivert = &lvert[mesh->vertindex]; 

		mesh->v[0] = (trivert->v[0] + (ltrivert->v[0] - trivert->v[0]) * r_framelerp) * paliashdr->scale[0] + paliashdr->scale_origin[0];
		mesh->v[1] = (trivert->v[1] + (ltrivert->v[1] - trivert->v[1]) * r_framelerp) * paliashdr->scale[1] + paliashdr->scale_origin[1];
		mesh->v[2] = (trivert->v[2] + (ltrivert->v[2] - trivert->v[2]) * r_framelerp) * paliashdr->scale[2] + paliashdr->scale_origin[2];

		mesh->v[0] = (mesh->v[0] - shadevector[0]*(mesh->v[2]+lheight));
		mesh->v[1] = (mesh->v[1] - shadevector[1]*(mesh->v[2]+lheight));
		mesh->v[2] = height;
	}
	glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, (unsigned short *) ((byte *) paliashdr + paliashdr->indexes)); 

	glDisableClientState (GL_VERTEX_ARRAY); 
	glDisableClientState (GL_TEXTURE_COORD_ARRAY); 
}

/*
=================
R_SetupAliasFrame
=================
*/
void R_SetupAliasFrame (maliasframedesc_t *oldframe, maliasframedesc_t *frame, aliashdr_t *paliashdr)
{
	int				pose, oldpose, numposes;
	float			interval;

	oldpose = oldframe->firstpose;
	numposes = oldframe->numposes;

	if (numposes > 1)
	{
		interval = oldframe->interval;
		oldpose += (int)(r_refdef.oldtime / interval) % numposes;
	}
	pose = frame->firstpose;
	numposes = frame->numposes;

	if (numposes > 1)
	{
		interval = frame->interval;
		pose += (int)(r_refdef.time / interval) % numposes;
	}
	GL_DrawAliasFrame (paliashdr, oldpose, pose);
}



/*
=================
R_DrawAliasModel
=================
*/
void R_DrawAliasModel (entity_t *e)
{
	int					i;
	int					lnum;
	vec3_t				dist;
	float				add;
	model_t				*clmodel = e->model;
	aliashdr_t			*paliashdr;
	int					anim;
	maliasframedesc_t	*oldframe, *frame;

	//
	// locate the proper data
	//
	paliashdr = (aliashdr_t *)Mod_Extradata (clmodel);

	if ((e->frame >= paliashdr->numframes) || (e->frame < 0))
	{
		Con_DPrintf ("R_AliasSetupFrame: no such frame %d\n", e->frame);
		e->frame = 0;
	}

	if ((e->oldframe >= paliashdr->numframes) || (e->oldframe < 0))
	{
		Con_DPrintf ("R_AliasSetupFrame: no such frame %d\n", e->oldframe);
		e->oldframe = 0;
	}
	frame = &paliashdr->frames[e->frame];
	oldframe = &paliashdr->frames[e->oldframe];

	if (!(e->flags & RF_WEAPONMODEL))
	{
		if (e->angles[0] || e->angles[1] || e->angles[2])
		{
			if (R_CullSphere (e->origin, max (oldframe->radius, frame->radius))) return;
		}
		else
		{
			vec3_t	mins, maxs;

			for (i = 0; i < 3; i++)
			{
				mins[i] = e->origin[i] + min (oldframe->bboxmin.v[i], frame->bboxmin.v[i]);
				maxs[i] = e->origin[i] + max (oldframe->bboxmax.v[i], frame->bboxmax.v[i]);
			}

			if (R_CullBox (mins, maxs))	return;
		}
	}
	VectorCopy (e->origin, r_entorigin);
	VectorSubtract (r_origin, r_entorigin, modelorg);

	//
	// get lighting information
	//
	if (e->flags & RF_FULLBRIGHT)
	{
		shadelight[0] = shadelight[1] = shadelight[2] = 255;
	}
	else
	{
		if (!(r_refdef.flags & RDF_NOWORLDMODEL))
			shadelight[0] = shadelight[1] = shadelight[2] = R_LightPoint (e->origin);
		else
			shadelight[0] = shadelight[1] = shadelight[2] = 0;

		// always give some light
		if (e->flags & RF_MINLIGHT)
		{
			shadelight[0] = shadelight[1] = shadelight[2] = 24;
		}

		for (lnum=0 ; lnum<r_refdef.num_dlights ; lnum++)
		{
			VectorSubtract (e->origin, r_refdef.dlights[lnum].origin, dist);
			add = r_refdef.dlights[lnum].radius - VectorLength(dist);

			if (add > 0) 
			{
				//ZOID models should be affected by dlights as well
				shadelight[0] += add * r_refdef.dlights[lnum].color[0];
				shadelight[1] += add * r_refdef.dlights[lnum].color[1];
				shadelight[2] += add * r_refdef.dlights[lnum].color[2];
			}
		}
	}

	// HACK HACK HACK -- no fullbright colors, so make torches full light
	if (!strcmp (clmodel->name, "progs/flame2.mdl")	|| !strcmp (clmodel->name, "progs/flame.mdl") )
	{
		shadelight[0] = shadelight[1] = shadelight[2] = 255;
	}
	shadedots = r_avertexnormal_dots[((int)(e->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];
	VectorScale(shadelight, 1.0f / 200.0f, shadelight);

	c_alias_polys += paliashdr->numtris;

	//
	// draw all the triangles
	//

	GL_DisableMultitexture();

    glPushMatrix ();
	R_RotateForEntity (e);

	if (e->flags & RF_DEPTHSCALE) 
	{
		glDepthRange (gldepthmin, gldepthmin + 0.3*(gldepthmax-gldepthmin));
	}

	if (e->model->synctype == ST_RAND)
		anim = (int)((r_refdef.time + e->syncbase) * 10) & 3;
	else
		anim = (int)(r_refdef.time * 10) & 3;

    GL_Bind(paliashdr->gl_texturenum[e->skinnum][anim]);

	// we can't dynamically colormap textures, so they are cached
	// seperately for the players. Heads are just uncolored.
	if (e->colormap != vid.colormap && !gl_nocolors.value)
	{
		i = e->number;

		if (i >= 0 && i<=Com_ClientMaxclients())
		{
		    GL_Bind(playertextures + i);
		}
	}

	if (gl_smoothmodels.value)
		glShadeModel (GL_SMOOTH);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	if (gl_affinemodels.value)
		glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);

	if (!r_lerpmodels.value) 
		r_framelerp = 1.0f;
	else
		r_framelerp = bound (0, e->framelerp, 1);

	R_SetupAliasFrame (oldframe, frame, paliashdr);

	if (e->flags & RF_DEPTHSCALE) 
	{
		glDepthRange (gldepthmin, gldepthmax);
	}

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

	glShadeModel (GL_FLAT);

	if (gl_affinemodels.value)
	{
		glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	}
	glPopMatrix ();

	if (r_shadows.value && !(e->flags & RF_NOSHADOW))
	{
		float an = -e->angles[1]/180*M_PI;
		shadevector[0] = cos(an);
		shadevector[1] = sin(an);
		shadevector[2] = 1;
		VectorNormalize (shadevector);

		glPushMatrix ();
		glTranslatef (e->origin[0], e->origin[1], e->origin[2]);
		glRotatef (e->angles[1], 0, 0, 1);
		glDisable (GL_TEXTURE_2D);
		glEnable (GL_BLEND);
		glColor4f (0,0,0,0.5);
		GL_DrawAliasShadow (paliashdr, e->oldframe, e->frame);
		glEnable (GL_TEXTURE_2D);
		glDisable (GL_BLEND);
		glColor4f (1,1,1,1);
		glPopMatrix ();
	}

}
maybe a bugger on my side you newer know ;)
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Post by JasonX »

Do you have any news on the software version of this? :roll:
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

As in software rendering? Someone who knows more about the software renderer than I do would need to build that; the code is completely different (and large chunks of it in asm).
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
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

hmm had another go at his one but im running into something rather weird.

lightnormalindex crashes as a bad pointer ? and heres the real weird thing.

lightnormalindex is valid untill i try to fire a gun whack an axe etc :shock:

what on earth can cause that ? i didnt touch anything besides changing this ->

Code: Select all

/*
=================
Mod_LoadAliasFrame
=================
*/
void *Mod_LoadAliasFrame (void *pin, maliasframedesc_t *frame) 
{ 
   int				i; 
   trivertx_t		*verts; 
   daliasframe_t	*pdaliasframe; 
	
   pdaliasframe = (daliasframe_t *) pin; 

   strcpy (frame->name, pdaliasframe->name); 
   frame->firstpose = pheader->nummeshframes; 
   frame->numposes = 1; 

   for (i = 0; i < 3; i++) 
   { 
		frame->bboxmin[i] = pdaliasframe->bboxmin.v[i] * pheader->scale[i] + pheader->scale_origin[i];
		frame->bboxmax[i] = pdaliasframe->bboxmax.v[i] * pheader->scale[i] + pheader->scale_origin[i];

		frame->radius = RadiusFromBounds (frame->bboxmin, frame->bboxmax);

		loadmodel->mins[i] = min (loadmodel->mins[i], frame->bboxmin[i]);
		loadmodel->mins[i] = max (loadmodel->maxs[i], frame->bboxmax[i]);
   }
   verts = (trivertx_t *) (pdaliasframe + 1); 

   // load the frame vertexes 
   Mod_LoadFrameVerts (verts); 

   verts += pheader->vertsperframe; 

   return (void *) verts; 
}

/*
=================
Mod_LoadAliasGroup
=================
*/
void *Mod_LoadAliasGroup (void *pin,  maliasframedesc_t *frame) 
{ 
   daliasgroup_t		*pingroup; 
   int					i, numframes; 
   daliasinterval_t		*pin_intervals; 
   void					*ptemp;

   pingroup = (daliasgroup_t *) pin; 
   numframes = LittleLong (pingroup->numframes); 
   frame->firstpose = pheader->nummeshframes; 
   frame->numposes = numframes; 

   for (i = 0; i < 3; i++) 
   { 
	   frame->bboxmin[i] = pingroup->bboxmin.v[i] * pheader->scale[i] + pheader->scale_origin[i];
	   frame->bboxmax[i] = pingroup->bboxmax.v[i] * pheader->scale[i] + pheader->scale_origin[i];

	   frame->radius = RadiusFromBounds (frame->bboxmin, frame->bboxmax);

	   loadmodel->mins[i] = min (loadmodel->mins[i], frame->bboxmin[i]);
	   loadmodel->mins[i] = max (loadmodel->maxs[i], frame->bboxmax[i]);
   } 
   pin_intervals = (daliasinterval_t *) (pingroup + 1); 
   frame->interval = LittleFloat (pin_intervals->interval); 
   pin_intervals += numframes; 
   ptemp = (void *) pin_intervals; 

   for (i = 0; i < numframes; i++) 
   { 
	  Mod_LoadFrameVerts ((trivertx_t *) ((daliasframe_t *) ptemp + 1)); 

	  ptemp = (trivertx_t *) ((daliasframe_t *) ptemp + 1) + pheader->vertsperframe; 
   } 
   return ptemp; 
}
to fit the former code in tochris
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

you mentioned something about a protocol change ? not sure if realted to the problem im facing since no matter what i do it simply refuses to use lightnormalindex.

i get data in pretty much all the verts up untill it hits that then boom

lightnormalindex=??? <Bad Ptr> hmm :/

my heads spinning allready trying to figure out where it gets its data from (i guess the model) but somewhere this information seems to go lost.
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

doh forget the bug allthough its good for a laugh.

i forgot to remove a pak containing some updated weapon models.

now the funny thing is these are actually mdl but with a higher polycount and they are skinned. well guess what yup i hadnt got around to do external texture support yet but its somewhat funny that the debugger blames lightnormalindex :lol:
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Nothing useful to say except I'm going to try this soon.

Looks like a very great rewrite.

I like how gl_mesh.c gets streamlined and how draw alias frame gets streamlined.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

I highly recommend building per-frame bounding boxes on the client too. You can then interpolate between two bounding boxes and get much better culling.

For true hardcore performance, build 2 VBOs. VBO 1 contains the position data, VBO 2 contains the texcoord data. glVertexPointer to (void *) (0 + pose1 * sizeof (float) * 3) in VBO 1, glTexCoordPointer to (void *) (0 + pose2 * sizeof (float) * 3) in VBO 1 (it's not really a texcoord and using generic vertex attribs would be less confusing here) and glTexCoordPointer to (void *) 0 in VBO 2. Find another way to handle colour (e.g. by using proper normals and a software Quake like system), send blend factors as uniforms and interpolate in a vertex shader.

If you can use UBYTE4 data you can realize that positions are stored as bytes in the MDL so you just need to store them as bytes in your VBO too. Cuts video RAM using to a third.
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
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

allready extremely fast (level load is so fast i newer see it actually loading) :D

on the other hand im having a heap of problems getting tochris to actually accept stuff like lit support (uses a server client alike system bit like quake2's) so the light data is actually precalculated clientside. but the function for that does not take a color variable so i had to modify it a bit.

also need to get rid of the old sequentialpoly stuff in gl_rsurf.c and then change stuff to vertex arrays in that to.

i allready changed to old SGIS calls to RGBA (should probably try to adopt the BGRA path).
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Just getting rid of sequential poly on it's own isn't enough; you also need to sort properly by texture AND lightmap to get as many polygons as possible between each state change (this assumes multitexture). That's critical; GLQuake is deeply criminal in calling glBegin/glEnd in tight inner loops in so many places and that's a huge perf drain. BGRA is critical, and so is moving your lightmap updates out of the main rendering loop. GLQuake also does the worst possible thing ever here:

- draw with a lightmap
- stall the pipeline so that the draw can finish before updating
- update a tiny subrect of it
- stall the pipeline so that the update can finish before drawing with it again

Several hundred or thousand times per frame. Again, multitexture path only. (Is there any point in even supporting a single texture path in a modern engine? If you've got a Voodoo 2 or better you've got multitexture. Dump the legacy, clean up your code, give yourself less headaches, less bugfixing and less code duplication.)

Any engine which follows GLQuake closely enough in this part of it's code invariably ends up doing the very same thing, which is one of the reasons why some engines are known to get horrifically slow when dynamic lights go off. (Another reason is using GL_RGB for the lightmap format - OpenGL needs to convert this to BGRA in software before it can upload. Skip the conversion step and just use BGRA natively. And no, BGR isn't enough. Textures are 4 components, GPUs can't address in groups of 24 bits. GL_UNSIGNED_INT_8_8_8_8_REV instead of GL_UNSIGNED_BYTE will get you a direct DMA transfer, but you only notice the perf gain on really bad hardware as BGRA on it's own removes 99% of the bottleneck in texture uploads on anything halfway decent.)

Break away from that crap and you likely don't even need vertex arrays. They'll definitely get you more performance, but you may find that you're fast enough for your own needs without them.

Another VBO-related trick. Put an entire brush model into a VBO. Sorted by texture and lightmap. Draw the entire thing in a coupla calls (you'll need glDrawElements for that). Don't bother with backface culling (SURF_PLANEBACK stuff); the GPU will do that for you anyway, and since the data is already on the GPU you're not saving bandwidth by culling surfaces. You do spend some extra on the vertex pipeline, and a handful of extra bytes for command buffer entries, but on balance it's about 1.5 to 2 times as fast in scenes with heavy use of brush models.

You should pick a cutoff point here where you fall back on the old way; something like models with less than 20 surfs in them worked out good in my own tests.
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