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;
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++;
}
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;
}
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;
}
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);
}
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];
}
}
}
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 ();
}
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.