Skip to content

Instantly share code, notes, and snippets.

@jamesu
Created March 18, 2009 22:21
Show Gist options
  • Save jamesu/81445 to your computer and use it in GitHub Desktop.
Save jamesu/81445 to your computer and use it in GitHub Desktop.
/*
Hey there,
I writ this code a few months ago, in order to familiarize myself with the quake3 code. Rather than let it rot away in the code dungeon, i thought you guy's might find some use for it.
The following modification allows Quake3 to load Quake1 MDL's and Quake2 MD2's. This is done at runtime via a step-by-step in-memory conversion process, which converts the source models into Quake3 MD3's.
I got the MD2 conversion going on pretty well - successfully loaded in some Quake2 models complete with original textures, although my testing has been limited as i am not too familiar with the game code (which would allow me to try out replacing actual player models).
The MDL converter does not work as well (does not convert skins and has scaling issues), but i was still able to display the Quake1 player model in-game as a powerup.
Neither converters convert tag's since they do not exist in the MDL or MD2 formats (unless i missed something obvious). However, the code is quite straight forward, so i have no doubt that someone would be able to hack support in for any missing features.
Due to the structure of both formats, i had to write a converter to remap all of the triangle and vertex/texcoord references into one continuous list for the md3 surfaces. Depending on the amount of triangles in your source meshes, there may be more than one md3 surface generated.
Enough with the details, here is the code.
*/
//!! In code/qcommon/qfiles.h, just after " } TargaHeader;", add the following:
/*
==============================================================================
.MDL triangle model file format
==============================================================================
*/
#define MDL_IDENT (('O'<<24)+('P'<<16)+('D'<<8)+'I')
#define MDL_VERSION 6
#define MDL_ONSEAM 0x0020
#define MDL_DT_FACES_FRONT 0x0010
#define MDL_MAX_SKINS 32
#define MDL_MAXVERTS 1024
#define MDL_MAXFRAMES 256
#define MDL_MAXTRIS 2048
#define ALIAS_BASE_SIZE_RATIO (1.0 / 11.0)
typedef enum { MDL_SINGLE=0, MDL_GROUP } mdlFrametype_t;
typedef enum { MDL_SKIN_SINGLE=0, MDL_SKIN_GROUP } mdlSkintype_t;
typedef enum {ST_SYNC=0, ST_RAND } mdlSynctype_t;
// structs below here are figured out
typedef struct
{
mdlSkintype_t type;
void *pcachespot;
int skin;
} mdlSkinDesc_t;
typedef struct
{
int numskins;
int intervals;
mdlSkinDesc_t skindescs[1];
} mdlSkingroup_t;
// NOTE: the following could be typedef type instead?
typedef struct {
int numskins;
} dmdlSkingroup_t;
typedef struct {
float interval;
} dmdlInterval_t;
typedef struct {
float interval;
} dmdlSkininterval_t;
typedef struct {
mdlFrametype_t type;
} dmdlFrametype_t;
typedef struct {
mdlSkintype_t type;
} dmdlSkintype_t;
// NOTE: later versions have these as shorts, then floats
typedef struct {
int onseam;
int s;
int t;
} mdlSt_t; //
typedef struct
{
byte v[3];
byte lightnormalindex;
} mdlTrivertx_t;
typedef struct {
int numframes;
mdlTrivertx_t bboxmin; // lightnormal isn't used
mdlTrivertx_t bboxmax; // lightnormal isn't used
} mdlGroup_t;
typedef struct {
mdlTrivertx_t bboxmin; // lightnormal isn't used
mdlTrivertx_t bboxmax; // lightnormal isn't used
char name[16]; // frame name from grabbing
} mdlFrame_t;
typedef struct {
int facesfront;
int vertindex[3];
} mdlTriangle_t; //
typedef struct {
int ident;
int version;
vec3_t scale;
vec3_t scale_origin;
float boundingradius;
vec3_t eyeposition;
int numskins;
int skinwidth;
int skinheight;
int numverts;
int numtris;
int numframes;
mdlSynctype_t synctype;
int flags;
float size;
} mdlHeader_t; //
/*
========================================================================
.MD2 triangle model file format
========================================================================
*/
#define MD2_IDENT (('2'<<24)+('P'<<16)+('D'<<8)+'I')
#define MD2_VERSION 8
#define MD2_MAX_TRIANGLES 4096
#define MD2_MAX_VERTS 2048
#define MD2_MAX_FRAMES 512
#define MD2_MAX_SKINS 32
#define MD2_MAX_SKINNAME 64
typedef struct
{
short s;
short t;
} md2St_t;
typedef struct
{
short index_xyz[3];
short index_st[3];
} md2Triangle_t;
typedef struct
{
byte v[3]; // scaled byte to fit in frame mins/maxs
byte lightnormalindex;
} md2Trivertx_t;
#define MD2_TRIVERTX_V0 0
#define MD2_TRIVERTX_V1 1
#define MD2_TRIVERTX_V2 2
#define MD2_TRIVERTX_LNI 3
#define MD2_TRIVERTX_SIZE 4
typedef struct
{
float scale[3]; // multiply byte verts by this
float translate[3]; // then add this
char name[16]; // frame name from grabbing
md2Trivertx_t verts[1]; // variable sized
} md2Frame_t;
// the glcmd format:
// a positive integer starts a tristrip command, followed by that many
// vertex structures.
// a negative integer starts a trifan command, followed by -x vertexes
// a zero indicates the end of the command list.
// a vertex consists of a floating point s, a floating point t,
// and an integer vertex index.
typedef struct
{
int ident;
int version;
int skinwidth;
int skinheight;
int framesize; // byte size of each frame
int numSkins;
int numVerts;
int numSt; // greater than num_xyz for seams
int numTriangles;
int numGLcmds; // dwords in strip/fan command list
int numFrames;
int ofsSkins; // each skin is a MAX_SKINNAME string
int ofsSt; // byte offset from start for stverts
int ofsTris; // offset for dtriangles
int ofsFrames; // offset for first frame
int ofsGLcmds;
int ofsEnd; // end of file
} md2Header_t;
//!! In code/renderer/tr_model.c, add the following after "#define LL(x) x=LittleLong(x)":
#define LS(x) x=LittleShort(x)
#define LF(x) x=LittleFloat(x)
static qboolean R_LoadMDL (model_t *mod, int lod, void *buffer, const char *name );
static qboolean R_LoadMD2 (model_t *mod, int lod, void *buffer, const char *name );
//!! We also need to fix RE_RegisterModel in order to allow for the MDl/MD2 files to be loaded.
//!! just above "if ( lod != 0 ) {", add the following:
// Use lod's only for md3's
if ( lod == 1)
{
const char *ext = strrchr(filename, '.');
if (ext && strcmp(ext, ".md3"))
break;
}
// Hook in lod names
//!! Then replace everything between "ident = LittleLong(*(unsigned *)buf);" and "ri.FS_FreeFile (buf);" with the following:
switch (ident)
{
case MD3_IDENT:
loaded = R_LoadMD3( mod, lod, buf, name );
break;
case MD4_IDENT:
loaded = R_LoadMD4( mod, buf, name );
break;
case MD2_IDENT:
loaded = R_LoadMD2( mod, lod, buf, name );
break;
case MDL_IDENT:
loaded = R_LoadMDL( mod, lod, buf, name);
break;
default:
ri.Printf (PRINT_WARNING,"RE_RegisterModel: unknown fileid for %s\n", name);
goto fail;
}
//!! In the same file, we can now add in the model loading functions, as follows:
// JamesU - MDL/MD2 loading code
/*
=================
Alias model vertex map
Structures hold a list of triangle indices pointing to originals, grouped by surfaces for MD3 models.
Functions build this list from a set of MD2 triangle indices.
NOTE:
Structures and the routines could be optimized to minimize memory usage.
An initialized vertex map requires around 87kb of temporary memory (or 562kb with 32 surfaces).
=================
*/
#define ALIAS_VERTEXMAP_MAX_SURFACES 5 // Realistic maximum surfaces we are going to generate from an MD2
typedef struct {
short verts[SHADER_MAX_VERTEXES]; // pointers to vertex_map (no duplicates)
short inds[SHADER_MAX_INDEXES]; // pointers to verts (in triangles)
short nverts; // number of verts
short ninds; // number of inds
} aliasSurfaceVerts_t;
typedef struct {
short vertex_map[ALIAS_VERTEXMAP_MAX_SURFACES * SHADER_MAX_VERTEXES]; // map of new texcoords -> old vertex
short vertex_st[ALIAS_VERTEXMAP_MAX_SURFACES * SHADER_MAX_VERTEXES]; // map of new texcoords -> old texcoord
aliasSurfaceVerts_t new_surfaces[ALIAS_VERTEXMAP_MAX_SURFACES]; // list of surfaces with split vertex lists
short nverts; // # of vertexes used in vertex_map and new_st arrays
short nsurfs; // # of indices used in surface_split
} aliasVertexmap_t;
qboolean R_VMAPAddExisting(aliasVertexmap_t *vmap, short nvtx_new) {
aliasSurfaceVerts_t *surf = &vmap->new_surfaces[vmap->nsurfs];
aliasSurfaceVerts_t *splitsurf = NULL;
int surf_idx; // index of vertex to add to inds list
// Check vertexes
for (surf_idx=0; surf_idx<surf->nverts; surf_idx++)
{
if (surf->verts[surf_idx] == nvtx_new)
break;
}
if (surf_idx == surf->nverts)
{
// New vertex referenced
if (surf->nverts == SHADER_MAX_VERTEXES)
{
if (vmap->nsurfs == ALIAS_VERTEXMAP_MAX_SURFACES)
surf = NULL;
else
{
// fix splits
int splitat = surf->ninds % 3;
if (splitat != 0)
{
splitsurf = surf;
splitsurf->ninds -= splitat;
surf++;
vmap->nsurfs++;
surf->ninds = splitat;
surf->nverts = 1;
surf_idx = 0;
if (splitat == 1)
{
surf->inds[0] = 0;
surf->verts[0] = splitsurf->verts[splitsurf->inds[splitsurf->ninds]];
surf->nverts = splitat;
}
else
{
surf->inds[0] = 0;
surf->verts[0] = splitsurf->verts[splitsurf->inds[splitsurf->ninds]];
if (surf->inds[0] != surf->inds[1]) {
// Extra unique vertex
surf->verts[surf->nverts++] = splitsurf->verts[splitsurf->inds[splitsurf->ninds+1]]; // another vertex
surf->nverts++;
surf->inds[1] = 1;
}
else
surf->inds[1] = 0;
}
surf_idx = surf->nverts;
}
else
{
// regular new surface
surf++;
vmap->nsurfs++;
surf->nverts = 1;
surf->verts[0] = nvtx_new;
surf->ninds = 0;
surf_idx = 0;
}
}
}
else if (surf != NULL)
{
// Add vertex (existing surface)
surf_idx = surf->nverts;
surf->verts[surf->nverts++] = nvtx_new;
}
}
// Check indices (no splits here since they are divisible by 3)
if (surf != NULL && surf->ninds == SHADER_MAX_INDEXES)
{
if (vmap->nsurfs == ALIAS_VERTEXMAP_MAX_SURFACES)
surf = NULL;
else
{
// New surface, add vertex
surf++;
vmap->nsurfs++;
surf->nverts = 1;
surf->verts[0] = nvtx_new;
surf->ninds = 0;
surf_idx = 0;
}
}
if (surf == NULL)
{
ri.Error (ERR_DROP, "R_LoadMD2: model needs too many surfaces\n");
return qfalse;
}
surf->inds[surf->ninds++] = surf_idx;
return qtrue;
}
qboolean R_VMAPAddMapping(aliasVertexmap_t *vmap, int nvtx, int ntx) {
// Add stuff to vertex map
vmap->vertex_map[vmap->nverts] = nvtx;
vmap->vertex_st[vmap->nverts] = ntx;
if (!R_VMAPAddExisting(vmap, vmap->nverts))
return qfalse;
// check for illegal immigrants
if (vmap->nverts != ALIAS_VERTEXMAP_MAX_SURFACES * SHADER_MAX_VERTEXES)
vmap->nverts++;
else
return qfalse;
return qtrue;
}
qboolean R_VMAPAddVertexIndex(aliasVertexmap_t *vmap, short nvtx, short ntxc) {
int i;
// Find vertex idx in list
for (i=0; i<vmap->nverts; i++)
{
if (vmap->vertex_map[i] == nvtx)
{
// Vertex in map, check the texcoord
if (vmap->vertex_st[i] == ntxc)
// Yay, add indicie
return R_VMAPAddExisting(vmap, i);
else
// Boo, add duplicate vertex with new texcoord
return R_VMAPAddMapping(vmap, nvtx, ntxc);
}
}
// All else failed, add new mapping
return R_VMAPAddMapping(vmap, nvtx, ntxc);
}
static qboolean R_CreateMDLVertexMap(aliasVertexmap_t *vmap, int numTriangles, short numVerts, mdlTriangle_t *start, mdlSt_t *stverts) {
int i, y;
qboolean result;
//
// Create a vertex index map to provide duplicated texture coords
//
// This is slightly different to the Q2 version, as the ST list is treated as twice as big, to account for mirror st coords.
vmap->nverts = 0;
vmap->nsurfs = 0;
vmap->new_surfaces[0].ninds = 0;
vmap->new_surfaces[0].nverts = 0;
// Begin the iteration through the triangles
for (i=0 ; i<numTriangles ; i++)
{
int facesfront = LittleLong(start[i].facesfront);
for (y=0; y<3; y++)
{
// NOTE: mdl indices are 32bit, whereas the vertex map indices are 16bit
// although it is unlikely there will be 65536+ indices in an mdl!
int vidx = LittleLong(start[i].vertindex[y]);
if (!facesfront && stverts[vidx].onseam)
result = R_VMAPAddVertexIndex(vmap, (short)vidx, (short)vidx + numVerts);
else
result = R_VMAPAddVertexIndex(vmap, (short)vidx, (short)vidx);
}
if (result == qfalse) return qfalse;
}
return result;
}
static qboolean R_CreateMD2VertexMap(aliasVertexmap_t *vmap, int numTriangles, md2Triangle_t *start) {
int i;
//
// Create a vertex index map to provide duplicated texture coords
//
vmap->nverts = 0;
vmap->nsurfs = 0;
vmap->new_surfaces[0].ninds = 0;
vmap->new_surfaces[0].nverts = 0;
// Begin the iteration through the triangles
for (i=0 ; i<numTriangles ; i++)
{
if (!(
R_VMAPAddVertexIndex(vmap, LittleShort(start[i].index_xyz[0]), LittleShort(start[i].index_st[0])) &&
R_VMAPAddVertexIndex(vmap, LittleShort(start[i].index_xyz[1]), LittleShort(start[i].index_st[1])) &&
R_VMAPAddVertexIndex(vmap, LittleShort(start[i].index_xyz[2]), LittleShort(start[i].index_st[2]))
))
return qfalse;
}
return qtrue;
}
int R_CalculateMD3VertexMapSurfaceSize(aliasVertexmap_t *vmap, int numFrames) {
int calcSize;
int i;
aliasSurfaceVerts_t *surf;
calcSize = vmap->nsurfs * sizeof(md3Surface_t);
for (i=0; i<vmap->nsurfs; i++)
{
surf = &vmap->new_surfaces[i];
calcSize += sizeof(md3Shader_t);
calcSize += (surf->ninds / 3) * sizeof(md3Triangle_t);
calcSize += (surf->nverts) * sizeof(md3St_t);
calcSize += (surf->nverts) * numFrames * sizeof(md3XyzNormal_t);
ri.Printf( PRINT_ALL, "DBG: MD3[%d] mapped to %d triangles, %d vert/texcoords, %d frames\n", i, surf->ninds / 3, surf->nverts, numFrames);
}
return calcSize;
}
// #define MDL_CALCULATE_BOUNDS // Force calculate bounds in MDL
/*
=================
R_ProcessMDLSkins
=================
*/
void *R_ProcessMDLSkins(mdlHeader_t *pheader, dmdlSkintype_t *pskintype)
{
int i, j;
char name[32];
int s;
byte *skin;
dmdlSkingroup_t *pinskingroup;
int groupskins;
dmdlSkininterval_t *pinskinintervals;
skin = (byte *)(pskintype + 1);
if (pheader->numskins < 1 || pheader->numskins > MDL_MAX_SKINS) {
ri.Error(ERR_DROP, "R_LoadMDL: Invalid # of skins: %d\n", pheader->numskins);
return NULL;
}
s = pheader->skinwidth * pheader->skinheight;
for (i=0 ; i<pheader->numskins ; i++)
{
if (pskintype->type == MDL_SKIN_SINGLE) {
//Mod_FloodFillSkin( skin, pheader->skinwidth, pheader->skinheight );
// NOTE: could dump these textures so shaders can be generated
//Com_sprintf (name, "%s_%i", loadmodel->name, i);
//pheader->skinheight, (byte *)(pskintype + 1), true, false);
pskintype = (dmdlSkintype_t *)(skin + s);
} else {
// animating skin group. yuck.
pskintype++;
pinskingroup = (dmdlSkingroup_t *)pskintype;
groupskins = LittleLong (pinskingroup->numskins);
pinskinintervals = (dmdlSkininterval_t *)(pinskingroup + 1);
pskintype = (void *)(pinskinintervals + groupskins);
for (j=0 ; j<groupskins ; j++)
{
//Mod_FloodFillSkin( skin, pheader->skinwidth, pheader->skinheight );
// NOTE: could dump these textures so shaders can be generated
//sprintf (name, "%s_%i_%i", loadmodel->name, i,j);
//pheader->gl_texturenum[i][j&3] =
//GL_LoadTexture (name, pheader->skinwidth,
//pheader->skinheight, (byte *)(pskintype), true, false);
pskintype = (dmdlSkintype_t *)((byte *)(pskintype) + s);
}
//k = j;
//for (/* */; j < 4; j++)
// pheader->gl_texturenum[i][j&3] =
// pheader->gl_texturenum[i][j - k];
}
}
return (void *)pskintype;
}
int R_CalculateMDLFrameNumber(mdlHeader_t *pinmodel, dmdlFrametype_t *pinframe)
{
int i, tmp, count;
dmdlFrametype_t *pinframetype;
count = 0;
pinframetype = pinframe;
for (i=0; i<pinmodel->numframes; i++)
{
tmp = LittleLong(pinframetype->type);
if (pinframetype->type == MDL_SINGLE)
{
// Single set of frame data
mdlFrame_t *frame = (mdlFrame_t*)(pinframetype + 1);
count++;
pinframetype = (dmdlFrametype_t *) (((mdlTrivertx_t*)(frame + 1)) + pinmodel->numverts);
}
else
{
// Multiple sets of frame data (sharing same bounds, name, etc)
mdlGroup_t *pingroup = (mdlGroup_t *)(pinframetype + 1);
dmdlInterval_t *pininterval;
mdlTrivertx_t *pinvert;
tmp = LittleLong(pingroup->numframes);
// Intervals (important?)
pininterval = (dmdlInterval_t *)(pingroup + 1);
pininterval += tmp;
// Get frame pointer
// [header] verts [header] verts...
pinvert = (mdlTrivertx_t *)((mdlFrame_t*)pininterval + 1);
while (tmp != 0)
{
pinvert = (mdlTrivertx_t *)(((mdlFrame_t *)pinvert + 1) + pinmodel->numverts);
count++;
tmp--;
}
}
}
ri.Printf( PRINT_WARNING, "DBG: %d frames found in MDL\n", count);
return count;
}
/*
=================
R_LoadMDL
=================
*/
static qboolean R_LoadMDL (model_t *mod, int lod, void *buffer, const char *name ) {
int i, j, y; // cabin crew
// Inbound flight 80 from medieval village
mdlHeader_t *pinmodel;
mdlSt_t *pinst;
mdlTriangle_t *pintri;
dmdlFrametype_t *pinframetype;
mdlTrivertx_t *pinvert;
// Outbound flight 101 to fragville
md3Header_t *poutmodel;
md3St_t *poutst;
md3Surface_t *psurfaces;
md3Frame_t *poutframe;
md3Triangle_t *pouttri;
md3XyzNormal_t *poutverts;
// Stowaway spy
int tmp;
// Airport security
aliasVertexmap_t *vmap;
// Version check - very important!
pinmodel = (mdlHeader_t *)buffer;
ri.Printf( PRINT_ALL, "DBG: Loading an MDL (q1)\n" );
tmp = LittleLong(pinmodel->version);
if (tmp != MDL_VERSION) {
ri.Printf( PRINT_WARNING, "R_LoadMDL: %s has wrong version number (%i should be %i)\n",
mod->name, tmp, MDL_VERSION);
return qfalse;
}
mod->type = MOD_MESH;
// Byteswap header fields
LL(pinmodel->flags);
LF(pinmodel->boundingradius);
LL(pinmodel->numskins);
LL(pinmodel->skinwidth);
LL(pinmodel->skinheight);
pinmodel->size = LittleFloat(pinmodel->size) * ALIAS_BASE_SIZE_RATIO;
LL(pinmodel->synctype);
LL(pinmodel->numverts);
LL(pinmodel->numtris);
LL(pinmodel->numframes);
for (i=0 ; i<3 ; i++) {
LF(pinmodel->scale[i]);
LF(pinmodel->scale_origin[i]);
LF(pinmodel->eyeposition[i]);
}
// Quick overview of MDL format:
// Header
// Skins[numskins]
// ST verts[numverts]
// Triangles[numtris]
// Frame Data[numframes]
// * Single (like MD2) OR
// * Grouped (each frame contains multiple references to vertex data,
// perhaps to save the need for multiple frame headers)
// Load skins, calculate st and triangle pointers
pinst = (mdlSt_t*) R_ProcessMDLSkins(pinmodel, (dmdlSkintype_t*)&pinmodel[1]);
if (pinst == NULL)
return qfalse;
pintri = (mdlTriangle_t*) (pinst + pinmodel->numverts);
pinframetype = (dmdlFrametype_t *)(pintri + pinmodel->numtris);
// Create vertex map which will tell us about size requirements
vmap = ri.Hunk_AllocateTempMemory(sizeof(aliasVertexmap_t));
if (vmap == NULL)
{
ri.Error (ERR_DROP, "R_LoadMDL: not enough temporory memory to convert %s (%d bytes required)\n", mod->name, sizeof(aliasVertexmap_t));
return qfalse;
}
if (!R_CreateMDLVertexMap(vmap, pinmodel->numtris, pinmodel->numverts, pintri, pinst))
{
ri.Error (ERR_DROP, "R_LoadMDL: failed to generate vertex map whilst error converting %s\n", mod->name);
ri.Hunk_FreeTempMemory(vmap);
return qfalse;
}
if (vmap->new_surfaces[vmap->nsurfs].ninds != 0) vmap->nsurfs++; // Close last surface
ri.Printf( PRINT_ALL, "DBG: MDL mapped to %d verts using %d surfaces from %d tris\n", vmap->nverts, vmap->nsurfs, pinmodel->numtris );
// Calculate size of model as an MD3, and Allocate hunk
// TODO: need to go through frames one time to determine max frames
tmp = sizeof(md3Header_t) +
(R_CalculateMDLFrameNumber(pinmodel, pinframetype) * sizeof(md3Frame_t)) + // Frame list
0 + // Tag list
R_CalculateMD3VertexMapSurfaceSize(vmap, pinmodel->numframes) // Surface list
;
mod->dataSize += tmp;
mod->md3[lod] = ri.Hunk_Alloc( tmp, h_low );
poutmodel = mod->md3[lod]; // so we don't have to use mod->md3[lod] every time
poutmodel->numSurfaces = vmap->nsurfs;
// Byte swap and convert the header fields
poutmodel->ident = MD3_IDENT;
poutmodel->version = MD3_VERSION;
poutmodel->flags = 0; // TODO
poutmodel->name[0] = '\0'; // TODO
// Background check
if ( pinmodel->numframes <= 0 ) {
ri.Error (ERR_DROP, "R_LoadMDL: %s has no frames\n", mod->name);
ri.Hunk_FreeTempMemory(vmap);
return qfalse;
}
//
// Start with the frames
// Since MDL frames can be grouped, so we need to be careful
//
poutmodel->numFrames = 0;
poutmodel->ofsFrames = sizeof(md3Header_t);
poutframe = (md3Frame_t *) ((byte *)poutmodel
+ poutmodel->ofsFrames);
mdlTrivertx_t *poses[MDL_MAXFRAMES]; // Useful reference for vertex frames
for (i=0; i<pinmodel->numframes; i++)
{
tmp = LittleLong(pinframetype->type);
if (tmp == MDL_SINGLE)
{
// Single set of frame data
mdlFrame_t *frame = (mdlFrame_t*)(pinframetype + 1);
Com_Memcpy(poutframe->name, frame->name, 16);
#ifndef MDL_CALCULATE_BOUNDS
// Bounds
for (y=0; y<3; y++)
{
poutframe->bounds[0][y] = frame->bboxmin.v[y];
poutframe->bounds[1][y] = frame->bboxmax.v[y]; // NOTE: typo assignment (min instead of max) in original source
}
#else
// Bounds
for (y=0 ; y<3 ; y++)
poutframe->bounds[0][y] = 10e30; // min
for (y=0 ; y<3 ; y++)
poutframe->bounds[1][y] = -10e30; // max
#endif
// Get frame pointer
pinvert = (mdlTrivertx_t*)(frame + 1);
poses[poutmodel->numFrames] = pinvert;
poutmodel->numFrames++;
pinframetype = (dmdlFrametype_t *) (pinvert + pinmodel->numverts);
#ifndef MDL_CALCULATE_BOUNDS
// Calculate center
// ((min - max) / 2) + max
poutframe->localOrigin[0] = ((poutframe->bounds[0][0] - poutframe->bounds[1][0])/2.f) + poutframe->bounds[1][0];
poutframe->localOrigin[1] = ((poutframe->bounds[0][1] - poutframe->bounds[1][1])/2.f) + poutframe->bounds[1][1];
poutframe->localOrigin[2] = ((poutframe->bounds[0][2] - poutframe->bounds[1][2])/2.f) + poutframe->bounds[1][2];
// Calculate radius
// A bit inaccurate, but not much we can check here
poutframe->radius = RadiusFromBounds(poutframe->bounds[0], poutframe->bounds[1]);
#endif
poutframe++;
}
else
{
// Multiple sets of frame data (sharing same bounds, name, etc)
mdlGroup_t *pingroup = (mdlGroup_t *)(pinframetype + 1);
dmdlInterval_t *pininterval;
y = LittleLong(pingroup->numframes);
#ifndef MDL_CALCULATE_BOUNDS
// Bounds
for (j=0; j<3; j++)
{
poutframe->bounds[0][j] = pingroup->bboxmin.v[j];
poutframe->bounds[1][j] = pingroup->bboxmax.v[j];
}
// Calculate center
// ((min - max) / 2) + max
poutframe->localOrigin[0] = ((poutframe->bounds[0][0] - poutframe->bounds[1][0])/2.f) + poutframe->bounds[1][0];
poutframe->localOrigin[1] = ((poutframe->bounds[0][1] - poutframe->bounds[1][1])/2.f) + poutframe->bounds[1][1];
poutframe->localOrigin[2] = ((poutframe->bounds[0][2] - poutframe->bounds[1][2])/2.f) + poutframe->bounds[1][2];
// Calculate radius
// A bit inaccurate, but not much we can check here
poutframe->radius = RadiusFromBounds(poutframe->bounds[0], poutframe->bounds[1]);
#else
// Bounds
for (y=0 ; y<3 ; y++)
poutframe->bounds[0][y] = 10e30; // min
for (y=0 ; y<3 ; y++)
poutframe->bounds[1][y] = -10e30; // max
#endif
// Intervals (important?)
pininterval = (dmdlInterval_t *)(pingroup + 1);
pininterval += y;
// Get frame pointer
// [header] verts [header] verts...
// NOTE: seems horrifically redundant - only advantage is shared bounds and no repeating frame type,
// as well as a sequence-like grouping mechanizm
pinvert = (mdlTrivertx_t *)((mdlFrame_t*)pininterval + 1);
while (y != 0)
{
poses[poutmodel->numFrames] = pinvert;
pinvert = (mdlTrivertx_t *)(((mdlFrame_t *)pinvert + 1) + pinmodel->numverts);
poutmodel->numFrames++;
Com_Memcpy(poutframe, poutframe-1, sizeof(md3Frame_t)); // prev -> next
poutframe++;
y--;
}
}
}
//
// Tags
// TODO: add a tag for eyes (eyeposition[])?
//
poutmodel->numTags = 0;
poutmodel->ofsTags = poutmodel->ofsFrames + (poutmodel->numFrames * sizeof(md3Frame_t));
//
// Surfaces
//
poutmodel->ofsSurfaces = poutmodel->ofsTags + (poutmodel->numTags * sizeof(md3Tag_t));
psurfaces = (md3Surface_t *) ((byte *)poutmodel
+ poutmodel->ofsSurfaces);
poutmodel->ofsEnd = poutmodel->ofsSurfaces;
for(i = 0; i<poutmodel->numSurfaces; i++)
{
aliasSurfaceVerts_t *surf = &vmap->new_surfaces[i];
md3Shader_t *pshader;
psurfaces[i].ident = SF_MD3;
Com_sprintf(psurfaces[i].name, MAX_QPATH, "mdlsurf%d", i);
psurfaces[i].flags = 0;
psurfaces[i].numFrames = poutmodel->numFrames;
psurfaces[i].numShaders = 1;
psurfaces[i].numVerts = surf->nverts;
psurfaces[i].numTriangles = surf->ninds / 3;
//
// Convert the surface
//
// Surface Shader
psurfaces[i].ofsShaders = sizeof(md3Surface_t);
pshader = (md3Shader_t *) ((byte *)&psurfaces[i] + psurfaces[i].ofsShaders);
Com_sprintf(pshader->name, MAX_QPATH, "default"); // TODO
shader_t *sh;
sh = R_FindShader( pshader->name, LIGHTMAP_NONE, qtrue );
if ( sh->defaultShader ) {
pshader->shaderIndex = 0;
} else {
pshader->shaderIndex = sh->index;
}
// Surface Triangles
psurfaces[i].ofsTriangles = psurfaces[i].ofsShaders + sizeof(md3Shader_t);
pouttri = (md3Triangle_t *) ((byte *)&psurfaces[i] + psurfaces[i].ofsTriangles);
for (j=0; j<surf->ninds; j+=3)
{
pouttri->indexes[0] = surf->inds[j];
pouttri->indexes[1] = surf->inds[j+1];
pouttri->indexes[2] = surf->inds[j+2];
pouttri++;
};
// Surface Texcoords
psurfaces[i].ofsSt = psurfaces[i].ofsTriangles + (psurfaces[i].numTriangles * sizeof(md3Triangle_t));
poutst = (md3St_t *) ((byte *)&psurfaces[i] + psurfaces[i].ofsSt);
for (j=0; j<surf->nverts; j++)
{
tmp = vmap->vertex_st[surf->verts[j]]; // Need to map new verts -> old verts
// verts -> vertex_map,st_map -> frame[].pinvert, pinst
if (tmp >= pinmodel->numverts) {
tmp -= pinmodel->numverts;
poutst->st[0] = ((LittleLong(pinst[tmp].s) + 0.5) / pinmodel->skinwidth) + (pinmodel->skinwidth / 2);
}
else
poutst->st[0] = (LittleLong(pinst[tmp].s) + 0.5) / pinmodel->skinwidth;
poutst->st[1] = (LittleLong(pinst[tmp].t) + 0.5) / pinmodel->skinheight;
poutst++;
}
// Surface Vertexes[frames]
psurfaces[i].ofsXyzNormals = psurfaces[i].ofsSt + (psurfaces[i].numVerts * sizeof(md3St_t));
poutverts = (md3XyzNormal_t *) ((byte *)&psurfaces[i] + psurfaces[i].ofsXyzNormals);
poutframe = (md3Frame_t *) ((byte *)poutmodel
+ poutmodel->ofsFrames);
for (j=0; j<poutmodel->numFrames; j++)
{
pinvert = poses[j];
// Convert each vertex into an MD3 representation
for (y=0; y<surf->nverts; y++)
{
tmp = vmap->vertex_map[surf->verts[y]]; // Need to map new verts -> old verts
float base[3];
// TODO: fix origin
base[0] = ((float)pinvert[tmp].v[0] * pinmodel->scale[0]) + pinmodel->scale_origin[0];
base[1] = ((float)pinvert[tmp].v[1] * pinmodel->scale[1]) + pinmodel->scale_origin[1];
base[2] = ((float)pinvert[tmp].v[2] * pinmodel->scale[2]) + pinmodel->scale_origin[2];
poutverts->normal = pinvert[tmp].lightnormalindex;
#ifdef MDL_CALCULATE_BOUNDS
// Expand bounds
for (tmp=0; tmp<3; tmp++)
if (base[tmp] < poutframe->bounds[0][tmp]) poutframe->bounds[0][tmp] = base[tmp];
for (tmp=0; tmp<3; tmp++)
if (base[tmp] > poutframe->bounds[1][tmp]) poutframe->bounds[1][tmp] = base[tmp];
#endif
// This should give an appropriate MD3 vertex
poutverts->xyz[0] = base[0] / MD3_XYZ_SCALE;
poutverts->xyz[1] = base[1] / MD3_XYZ_SCALE;
poutverts->xyz[2] = base[2] / MD3_XYZ_SCALE;
poutverts++;
}
#ifdef MDL_CALCULATE_BOUNDS
// Calculate center
// ((min - max) / 2) + max
poutframe->localOrigin[0] = ((poutframe->bounds[0][0] - poutframe->bounds[1][0])/2.f) + poutframe->bounds[1][0];
poutframe->localOrigin[1] = ((poutframe->bounds[0][1] - poutframe->bounds[1][1])/2.f) + poutframe->bounds[1][1];
poutframe->localOrigin[2] = ((poutframe->bounds[0][2] - poutframe->bounds[1][2])/2.f) + poutframe->bounds[1][2];
// Calculate radius
// A bit inaccurate, but its better than checking every single vertex using sqrt
poutframe->radius = RadiusFromBounds(poutframe->bounds[0], poutframe->bounds[1]);
#endif
// Next please
poutframe++;
}
psurfaces[i].ofsEnd = psurfaces[i].ofsXyzNormals + (psurfaces[i].numVerts * psurfaces[i].numFrames * sizeof(md3XyzNormal_t));
poutmodel->ofsEnd += psurfaces[i].ofsEnd;
}
ri.Hunk_FreeTempMemory(vmap);
// Test MDL dump
FILE *fp = fopen("/Users/jamesu/debug_model.md3", "wb");
fwrite(poutmodel, poutmodel->ofsEnd, 1, fp);
fclose(fp);
return qtrue;
}
/*
=================
R_LoadMD2
=================
*/
static qboolean R_LoadMD2 (model_t *mod, int lod, void *buffer, const char *mod_name ) {
int i, j, y; // cabin crew
// Inbound flight 102 from stroggos
md2Header_t *pinmodel;
md2St_t *pinst;
md2Frame_t *pinframe;
md2Trivertx_t *pinvert; // base triangle data and normal
// Outbound flight 106 to fragville
md3Header_t *poutmodel;
md3St_t *poutst;
md3Surface_t *psurfaces;
md3Frame_t *poutframe;
md3Triangle_t *pouttri;
md3XyzNormal_t *poutverts;
// Stowaway spy
int tmp;
// Airport security
aliasVertexmap_t *vmap;
// Version check - very important!
pinmodel = (md2Header_t *)buffer;
ri.Printf( PRINT_ALL, "DBG: Loading an MD2\n" );
tmp = LittleLong(pinmodel->version);
if (tmp != MD2_VERSION) {
ri.Printf( PRINT_WARNING, "R_LoadMD2: %s has wrong version number (%i should be %i)\n",
mod->name, tmp, MD2_VERSION);
return qfalse;
}
mod->type = MOD_MESH;
// Byteswap header fields
for (i=0 ; i<sizeof(md2Header_t)/4 ; i++)
((int *)pinmodel)[i] = LittleLong (((int *)pinmodel)[i]);
// Create vertex map which will tell us about size requirements
vmap = ri.Hunk_AllocateTempMemory(sizeof(aliasVertexmap_t));
if (vmap == NULL)
{
ri.Error (ERR_DROP, "R_LoadMD2: not enough temporory memory to convert %s (%d bytes required)\n", mod->name, sizeof(aliasVertexmap_t));
return qfalse;
}
if (!R_CreateMD2VertexMap(vmap, pinmodel->numTriangles, (md2Triangle_t *) ((byte *)pinmodel + pinmodel->ofsTris)))
{
ri.Error (ERR_DROP, "R_LoadMD2: failed to generate vertex map whilst error converting %s\n", mod->name);
ri.Hunk_FreeTempMemory(vmap);
return qfalse;
}
if (vmap->new_surfaces[vmap->nsurfs].ninds != 0) vmap->nsurfs++; // Close last surface
ri.Printf( PRINT_ALL, "DBG: MD2 mapped to %d verts using %d surfaces from %d tris\n", vmap->nverts, vmap->nsurfs, pinmodel->numTriangles );
// Calculate size of model as an MD3, and Allocate hunk
tmp = sizeof(md3Header_t) +
(pinmodel->numFrames * sizeof(md3Frame_t)) + // Frame list
0 + // Tag list
R_CalculateMD3VertexMapSurfaceSize(vmap, pinmodel->numFrames) // Surface list
;
mod->dataSize += tmp;
mod->md3[lod] = ri.Hunk_Alloc( tmp, h_low );
poutmodel = mod->md3[lod]; // so we don't have to use mod->md3[lod] every time
poutmodel->numSurfaces = vmap->nsurfs;
// Byte swap and convert the header fields
poutmodel->ident = MD3_IDENT;
poutmodel->version = MD3_VERSION;
poutmodel->flags = 0; // TODO
poutmodel->name[0] = '\0'; // TODO
// Background check
if ( pinmodel->numFrames <= 0 ) {
ri.Error (ERR_DROP, "R_LoadMD2: %s has no frames\n", mod->name);
ri.Hunk_FreeTempMemory(vmap);
return qfalse;
}
//
// Start with the frames (bounds and radius calculated later)
//
poutmodel->numFrames = pinmodel->numFrames;
poutmodel->ofsFrames = sizeof(md3Header_t);
pinframe = (md2Frame_t *) ((byte *)pinmodel
+ pinmodel->ofsFrames);
poutframe = (md3Frame_t *) ((byte *)poutmodel
+ poutmodel->ofsFrames);
for (i=0; i<pinmodel->numFrames ; i++)
{
pinframe = (md2Frame_t *) ((byte *)pinmodel
+ pinmodel->ofsFrames + (pinmodel->framesize * i));
Com_Memcpy(poutframe->name, pinframe->name, 16);
for (j=0 ; j<3 ; j++)
{
LF(pinframe->scale[j]);
LF(pinframe->translate[j]);
}
// Bounds
for (j=0 ; j<3 ; j++)
poutframe->bounds[0][j] = 10e30; // min
for (j=0 ; j<3 ; j++)
poutframe->bounds[1][j] = -10e30; // max
poutframe->radius = 0;
poutframe++;
}
//
// Tags
//
poutmodel->numTags = 0;
poutmodel->ofsTags = poutmodel->ofsFrames + (poutmodel->numFrames * sizeof(md3Frame_t));
//
// Surfaces
//
poutmodel->ofsSurfaces = poutmodel->ofsTags + (poutmodel->numTags * sizeof(md3Tag_t));
psurfaces = (md3Surface_t *) ((byte *)poutmodel
+ poutmodel->ofsSurfaces);
poutmodel->ofsEnd = poutmodel->ofsSurfaces;
for(i = 0; i<poutmodel->numSurfaces; i++)
{
aliasSurfaceVerts_t *surf = &vmap->new_surfaces[i];
psurfaces[i].ident = SF_MD3;
Com_sprintf(psurfaces[i].name, MAX_QPATH, "md2surf%d", i);
psurfaces[i].flags = 0;
psurfaces[i].numFrames = poutmodel->numFrames;
psurfaces[i].numShaders = 1;
psurfaces[i].numVerts = surf->nverts;
psurfaces[i].numTriangles = surf->ninds / 3;
//
// Convert the surface
//
// Surface Shader
psurfaces[i].ofsShaders = sizeof(md3Surface_t);
md3Shader_t *pshader = (md3Shader_t *) ((byte *)&psurfaces[i] + psurfaces[i].ofsShaders);
if (pinmodel->numSkins == 0)
Com_sprintf(pshader->name, MAX_QPATH, "default");
else
Com_Memcpy(pshader->name, (byte*)pinmodel + pinmodel->ofsSkins, MAX_QPATH);
shader_t *sh;
sh = R_FindShader( pshader->name, LIGHTMAP_NONE, qtrue );
if ( sh->defaultShader ) {
pshader->shaderIndex = 0;
} else {
pshader->shaderIndex = sh->index;
}
// Surface Triangles
psurfaces[i].ofsTriangles = psurfaces[i].ofsShaders + sizeof(md3Shader_t);
pouttri = (md3Triangle_t *) ((byte *)&psurfaces[i] + psurfaces[i].ofsTriangles);
for (j=0; j<surf->ninds; j+=3)
{
pouttri->indexes[0] = surf->inds[j];
pouttri->indexes[1] = surf->inds[j+1];
pouttri->indexes[2] = surf->inds[j+2];
pouttri++;
};
// Surface Texcoords
psurfaces[i].ofsSt = psurfaces[i].ofsTriangles + (psurfaces[i].numTriangles * sizeof(md3Triangle_t));
pinst = (md2St_t *) ((byte *)pinmodel + pinmodel->ofsSt);
poutst = (md3St_t *) ((byte *)&psurfaces[i] + psurfaces[i].ofsSt);
for (j=0; j<surf->nverts; j++)
{
tmp = vmap->vertex_st[surf->verts[j]]; // Need to map new verts -> old verts
// verts -> vertex_map,st_map -> frame[].pinvert, pinst
poutst->st[0] = (float)LittleShort(pinst[tmp].s) / pinmodel->skinwidth;
poutst->st[1] = (float)LittleShort(pinst[tmp].t) / pinmodel->skinheight;
poutst++;
}
// Surface Vertexes[frames]
psurfaces[i].ofsXyzNormals = psurfaces[i].ofsSt + (psurfaces[i].numVerts * sizeof(md3St_t));
poutverts = (md3XyzNormal_t *) ((byte *)&psurfaces[i] + psurfaces[i].ofsXyzNormals);
poutframe = (md3Frame_t *) ((byte *)poutmodel
+ poutmodel->ofsFrames);
for (j=0; j<poutmodel->numFrames; j++)
{
//if (j == 0) fp = fopen("/Users/jamesu/import_md2_verts.txt", "w");
pinframe = (md2Frame_t *) ((byte *)pinmodel
+ pinmodel->ofsFrames + (pinmodel->framesize * j));
pinvert = pinframe->verts;
// Convert each vertex into an MD3 representation
for (y=0; y<surf->nverts; y++)
{
tmp = vmap->vertex_map[surf->verts[y]]; // Need to map new verts -> old verts
float base[3];
base[0] = ((float)pinvert[tmp].v[0] * pinframe->scale[0]) + pinframe->translate[0];
base[1] = ((float)pinvert[tmp].v[1] * pinframe->scale[1]) + pinframe->translate[1];
base[2] = ((float)pinvert[tmp].v[2] * pinframe->scale[2]) + pinframe->translate[2];
poutverts->normal = pinvert[tmp].lightnormalindex;
// Expand bounds
for (tmp=0; tmp<3; tmp++)
if (base[tmp] < poutframe->bounds[0][tmp]) poutframe->bounds[0][tmp] = base[tmp];
for (tmp=0; tmp<3; tmp++)
if (base[tmp] > poutframe->bounds[1][tmp]) poutframe->bounds[1][tmp] = base[tmp];
// This should give an appropriate MD3 vertex
poutverts->xyz[0] = base[0] / MD3_XYZ_SCALE;
poutverts->xyz[1] = base[1] / MD3_XYZ_SCALE;
poutverts->xyz[2] = base[2] / MD3_XYZ_SCALE;
poutverts++;
}
// Calculate center
// ((min - max) / 2) + max
poutframe->localOrigin[0] = ((poutframe->bounds[0][0] - poutframe->bounds[1][0])/2.f) + poutframe->bounds[1][0];
poutframe->localOrigin[1] = ((poutframe->bounds[0][1] - poutframe->bounds[1][1])/2.f) + poutframe->bounds[1][1];
poutframe->localOrigin[2] = ((poutframe->bounds[0][2] - poutframe->bounds[1][2])/2.f) + poutframe->bounds[1][2];
// Calculate radius
// A bit inaccurate, but its better than checking every single vertex using sqrt
poutframe->radius = RadiusFromBounds(poutframe->bounds[0], poutframe->bounds[1]);
// Next please
pinframe += pinmodel->framesize;
poutframe++;
}
psurfaces[i].ofsEnd = psurfaces[i].ofsXyzNormals + (psurfaces[i].numVerts * psurfaces[i].numFrames * sizeof(md3XyzNormal_t));
poutmodel->ofsEnd += psurfaces[i].ofsEnd;
}
ri.Hunk_FreeTempMemory(vmap);
// Test MD2 dump
//FILE *fp = fopen("/Users/jamesu/debug_md2model.md3", "wb");
//fwrite(poutmodel, poutmodel->ofsEnd, 1, fp);
//fclose(fp);
return qtrue;
}
// JamesU - End of MDL/MD2 loading code
//!! Enjoy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment