reaction/code/renderergl2/tr_model.c
2013-01-04 13:27:22 +00:00

1580 lines
41 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// tr_models.c -- model loading and caching
#include "tr_local.h"
#define LL(x) x=LittleLong(x)
static qboolean R_LoadMD3(model_t *mod, int lod, void *buffer, int bufferSize, const char *modName);
static qboolean R_LoadMD4(model_t *mod, void *buffer, const char *name );
#ifdef RAVENMD4
static qboolean R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name );
#endif
/*
====================
R_RegisterMD3
====================
*/
qhandle_t R_RegisterMD3(const char *name, model_t *mod)
{
union {
unsigned *u;
void *v;
} buf;
int size;
int lod;
int ident;
qboolean loaded = qfalse;
int numLoaded;
char filename[MAX_QPATH], namebuf[MAX_QPATH+20];
char *fext, defex[] = "md3";
numLoaded = 0;
strcpy(filename, name);
fext = strchr(filename, '.');
if(!fext)
fext = defex;
else
{
*fext = '\0';
fext++;
}
for (lod = MD3_MAX_LODS - 1 ; lod >= 0 ; lod--)
{
if(lod)
Com_sprintf(namebuf, sizeof(namebuf), "%s_%d.%s", filename, lod, fext);
else
Com_sprintf(namebuf, sizeof(namebuf), "%s.%s", filename, fext);
size = ri.FS_ReadFile( namebuf, &buf.v );
if(!buf.u)
continue;
ident = LittleLong(* (unsigned *) buf.u);
if (ident == MD4_IDENT)
loaded = R_LoadMD4(mod, buf.u, name);
else
{
if (ident == MD3_IDENT)
loaded = R_LoadMD3(mod, lod, buf.u, size, name);
else
ri.Printf(PRINT_WARNING,"R_RegisterMD3: unknown fileid for %s\n", name);
}
ri.FS_FreeFile(buf.v);
if(loaded)
{
mod->numLods++;
numLoaded++;
}
else
break;
}
if(numLoaded)
{
// duplicate into higher lod spots that weren't
// loaded, in case the user changes r_lodbias on the fly
for(lod--; lod >= 0; lod--)
{
mod->numLods++;
mod->mdv[lod] = mod->mdv[lod + 1];
}
return mod->index;
}
#ifdef _DEBUG
ri.Printf(PRINT_WARNING,"R_RegisterMD3: couldn't load %s\n", name);
#endif
mod->type = MOD_BAD;
return 0;
}
#ifdef RAVENMD4
/*
====================
R_RegisterMDR
====================
*/
qhandle_t R_RegisterMDR(const char *name, model_t *mod)
{
union {
unsigned *u;
void *v;
} buf;
int ident;
qboolean loaded = qfalse;
int filesize;
filesize = ri.FS_ReadFile(name, (void **) &buf.v);
if(!buf.u)
{
mod->type = MOD_BAD;
return 0;
}
ident = LittleLong(*(unsigned *)buf.u);
if(ident == MDR_IDENT)
loaded = R_LoadMDR(mod, buf.u, filesize, name);
ri.FS_FreeFile (buf.v);
if(!loaded)
{
ri.Printf(PRINT_WARNING,"R_RegisterMDR: couldn't load mdr file %s\n", name);
mod->type = MOD_BAD;
return 0;
}
return mod->index;
}
#endif
/*
====================
R_RegisterIQM
====================
*/
qhandle_t R_RegisterIQM(const char *name, model_t *mod)
{
union {
unsigned *u;
void *v;
} buf;
qboolean loaded = qfalse;
int filesize;
filesize = ri.FS_ReadFile(name, (void **) &buf.v);
if(!buf.u)
{
mod->type = MOD_BAD;
return 0;
}
loaded = R_LoadIQM(mod, buf.u, filesize, name);
ri.FS_FreeFile (buf.v);
if(!loaded)
{
ri.Printf(PRINT_WARNING,"R_RegisterIQM: couldn't load iqm file %s\n", name);
mod->type = MOD_BAD;
return 0;
}
return mod->index;
}
typedef struct
{
char *ext;
qhandle_t (*ModelLoader)( const char *, model_t * );
} modelExtToLoaderMap_t;
// Note that the ordering indicates the order of preference used
// when there are multiple models of different formats available
static modelExtToLoaderMap_t modelLoaders[ ] =
{
{ "iqm", R_RegisterIQM },
#ifdef RAVENMD4
{ "mdr", R_RegisterMDR },
#endif
{ "md4", R_RegisterMD3 },
{ "md3", R_RegisterMD3 }
};
static int numModelLoaders = ARRAY_LEN(modelLoaders);
//===============================================================================
/*
** R_GetModelByHandle
*/
model_t *R_GetModelByHandle( qhandle_t index ) {
model_t *mod;
// out of range gets the defualt model
if ( index < 1 || index >= tr.numModels ) {
return tr.models[0];
}
mod = tr.models[index];
return mod;
}
//===============================================================================
/*
** R_AllocModel
*/
model_t *R_AllocModel( void ) {
model_t *mod;
if ( tr.numModels == MAX_MOD_KNOWN ) {
return NULL;
}
mod = ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low );
mod->index = tr.numModels;
tr.models[tr.numModels] = mod;
tr.numModels++;
return mod;
}
/*
====================
RE_RegisterModel
Loads in a model for the given name
Zero will be returned if the model fails to load.
An entry will be retained for failed models as an
optimization to prevent disk rescanning if they are
asked for again.
====================
*/
qhandle_t RE_RegisterModel( const char *name ) {
model_t *mod;
qhandle_t hModel;
qboolean orgNameFailed = qfalse;
int orgLoader = -1;
int i;
char localName[ MAX_QPATH ];
const char *ext;
char altName[ MAX_QPATH ];
if ( !name || !name[0] ) {
ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" );
return 0;
}
if ( strlen( name ) >= MAX_QPATH ) {
ri.Printf( PRINT_ALL, "Model name exceeds MAX_QPATH\n" );
return 0;
}
//
// search the currently loaded models
//
for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) {
mod = tr.models[hModel];
if ( !strcmp( mod->name, name ) ) {
if( mod->type == MOD_BAD ) {
return 0;
}
return hModel;
}
}
// allocate a new model_t
if ( ( mod = R_AllocModel() ) == NULL ) {
ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name);
return 0;
}
// only set the name after the model has been successfully loaded
Q_strncpyz( mod->name, name, sizeof( mod->name ) );
// make sure the render thread is stopped
R_SyncRenderThread();
mod->type = MOD_BAD;
mod->numLods = 0;
//
// load the files
//
Q_strncpyz( localName, name, MAX_QPATH );
ext = COM_GetExtension( localName );
if( *ext )
{
// Look for the correct loader and use it
for( i = 0; i < numModelLoaders; i++ )
{
if( !Q_stricmp( ext, modelLoaders[ i ].ext ) )
{
// Load
hModel = modelLoaders[ i ].ModelLoader( localName, mod );
break;
}
}
// A loader was found
if( i < numModelLoaders )
{
if( !hModel )
{
// Loader failed, most likely because the file isn't there;
// try again without the extension
orgNameFailed = qtrue;
orgLoader = i;
COM_StripExtension( name, localName, MAX_QPATH );
}
else
{
// Something loaded
return mod->index;
}
}
}
// Try and find a suitable match using all
// the model formats supported
for( i = 0; i < numModelLoaders; i++ )
{
if (i == orgLoader)
continue;
Com_sprintf( altName, sizeof (altName), "%s.%s", localName, modelLoaders[ i ].ext );
// Load
hModel = modelLoaders[ i ].ModelLoader( altName, mod );
if( hModel )
{
if( orgNameFailed )
{
ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n",
name, altName );
}
break;
}
}
return hModel;
}
/*
=================
R_LoadMD3
=================
*/
static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, const char *modName)
{
int f, i, j, k;
md3Header_t *md3Model;
md3Frame_t *md3Frame;
md3Surface_t *md3Surf;
md3Shader_t *md3Shader;
md3Triangle_t *md3Tri;
md3St_t *md3st;
md3XyzNormal_t *md3xyz;
md3Tag_t *md3Tag;
mdvModel_t *mdvModel;
mdvFrame_t *frame;
mdvSurface_t *surf;//, *surface;
int *shaderIndex;
srfTriangle_t *tri;
mdvVertex_t *v;
mdvSt_t *st;
mdvTag_t *tag;
mdvTagName_t *tagName;
int version;
int size;
md3Model = (md3Header_t *) buffer;
version = LittleLong(md3Model->version);
if(version != MD3_VERSION)
{
ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n", modName, version, MD3_VERSION);
return qfalse;
}
mod->type = MOD_MESH;
size = LittleLong(md3Model->ofsEnd);
mod->dataSize += size;
mdvModel = mod->mdv[lod] = ri.Hunk_Alloc(sizeof(mdvModel_t), h_low);
// Com_Memcpy(mod->md3[lod], buffer, LittleLong(md3Model->ofsEnd));
LL(md3Model->ident);
LL(md3Model->version);
LL(md3Model->numFrames);
LL(md3Model->numTags);
LL(md3Model->numSurfaces);
LL(md3Model->ofsFrames);
LL(md3Model->ofsTags);
LL(md3Model->ofsSurfaces);
LL(md3Model->ofsEnd);
if(md3Model->numFrames < 1)
{
ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has no frames\n", modName);
return qfalse;
}
// swap all the frames
mdvModel->numFrames = md3Model->numFrames;
mdvModel->frames = frame = ri.Hunk_Alloc(sizeof(*frame) * md3Model->numFrames, h_low);
md3Frame = (md3Frame_t *) ((byte *) md3Model + md3Model->ofsFrames);
for(i = 0; i < md3Model->numFrames; i++, frame++, md3Frame++)
{
frame->radius = LittleFloat(md3Frame->radius);
for(j = 0; j < 3; j++)
{
frame->bounds[0][j] = LittleFloat(md3Frame->bounds[0][j]);
frame->bounds[1][j] = LittleFloat(md3Frame->bounds[1][j]);
frame->localOrigin[j] = LittleFloat(md3Frame->localOrigin[j]);
}
}
// swap all the tags
mdvModel->numTags = md3Model->numTags;
mdvModel->tags = tag = ri.Hunk_Alloc(sizeof(*tag) * (md3Model->numTags * md3Model->numFrames), h_low);
md3Tag = (md3Tag_t *) ((byte *) md3Model + md3Model->ofsTags);
for(i = 0; i < md3Model->numTags * md3Model->numFrames; i++, tag++, md3Tag++)
{
for(j = 0; j < 3; j++)
{
tag->origin[j] = LittleFloat(md3Tag->origin[j]);
tag->axis[0][j] = LittleFloat(md3Tag->axis[0][j]);
tag->axis[1][j] = LittleFloat(md3Tag->axis[1][j]);
tag->axis[2][j] = LittleFloat(md3Tag->axis[2][j]);
}
}
mdvModel->tagNames = tagName = ri.Hunk_Alloc(sizeof(*tagName) * (md3Model->numTags), h_low);
md3Tag = (md3Tag_t *) ((byte *) md3Model + md3Model->ofsTags);
for(i = 0; i < md3Model->numTags; i++, tagName++, md3Tag++)
{
Q_strncpyz(tagName->name, md3Tag->name, sizeof(tagName->name));
}
// swap all the surfaces
mdvModel->numSurfaces = md3Model->numSurfaces;
mdvModel->surfaces = surf = ri.Hunk_Alloc(sizeof(*surf) * md3Model->numSurfaces, h_low);
md3Surf = (md3Surface_t *) ((byte *) md3Model + md3Model->ofsSurfaces);
for(i = 0; i < md3Model->numSurfaces; i++)
{
LL(md3Surf->ident);
LL(md3Surf->flags);
LL(md3Surf->numFrames);
LL(md3Surf->numShaders);
LL(md3Surf->numTriangles);
LL(md3Surf->ofsTriangles);
LL(md3Surf->numVerts);
LL(md3Surf->ofsShaders);
LL(md3Surf->ofsSt);
LL(md3Surf->ofsXyzNormals);
LL(md3Surf->ofsEnd);
if(md3Surf->numVerts > SHADER_MAX_VERTEXES)
{
ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i verts on a surface (%i)",
modName, SHADER_MAX_VERTEXES, md3Surf->numVerts);
return qfalse;
}
if(md3Surf->numTriangles * 3 > SHADER_MAX_INDEXES)
{
ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i triangles on a surface (%i)",
modName, SHADER_MAX_INDEXES / 3, md3Surf->numTriangles);
return qfalse;
}
// change to surface identifier
surf->surfaceType = SF_MDV;
// give pointer to model for Tess_SurfaceMDX
surf->model = mdvModel;
// copy surface name
Q_strncpyz(surf->name, md3Surf->name, sizeof(surf->name));
// lowercase the surface name so skin compares are faster
Q_strlwr(surf->name);
// strip off a trailing _1 or _2
// this is a crutch for q3data being a mess
j = strlen(surf->name);
if(j > 2 && surf->name[j - 2] == '_')
{
surf->name[j - 2] = 0;
}
// register the shaders
surf->numShaderIndexes = md3Surf->numShaders;
surf->shaderIndexes = shaderIndex = ri.Hunk_Alloc(sizeof(*shaderIndex) * md3Surf->numShaders, h_low);
md3Shader = (md3Shader_t *) ((byte *) md3Surf + md3Surf->ofsShaders);
for(j = 0; j < md3Surf->numShaders; j++, shaderIndex++, md3Shader++)
{
shader_t *sh;
sh = R_FindShader(md3Shader->name, LIGHTMAP_NONE, qtrue);
if(sh->defaultShader)
{
*shaderIndex = 0;
}
else
{
*shaderIndex = sh->index;
}
}
// swap all the triangles
surf->numTriangles = md3Surf->numTriangles;
surf->triangles = tri = ri.Hunk_Alloc(sizeof(*tri) * md3Surf->numTriangles, h_low);
md3Tri = (md3Triangle_t *) ((byte *) md3Surf + md3Surf->ofsTriangles);
for(j = 0; j < md3Surf->numTriangles; j++, tri++, md3Tri++)
{
tri->indexes[0] = LittleLong(md3Tri->indexes[0]);
tri->indexes[1] = LittleLong(md3Tri->indexes[1]);
tri->indexes[2] = LittleLong(md3Tri->indexes[2]);
}
R_CalcSurfaceTriangleNeighbors(surf->numTriangles, surf->triangles);
// swap all the XyzNormals
surf->numVerts = md3Surf->numVerts;
surf->verts = v = ri.Hunk_Alloc(sizeof(*v) * (md3Surf->numVerts * md3Surf->numFrames), h_low);
md3xyz = (md3XyzNormal_t *) ((byte *) md3Surf + md3Surf->ofsXyzNormals);
for(j = 0; j < md3Surf->numVerts * md3Surf->numFrames; j++, md3xyz++, v++)
{
unsigned lat, lng;
unsigned short normal;
v->xyz[0] = LittleShort(md3xyz->xyz[0]) * MD3_XYZ_SCALE;
v->xyz[1] = LittleShort(md3xyz->xyz[1]) * MD3_XYZ_SCALE;
v->xyz[2] = LittleShort(md3xyz->xyz[2]) * MD3_XYZ_SCALE;
normal = LittleShort(md3xyz->normal);
lat = ( normal >> 8 ) & 0xff;
lng = ( normal & 0xff );
lat *= (FUNCTABLE_SIZE/256);
lng *= (FUNCTABLE_SIZE/256);
// decode X as cos( lat ) * sin( long )
// decode Y as sin( lat ) * sin( long )
// decode Z as cos( long )
v->normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
v->normal[1] = tr.sinTable[lat] * tr.sinTable[lng];
v->normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
}
// swap all the ST
surf->st = st = ri.Hunk_Alloc(sizeof(*st) * md3Surf->numVerts, h_low);
md3st = (md3St_t *) ((byte *) md3Surf + md3Surf->ofsSt);
for(j = 0; j < md3Surf->numVerts; j++, md3st++, st++)
{
st->st[0] = LittleFloat(md3st->st[0]);
st->st[1] = LittleFloat(md3st->st[1]);
}
#ifdef USE_VERT_TANGENT_SPACE
// calc tangent spaces
{
// Valgrind complaints: Conditional jump or move depends on uninitialised value(s)
// So lets Initialize them.
const float *v0 = NULL, *v1 = NULL, *v2 = NULL;
const float *t0 = NULL, *t1 = NULL, *t2 = NULL;
vec3_t tangent = { 0, 0, 0 };
vec3_t bitangent = { 0, 0, 0 };
vec3_t normal = { 0, 0, 0 };
for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++)
{
VectorClear(v->tangent);
VectorClear(v->bitangent);
if (r_recalcMD3Normals->integer)
VectorClear(v->normal);
}
for(f = 0; f < mdvModel->numFrames; f++)
{
for(j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++)
{
v0 = surf->verts[surf->numVerts * f + tri->indexes[0]].xyz;
v1 = surf->verts[surf->numVerts * f + tri->indexes[1]].xyz;
v2 = surf->verts[surf->numVerts * f + tri->indexes[2]].xyz;
t0 = surf->st[tri->indexes[0]].st;
t1 = surf->st[tri->indexes[1]].st;
t2 = surf->st[tri->indexes[2]].st;
if (!r_recalcMD3Normals->integer)
VectorCopy(v->normal, normal);
else
VectorClear(normal);
#if 1
R_CalcTangentSpace(tangent, bitangent, normal, v0, v1, v2, t0, t1, t2);
#else
R_CalcNormalForTriangle(normal, v0, v1, v2);
R_CalcTangentsForTriangle(tangent, bitangent, v0, v1, v2, t0, t1, t2);
#endif
for(k = 0; k < 3; k++)
{
float *v;
v = surf->verts[surf->numVerts * f + tri->indexes[k]].tangent;
VectorAdd(v, tangent, v);
v = surf->verts[surf->numVerts * f + tri->indexes[k]].bitangent;
VectorAdd(v, bitangent, v);
if (r_recalcMD3Normals->integer)
{
v = surf->verts[surf->numVerts * f + tri->indexes[k]].normal;
VectorAdd(v, normal, v);
}
}
}
}
for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++)
{
VectorNormalize(v->tangent);
VectorNormalize(v->bitangent);
VectorNormalize(v->normal);
}
}
#endif
// find the next surface
md3Surf = (md3Surface_t *) ((byte *) md3Surf + md3Surf->ofsEnd);
surf++;
}
{
srfVBOMDVMesh_t *vboSurf;
mdvModel->numVBOSurfaces = mdvModel->numSurfaces;
mdvModel->vboSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vboSurfaces) * mdvModel->numSurfaces, h_low);
vboSurf = mdvModel->vboSurfaces;
surf = mdvModel->surfaces;
for (i = 0; i < mdvModel->numSurfaces; i++, vboSurf++, surf++)
{
vec3_t *verts;
vec3_t *normals;
vec2_t *texcoords;
#ifdef USE_VERT_TANGENT_SPACE
vec3_t *tangents;
vec3_t *bitangents;
#endif
byte *data;
int dataSize;
int ofs_xyz, ofs_normal, ofs_st;
#ifdef USE_VERT_TANGENT_SPACE
int ofs_tangent, ofs_bitangent;
#endif
dataSize = 0;
ofs_xyz = dataSize;
dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*verts);
ofs_normal = dataSize;
dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*normals);
#ifdef USE_VERT_TANGENT_SPACE
ofs_tangent = dataSize;
dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*tangents);
ofs_bitangent = dataSize;
dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*bitangents);
#endif
ofs_st = dataSize;
dataSize += surf->numVerts * sizeof(*texcoords);
data = ri.Malloc(dataSize);
verts = (void *)(data + ofs_xyz);
normals = (void *)(data + ofs_normal);
#ifdef USE_VERT_TANGENT_SPACE
tangents = (void *)(data + ofs_tangent);
bitangents = (void *)(data + ofs_bitangent);
#endif
texcoords = (void *)(data + ofs_st);
v = surf->verts;
for ( j = 0; j < surf->numVerts * mdvModel->numFrames ; j++, v++ )
{
VectorCopy(v->xyz, verts[j]);
VectorCopy(v->normal, normals[j]);
#ifdef USE_VERT_TANGENT_SPACE
VectorCopy(v->tangent, tangents[j]);
VectorCopy(v->bitangent, bitangents[j]);
#endif
}
st = surf->st;
for ( j = 0 ; j < surf->numVerts ; j++, st++ ) {
texcoords[j][0] = st->st[0];
texcoords[j][1] = st->st[1];
}
vboSurf->surfaceType = SF_VBO_MDVMESH;
vboSurf->mdvModel = mdvModel;
vboSurf->mdvSurface = surf;
vboSurf->numIndexes = surf->numTriangles * 3;
vboSurf->numVerts = surf->numVerts;
vboSurf->vbo = R_CreateVBO(va("staticMD3Mesh_VBO '%s'", surf->name), data, dataSize, VBO_USAGE_STATIC);
vboSurf->vbo->ofs_xyz = ofs_xyz;
vboSurf->vbo->ofs_normal = ofs_normal;
#ifdef USE_VERT_TANGENT_SPACE
vboSurf->vbo->ofs_tangent = ofs_tangent;
vboSurf->vbo->ofs_bitangent = ofs_bitangent;
#endif
vboSurf->vbo->ofs_st = ofs_st;
vboSurf->vbo->stride_xyz = sizeof(*verts);
vboSurf->vbo->stride_normal = sizeof(*normals);
#ifdef USE_VERT_TANGENT_SPACE
vboSurf->vbo->stride_tangent = sizeof(*tangents);
vboSurf->vbo->stride_bitangent = sizeof(*bitangents);
#endif
vboSurf->vbo->stride_st = sizeof(*st);
vboSurf->vbo->size_xyz = sizeof(*verts) * surf->numVerts;
vboSurf->vbo->size_normal = sizeof(*normals) * surf->numVerts;
ri.Free(data);
vboSurf->ibo = R_CreateIBO2(va("staticMD3Mesh_IBO %s", surf->name), surf->numTriangles, surf->triangles, VBO_USAGE_STATIC);
}
}
return qtrue;
}
#ifdef RAVENMD4
/*
=================
R_LoadMDR
=================
*/
static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name )
{
int i, j, k, l;
mdrHeader_t *pinmodel, *mdr;
mdrFrame_t *frame;
mdrLOD_t *lod, *curlod;
mdrSurface_t *surf, *cursurf;
mdrTriangle_t *tri, *curtri;
mdrVertex_t *v, *curv;
mdrWeight_t *weight, *curweight;
mdrTag_t *tag, *curtag;
int size;
shader_t *sh;
pinmodel = (mdrHeader_t *)buffer;
pinmodel->version = LittleLong(pinmodel->version);
if (pinmodel->version != MDR_VERSION)
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has wrong version (%i should be %i)\n", mod_name, pinmodel->version, MDR_VERSION);
return qfalse;
}
size = LittleLong(pinmodel->ofsEnd);
if(size > filesize)
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: Header of %s is broken. Wrong filesize declared!\n", mod_name);
return qfalse;
}
mod->type = MOD_MDR;
LL(pinmodel->numFrames);
LL(pinmodel->numBones);
LL(pinmodel->ofsFrames);
// This is a model that uses some type of compressed Bones. We don't want to uncompress every bone for each rendered frame
// over and over again, we'll uncompress it in this function already, so we must adjust the size of the target md4.
if(pinmodel->ofsFrames < 0)
{
// mdrFrame_t is larger than mdrCompFrame_t:
size += pinmodel->numFrames * sizeof(frame->name);
// now add enough space for the uncompressed bones.
size += pinmodel->numFrames * pinmodel->numBones * ((sizeof(mdrBone_t) - sizeof(mdrCompBone_t)));
}
// simple bounds check
if(pinmodel->numBones < 0 ||
sizeof(*mdr) + pinmodel->numFrames * (sizeof(*frame) + (pinmodel->numBones - 1) * sizeof(*frame->bones)) > size)
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
return qfalse;
}
mod->dataSize += size;
mod->modelData = mdr = ri.Hunk_Alloc( size, h_low );
// Copy all the values over from the file and fix endian issues in the process, if necessary.
mdr->ident = LittleLong(pinmodel->ident);
mdr->version = pinmodel->version; // Don't need to swap byte order on this one, we already did above.
Q_strncpyz(mdr->name, pinmodel->name, sizeof(mdr->name));
mdr->numFrames = pinmodel->numFrames;
mdr->numBones = pinmodel->numBones;
mdr->numLODs = LittleLong(pinmodel->numLODs);
mdr->numTags = LittleLong(pinmodel->numTags);
// We don't care about the other offset values, we'll generate them ourselves while loading.
mod->numLods = mdr->numLODs;
if ( mdr->numFrames < 1 )
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has no frames\n", mod_name);
return qfalse;
}
/* The first frame will be put into the first free space after the header */
frame = (mdrFrame_t *)(mdr + 1);
mdr->ofsFrames = (int)((byte *) frame - (byte *) mdr);
if (pinmodel->ofsFrames < 0)
{
mdrCompFrame_t *cframe;
// compressed model...
cframe = (mdrCompFrame_t *)((byte *) pinmodel - pinmodel->ofsFrames);
for(i = 0; i < mdr->numFrames; i++)
{
for(j = 0; j < 3; j++)
{
frame->bounds[0][j] = LittleFloat(cframe->bounds[0][j]);
frame->bounds[1][j] = LittleFloat(cframe->bounds[1][j]);
frame->localOrigin[j] = LittleFloat(cframe->localOrigin[j]);
}
frame->radius = LittleFloat(cframe->radius);
frame->name[0] = '\0'; // No name supplied in the compressed version.
for(j = 0; j < mdr->numBones; j++)
{
for(k = 0; k < (sizeof(cframe->bones[j].Comp) / 2); k++)
{
// Do swapping for the uncompressing functions. They seem to use shorts
// values only, so I assume this will work. Never tested it on other
// platforms, though.
((unsigned short *)(cframe->bones[j].Comp))[k] =
LittleShort( ((unsigned short *)(cframe->bones[j].Comp))[k] );
}
/* Now do the actual uncompressing */
MC_UnCompress(frame->bones[j].matrix, cframe->bones[j].Comp);
}
// Next Frame...
cframe = (mdrCompFrame_t *) &cframe->bones[j];
frame = (mdrFrame_t *) &frame->bones[j];
}
}
else
{
mdrFrame_t *curframe;
// uncompressed model...
//
curframe = (mdrFrame_t *)((byte *) pinmodel + pinmodel->ofsFrames);
// swap all the frames
for ( i = 0 ; i < mdr->numFrames ; i++)
{
for(j = 0; j < 3; j++)
{
frame->bounds[0][j] = LittleFloat(curframe->bounds[0][j]);
frame->bounds[1][j] = LittleFloat(curframe->bounds[1][j]);
frame->localOrigin[j] = LittleFloat(curframe->localOrigin[j]);
}
frame->radius = LittleFloat(curframe->radius);
Q_strncpyz(frame->name, curframe->name, sizeof(frame->name));
for (j = 0; j < (int) (mdr->numBones * sizeof(mdrBone_t) / 4); j++)
{
((float *)frame->bones)[j] = LittleFloat( ((float *)curframe->bones)[j] );
}
curframe = (mdrFrame_t *) &curframe->bones[mdr->numBones];
frame = (mdrFrame_t *) &frame->bones[mdr->numBones];
}
}
// frame should now point to the first free address after all frames.
lod = (mdrLOD_t *) frame;
mdr->ofsLODs = (int) ((byte *) lod - (byte *)mdr);
curlod = (mdrLOD_t *)((byte *) pinmodel + LittleLong(pinmodel->ofsLODs));
// swap all the LOD's
for ( l = 0 ; l < mdr->numLODs ; l++)
{
// simple bounds check
if((byte *) (lod + 1) > (byte *) mdr + size)
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
return qfalse;
}
lod->numSurfaces = LittleLong(curlod->numSurfaces);
// swap all the surfaces
surf = (mdrSurface_t *) (lod + 1);
lod->ofsSurfaces = (int)((byte *) surf - (byte *) lod);
cursurf = (mdrSurface_t *) ((byte *)curlod + LittleLong(curlod->ofsSurfaces));
for ( i = 0 ; i < lod->numSurfaces ; i++)
{
// simple bounds check
if((byte *) (surf + 1) > (byte *) mdr + size)
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
return qfalse;
}
// first do some copying stuff
surf->ident = SF_MDR;
Q_strncpyz(surf->name, cursurf->name, sizeof(surf->name));
Q_strncpyz(surf->shader, cursurf->shader, sizeof(surf->shader));
surf->ofsHeader = (byte *) mdr - (byte *) surf;
surf->numVerts = LittleLong(cursurf->numVerts);
surf->numTriangles = LittleLong(cursurf->numTriangles);
// numBoneReferences and BoneReferences generally seem to be unused
// now do the checks that may fail.
if ( surf->numVerts > SHADER_MAX_VERTEXES )
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i verts on a surface (%i).\n",
mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
return qfalse;
}
if ( surf->numTriangles*3 > SHADER_MAX_INDEXES )
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i triangles on a surface (%i).\n",
mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
return qfalse;
}
// lowercase the surface name so skin compares are faster
Q_strlwr( surf->name );
// register the shaders
sh = R_FindShader(surf->shader, LIGHTMAP_NONE, qtrue);
if ( sh->defaultShader ) {
surf->shaderIndex = 0;
} else {
surf->shaderIndex = sh->index;
}
// now copy the vertexes.
v = (mdrVertex_t *) (surf + 1);
surf->ofsVerts = (int)((byte *) v - (byte *) surf);
curv = (mdrVertex_t *) ((byte *)cursurf + LittleLong(cursurf->ofsVerts));
for(j = 0; j < surf->numVerts; j++)
{
LL(curv->numWeights);
// simple bounds check
if(curv->numWeights < 0 || (byte *) (v + 1) + (curv->numWeights - 1) * sizeof(*weight) > (byte *) mdr + size)
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
return qfalse;
}
v->normal[0] = LittleFloat(curv->normal[0]);
v->normal[1] = LittleFloat(curv->normal[1]);
v->normal[2] = LittleFloat(curv->normal[2]);
v->texCoords[0] = LittleFloat(curv->texCoords[0]);
v->texCoords[1] = LittleFloat(curv->texCoords[1]);
v->numWeights = curv->numWeights;
weight = &v->weights[0];
curweight = &curv->weights[0];
// Now copy all the weights
for(k = 0; k < v->numWeights; k++)
{
weight->boneIndex = LittleLong(curweight->boneIndex);
weight->boneWeight = LittleFloat(curweight->boneWeight);
weight->offset[0] = LittleFloat(curweight->offset[0]);
weight->offset[1] = LittleFloat(curweight->offset[1]);
weight->offset[2] = LittleFloat(curweight->offset[2]);
weight++;
curweight++;
}
v = (mdrVertex_t *) weight;
curv = (mdrVertex_t *) curweight;
}
// we know the offset to the triangles now:
tri = (mdrTriangle_t *) v;
surf->ofsTriangles = (int)((byte *) tri - (byte *) surf);
curtri = (mdrTriangle_t *)((byte *) cursurf + LittleLong(cursurf->ofsTriangles));
// simple bounds check
if(surf->numTriangles < 0 || (byte *) (tri + surf->numTriangles) > (byte *) mdr + size)
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
return qfalse;
}
for(j = 0; j < surf->numTriangles; j++)
{
tri->indexes[0] = LittleLong(curtri->indexes[0]);
tri->indexes[1] = LittleLong(curtri->indexes[1]);
tri->indexes[2] = LittleLong(curtri->indexes[2]);
tri++;
curtri++;
}
// tri now points to the end of the surface.
surf->ofsEnd = (byte *) tri - (byte *) surf;
surf = (mdrSurface_t *) tri;
// find the next surface.
cursurf = (mdrSurface_t *) ((byte *) cursurf + LittleLong(cursurf->ofsEnd));
}
// surf points to the next lod now.
lod->ofsEnd = (int)((byte *) surf - (byte *) lod);
lod = (mdrLOD_t *) surf;
// find the next LOD.
curlod = (mdrLOD_t *)((byte *) curlod + LittleLong(curlod->ofsEnd));
}
// lod points to the first tag now, so update the offset too.
tag = (mdrTag_t *) lod;
mdr->ofsTags = (int)((byte *) tag - (byte *) mdr);
curtag = (mdrTag_t *) ((byte *)pinmodel + LittleLong(pinmodel->ofsTags));
// simple bounds check
if(mdr->numTags < 0 || (byte *) (tag + mdr->numTags) > (byte *) mdr + size)
{
ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
return qfalse;
}
for (i = 0 ; i < mdr->numTags ; i++)
{
tag->boneIndex = LittleLong(curtag->boneIndex);
Q_strncpyz(tag->name, curtag->name, sizeof(tag->name));
tag++;
curtag++;
}
// And finally we know the real offset to the end.
mdr->ofsEnd = (int)((byte *) tag - (byte *) mdr);
// phew! we're done.
return qtrue;
}
#endif
/*
=================
R_LoadMD4
=================
*/
static qboolean R_LoadMD4( model_t *mod, void *buffer, const char *mod_name ) {
int i, j, k, lodindex;
md4Header_t *pinmodel, *md4;
md4Frame_t *frame;
md4LOD_t *lod;
md4Surface_t *surf;
md4Triangle_t *tri;
md4Vertex_t *v;
int version;
int size;
shader_t *sh;
int frameSize;
pinmodel = (md4Header_t *)buffer;
version = LittleLong (pinmodel->version);
if (version != MD4_VERSION) {
ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has wrong version (%i should be %i)\n",
mod_name, version, MD4_VERSION);
return qfalse;
}
mod->type = MOD_MD4;
size = LittleLong(pinmodel->ofsEnd);
mod->dataSize += size;
mod->modelData = md4 = ri.Hunk_Alloc( size, h_low );
Com_Memcpy(md4, buffer, size);
LL(md4->ident);
LL(md4->version);
LL(md4->numFrames);
LL(md4->numBones);
LL(md4->numLODs);
LL(md4->ofsFrames);
LL(md4->ofsLODs);
md4->ofsEnd = size;
if ( md4->numFrames < 1 ) {
ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has no frames\n", mod_name );
return qfalse;
}
// we don't need to swap tags in the renderer, they aren't used
// swap all the frames
frameSize = (size_t)( &((md4Frame_t *)0)->bones[ md4->numBones ] );
for ( i = 0 ; i < md4->numFrames ; i++) {
frame = (md4Frame_t *) ( (byte *)md4 + md4->ofsFrames + i * frameSize );
frame->radius = LittleFloat( frame->radius );
for ( j = 0 ; j < 3 ; j++ ) {
frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] );
frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] );
frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] );
}
for ( j = 0 ; j < md4->numBones * sizeof( md4Bone_t ) / 4 ; j++ ) {
((float *)frame->bones)[j] = LittleFloat( ((float *)frame->bones)[j] );
}
}
// swap all the LOD's
lod = (md4LOD_t *) ( (byte *)md4 + md4->ofsLODs );
for ( lodindex = 0 ; lodindex < md4->numLODs ; lodindex++ ) {
// swap all the surfaces
surf = (md4Surface_t *) ( (byte *)lod + lod->ofsSurfaces );
for ( i = 0 ; i < lod->numSurfaces ; i++) {
LL(surf->ident);
LL(surf->numTriangles);
LL(surf->ofsTriangles);
LL(surf->numVerts);
LL(surf->ofsVerts);
LL(surf->ofsEnd);
if ( surf->numVerts > SHADER_MAX_VERTEXES ) {
ri.Printf(PRINT_WARNING, "R_LoadMD4: %s has more than %i verts on a surface (%i).\n",
mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
return qfalse;
}
if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) {
ri.Printf(PRINT_WARNING, "R_LoadMD4: %s has more than %i triangles on a surface (%i).\n",
mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
return qfalse;
}
// change to surface identifier
surf->ident = SF_MD4;
// lowercase the surface name so skin compares are faster
Q_strlwr( surf->name );
// register the shaders
sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue );
if ( sh->defaultShader ) {
surf->shaderIndex = 0;
} else {
surf->shaderIndex = sh->index;
}
// swap all the triangles
tri = (md4Triangle_t *) ( (byte *)surf + surf->ofsTriangles );
for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) {
LL(tri->indexes[0]);
LL(tri->indexes[1]);
LL(tri->indexes[2]);
}
// swap all the vertexes
// FIXME
// This makes TFC's skeletons work. Shouldn't be necessary anymore, but left
// in for reference.
//v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts + 12);
v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts);
for ( j = 0 ; j < surf->numVerts ; j++ ) {
v->normal[0] = LittleFloat( v->normal[0] );
v->normal[1] = LittleFloat( v->normal[1] );
v->normal[2] = LittleFloat( v->normal[2] );
v->texCoords[0] = LittleFloat( v->texCoords[0] );
v->texCoords[1] = LittleFloat( v->texCoords[1] );
v->numWeights = LittleLong( v->numWeights );
for ( k = 0 ; k < v->numWeights ; k++ ) {
v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex );
v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight );
v->weights[k].offset[0] = LittleFloat( v->weights[k].offset[0] );
v->weights[k].offset[1] = LittleFloat( v->weights[k].offset[1] );
v->weights[k].offset[2] = LittleFloat( v->weights[k].offset[2] );
}
// FIXME
// This makes TFC's skeletons work. Shouldn't be necessary anymore, but left
// in for reference.
//v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 );
v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights]);
}
// find the next surface
surf = (md4Surface_t *)( (byte *)surf + surf->ofsEnd );
}
// find the next LOD
lod = (md4LOD_t *)( (byte *)lod + lod->ofsEnd );
}
return qtrue;
}
//=============================================================================
/*
** RE_BeginRegistration
*/
void RE_BeginRegistration( glconfig_t *glconfigOut ) {
R_Init();
*glconfigOut = glConfig;
R_SyncRenderThread();
tr.visIndex = 0;
memset(tr.visClusters, -2, sizeof(tr.visClusters)); // force markleafs to regenerate
R_ClearFlares();
RE_ClearScene();
tr.registered = qtrue;
// NOTE: this sucks, for some reason the first stretch pic is never drawn
// without this we'd see a white flash on a level load because the very
// first time the level shot would not be drawn
// RE_StretchPic(0, 0, 0, 0, 0, 0, 1, 1, 0);
}
//=============================================================================
/*
===============
R_ModelInit
===============
*/
void R_ModelInit( void ) {
model_t *mod;
// leave a space for NULL model
tr.numModels = 0;
mod = R_AllocModel();
mod->type = MOD_BAD;
}
/*
================
R_Modellist_f
================
*/
void R_Modellist_f( void ) {
int i, j;
model_t *mod;
int total;
int lods;
total = 0;
for ( i = 1 ; i < tr.numModels; i++ ) {
mod = tr.models[i];
lods = 1;
for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) {
if ( mod->mdv[j] && mod->mdv[j] != mod->mdv[j-1] ) {
lods++;
}
}
ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name );
total += mod->dataSize;
}
ri.Printf( PRINT_ALL, "%8i : Total models\n", total );
#if 0 // not working right with new hunk
if ( tr.world ) {
ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name );
}
#endif
}
//=============================================================================
/*
================
R_GetTag
================
*/
static mdvTag_t *R_GetTag( mdvModel_t *mod, int frame, const char *_tagName ) {
int i;
mdvTag_t *tag;
mdvTagName_t *tagName;
if ( frame >= mod->numFrames ) {
// it is possible to have a bad frame while changing models, so don't error
frame = mod->numFrames - 1;
}
tag = mod->tags + frame * mod->numTags;
tagName = mod->tagNames;
for(i = 0; i < mod->numTags; i++, tag++, tagName++)
{
if(!strcmp(tagName->name, _tagName))
{
return tag;
}
}
return NULL;
}
#ifdef RAVENMD4
void R_GetAnimTag( mdrHeader_t *mod, int framenum, const char *tagName, md3Tag_t * dest)
{
int i, j, k;
int frameSize;
mdrFrame_t *frame;
mdrTag_t *tag;
if ( framenum >= mod->numFrames )
{
// it is possible to have a bad frame while changing models, so don't error
framenum = mod->numFrames - 1;
}
tag = (mdrTag_t *)((byte *)mod + mod->ofsTags);
for ( i = 0 ; i < mod->numTags ; i++, tag++ )
{
if ( !strcmp( tag->name, tagName ) )
{
Q_strncpyz(dest->name, tag->name, sizeof(dest->name));
// uncompressed model...
//
frameSize = (intptr_t)( &((mdrFrame_t *)0)->bones[ mod->numBones ] );
frame = (mdrFrame_t *)((byte *)mod + mod->ofsFrames + framenum * frameSize );
for (j = 0; j < 3; j++)
{
for (k = 0; k < 3; k++)
dest->axis[j][k]=frame->bones[tag->boneIndex].matrix[k][j];
}
dest->origin[0]=frame->bones[tag->boneIndex].matrix[0][3];
dest->origin[1]=frame->bones[tag->boneIndex].matrix[1][3];
dest->origin[2]=frame->bones[tag->boneIndex].matrix[2][3];
return;
}
}
AxisClear( dest->axis );
VectorClear( dest->origin );
strcpy(dest->name,"");
}
#endif
/*
================
R_LerpTag
================
*/
int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame,
float frac, const char *tagName ) {
mdvTag_t *start, *end;
#ifdef RAVENMD4
md3Tag_t start_space, end_space;
#endif
int i;
float frontLerp, backLerp;
model_t *model;
model = R_GetModelByHandle( handle );
if ( !model->mdv[0] )
{
#ifdef RAVENMD4
if(model->type == MOD_MDR)
{
start = &start_space;
end = &end_space;
R_GetAnimTag((mdrHeader_t *) model->modelData, startFrame, tagName, start);
R_GetAnimTag((mdrHeader_t *) model->modelData, endFrame, tagName, end);
}
else
#endif
if( model->type == MOD_IQM ) {
return R_IQMLerpTag( tag, model->modelData,
startFrame, endFrame,
frac, tagName );
} else {
AxisClear( tag->axis );
VectorClear( tag->origin );
return qfalse;
}
}
else
{
start = R_GetTag( model->mdv[0], startFrame, tagName );
end = R_GetTag( model->mdv[0], endFrame, tagName );
if ( !start || !end ) {
AxisClear( tag->axis );
VectorClear( tag->origin );
return qfalse;
}
}
frontLerp = frac;
backLerp = 1.0f - frac;
for ( i = 0 ; i < 3 ; i++ ) {
tag->origin[i] = start->origin[i] * backLerp + end->origin[i] * frontLerp;
tag->axis[0][i] = start->axis[0][i] * backLerp + end->axis[0][i] * frontLerp;
tag->axis[1][i] = start->axis[1][i] * backLerp + end->axis[1][i] * frontLerp;
tag->axis[2][i] = start->axis[2][i] * backLerp + end->axis[2][i] * frontLerp;
}
VectorNormalize( tag->axis[0] );
VectorNormalize( tag->axis[1] );
VectorNormalize( tag->axis[2] );
return qtrue;
}
/*
====================
R_ModelBounds
====================
*/
void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) {
model_t *model;
model = R_GetModelByHandle( handle );
if(model->type == MOD_BRUSH) {
VectorCopy( model->bmodel->bounds[0], mins );
VectorCopy( model->bmodel->bounds[1], maxs );
return;
} else if (model->type == MOD_MESH) {
mdvModel_t *header;
mdvFrame_t *frame;
header = model->mdv[0];
frame = header->frames;
VectorCopy( frame->bounds[0], mins );
VectorCopy( frame->bounds[1], maxs );
return;
} else if (model->type == MOD_MD4) {
md4Header_t *header;
md4Frame_t *frame;
header = (md4Header_t *)model->modelData;
frame = (md4Frame_t *) ((byte *)header + header->ofsFrames);
VectorCopy( frame->bounds[0], mins );
VectorCopy( frame->bounds[1], maxs );
return;
#ifdef RAVENMD4
} else if (model->type == MOD_MDR) {
mdrHeader_t *header;
mdrFrame_t *frame;
header = (mdrHeader_t *)model->modelData;
frame = (mdrFrame_t *) ((byte *)header + header->ofsFrames);
VectorCopy( frame->bounds[0], mins );
VectorCopy( frame->bounds[1], maxs );
return;
#endif
} else if(model->type == MOD_IQM) {
iqmData_t *iqmData;
iqmData = model->modelData;
if(iqmData->bounds)
{
VectorCopy(iqmData->bounds, mins);
VectorCopy(iqmData->bounds + 3, maxs);
return;
}
}
VectorClear( mins );
VectorClear( maxs );
}