openmohaa/code/renderer_gl3/tr_model.c
2016-03-27 11:49:47 +02:00

936 lines
20 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2011 Robert Beckebans <trebor_7@users.sourceforge.net>
This file is part of XreaL source code.
XreaL 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.
XreaL 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 XreaL 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)
#define LF(x) x=LittleFloat(x)
qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, const char *name);
#if defined(COMPAT_ET)
qboolean R_LoadMDC(model_t * mod, int lod, void *buffer, int bufferSize, const char *name);
qboolean R_LoadMDM(model_t * mod, void *buffer, const char *name);
static qboolean R_LoadMDX(model_t * mod, void *buffer, const char *name);
#endif
qboolean R_LoadMD5(model_t * mod, void *buffer, int bufferSize, const char *name);
qboolean R_LoadPSK(model_t * mod, void *buffer, int bufferSize, const char *name);
model_t *loadmodel;
/*
** 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;
unsigned *buffer;
int bufferLen = 0;
int lod;
int ident;
qboolean loaded;
qhandle_t hModel;
int numLoaded;
if(!name || !name[0])
{
ri.Printf(PRINT_ALL, "RE_RegisterModel: NULL name\n");
return 0;
}
if(strlen(name) >= MAX_QPATH)
{
Com_Printf("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->numLods = 0;
// load the files
numLoaded = 0;
#if defined(COMPAT_ET)
if(strstr(name, ".mds") || strstr(name, ".mdm") || strstr(name, ".mdx") || strstr(name, ".md5mesh") || strstr(name, ".psk"))
#else
if(strstr(name, ".md5mesh") || strstr(name, ".psk"))
#endif
{
// try loading skeletal file
loaded = qfalse;
bufferLen = ri.FS_ReadFile(name, (void **)&buffer);
if(buffer)
{
loadmodel = mod;
ident = LittleLong(*(unsigned *)buffer);
#if defined(COMPAT_ET)
#if 0
if(ident == MDS_IDENT)
{
loaded = R_LoadMDS(mod, buffer, name);
} else
#endif
if(ident == MDM_IDENT)
{
loaded = R_LoadMDM(mod, buffer, name);
}
else if(ident == MDX_IDENT)
{
loaded = R_LoadMDX(mod, buffer, name);
}
#endif
#if defined(USE_REFENTITY_ANIMATIONSYSTEM)
if(!Q_stricmpn((const char *)buffer, "MD5Version", 10))
{
loaded = R_LoadMD5(mod, buffer, bufferLen, name);
}
else if(!Q_stricmpn((const char *)buffer, PSK_IDENTSTRING, PSK_IDENTLEN))
{
loaded = R_LoadPSK(mod, buffer, bufferLen, name);
}
#endif
ri.FS_FreeFile(buffer);
}
if(loaded)
{
// make sure the VBO glState entries are save
R_BindNullVBO();
R_BindNullIBO();
return mod->index;
}
}
for(lod = MD3_MAX_LODS - 1; lod >= 0; lod--)
{
char filename[1024];
strcpy(filename, name);
if(lod != 0)
{
char namebuf[80];
if(strrchr(filename, '.'))
{
*strrchr(filename, '.') = 0;
}
sprintf(namebuf, "_%d.md3", lod);
strcat(filename, namebuf);
}
filename[strlen(filename) - 1] = 'c'; // try MDC first
ri.FS_ReadFile(filename, (void **)&buffer);
if(!buffer)
{
filename[strlen(filename) - 1] = '3'; // try MD3 second
ri.FS_ReadFile(filename, (void **)&buffer);
if(!buffer)
{
continue;
}
}
loadmodel = mod;
ident = LittleLong(*(unsigned *)buffer);
if(ident == MD3_IDENT)
{
loaded = R_LoadMD3(mod, lod, buffer, bufferLen, name);
ri.FS_FreeFile(buffer);
}
#if defined(COMPAT_ET)
else if(ident == MDC_IDENT)
{
loaded = R_LoadMDC(mod, lod, buffer, bufferLen, name);
ri.FS_FreeFile(buffer);
}
#endif
else
{
ri.FS_FreeFile(buffer);
ri.Printf(PRINT_WARNING, "RE_RegisterModel: unknown fileid for %s\n", name);
goto fail;
}
if(!loaded)
{
if(lod == 0)
{
goto fail;
}
else
{
break;
}
}
else
{
// make sure the VBO glState entries are save
R_BindNullVBO();
R_BindNullIBO();
mod->numLods++;
numLoaded++;
// if we have a valid model and are biased
// so that we won't see any higher detail ones,
// stop loading them
// if ( lod <= r_lodbias->integer ) {
// break;
// }
}
}
// make sure the VBO glState entries are save
R_BindNullVBO();
R_BindNullIBO();
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
else
{
ri.Printf(PRINT_WARNING, "couldn't load '%s'\n", name);
}
#endif
fail:
// we still keep the model_t around, so if the model name is asked for
// again, we won't bother scanning the filesystem
mod->type = MOD_BAD;
// make sure the VBO glState entries are save
R_BindNullVBO();
R_BindNullIBO();
return 0;
}
/*
=================
R_LoadMDX
=================
*/
#if defined(COMPAT_ET)
static qboolean R_LoadMDX(model_t * mod, void *buffer, const char *mod_name)
{
int i, j;
mdxHeader_t *pinmodel, *mdx;
mdxFrame_t *frame;
short *bframe;
mdxBoneInfo_t *bi;
int version;
int size;
int frameSize;
pinmodel = (mdxHeader_t *) buffer;
version = LittleLong(pinmodel->version);
if(version != MDX_VERSION)
{
ri.Printf(PRINT_WARNING, "R_LoadMDX: %s has wrong version (%i should be %i)\n", mod_name, version, MDX_VERSION);
return qfalse;
}
mod->type = MOD_MDX;
size = LittleLong(pinmodel->ofsEnd);
mod->dataSize += size;
mdx = mod->mdx = ri.Hunk_Alloc(size, h_low);
memcpy(mdx, buffer, LittleLong(pinmodel->ofsEnd));
LL(mdx->ident);
LL(mdx->version);
LL(mdx->numFrames);
LL(mdx->numBones);
LL(mdx->ofsFrames);
LL(mdx->ofsBones);
LL(mdx->ofsEnd);
LL(mdx->torsoParent);
if(LittleLong(1) != 1)
{
// swap all the frames
frameSize = (int)(sizeof(mdxBoneFrameCompressed_t)) * mdx->numBones;
for(i = 0; i < mdx->numFrames; i++)
{
frame = (mdxFrame_t *) ((byte *) mdx + mdx->ofsFrames + i * frameSize + i * sizeof(mdxFrame_t));
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]);
frame->parentOffset[j] = LittleFloat(frame->parentOffset[j]);
}
bframe = (short *)((byte *) mdx + mdx->ofsFrames + i * frameSize + ((i + 1) * sizeof(mdxFrame_t)));
for(j = 0; j < mdx->numBones * sizeof(mdxBoneFrameCompressed_t) / sizeof(short); j++)
{
((short *)bframe)[j] = LittleShort(((short *)bframe)[j]);
}
}
// swap all the bones
for(i = 0; i < mdx->numBones; i++)
{
bi = (mdxBoneInfo_t *) ((byte *) mdx + mdx->ofsBones + i * sizeof(mdxBoneInfo_t));
LL(bi->parent);
bi->torsoWeight = LittleFloat(bi->torsoWeight);
bi->parentDist = LittleFloat(bi->parentDist);
LL(bi->flags);
}
}
return qtrue;
}
#endif // #if defined(COMPAT_ET)
//=============================================================================
/*
=================
R_XMLError
=================
*/
void R_XMLError(void *ctx, const char *fmt, ...)
{
va_list argptr;
static char msg[4096];
va_start(argptr, fmt);
Q_vsnprintf(msg, sizeof(msg), fmt, argptr);
va_end(argptr);
ri.Printf(PRINT_WARNING, "%s", msg);
}
/*
=================
R_LoadDAE
=================
*/
/*
static qboolean R_LoadDAE(model_t * mod, void *buffer, int bufferLen, const char *modName)
{
xmlDocPtr doc;
xmlNodePtr node;
// setup error function handler
xmlInitParser();
xmlSetGenericErrorFunc(NULL, R_XMLError);
ri.Printf(PRINT_ALL, "...loading DAE '%s'\n", modName);
doc = xmlParseMemory(buffer, bufferLen);
if(doc == NULL)
{
ri.Printf(PRINT_WARNING, "R_LoadDAE: '%s' xmlParseMemory returned NULL\n", modName);
return qfalse;
}
node = xmlDocGetRootElement(doc);
if(node == NULL)
{
ri.Printf(PRINT_WARNING, "R_LoadDAE: '%s' empty document\n", modName);
xmlFreeDoc(doc);
return qfalse;
}
if(xmlStrcmp(node->name, (const xmlChar *) "COLLADA"))
{
ri.Printf(PRINT_WARNING, "R_LoadDAE: '%s' document of the wrong type, root node != COLLADA", modName);
xmlFreeDoc(doc);
return qfalse;
}
//TODO
xmlFreeDoc(doc);
ri.Printf(PRINT_ALL, "...finished DAE '%s'\n", modName);
return qfalse;
}
*/
//=============================================================================
/*
** RE_BeginRegistration
*/
void RE_BeginRegistration(glconfig_t * glconfigOut/*, glconfig2_t * glconfigOut2*/)
{
R_Init();
*glconfigOut = glConfig;
//*glconfigOut2 = glConfig2;
R_SyncRenderThread();
tr.visIndex = 0;
memset(tr.visClusters, -2, sizeof(tr.visClusters)); // force markleafs to regenerate
#if defined(USE_D3D10)
// TODO
#else
R_ClearFlares();
#endif
RE_ClearScene();
// HACK: give world entity white color for "colored" shader keyword
tr.worldEntity.e.shaderRGBA[0] = 255;
tr.worldEntity.e.shaderRGBA[1] = 255;
tr.worldEntity.e.shaderRGBA[2] = 255;
tr.worldEntity.e.shaderRGBA[3] = 255;
tr.worldEntity.e.nonNormalizedAxes = qfalse;
// RB: world will be never ignored by occusion query test
tr.worldEntity.occlusionQuerySamples = 1;
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, k;
model_t *mod;
int total;
int totalDataSize;
qboolean showFrames;
if(!strcmp(ri.Cmd_Argv(1), "frames"))
{
showFrames = qtrue;
}
else
{
showFrames = qfalse;
}
total = 0;
totalDataSize = 0;
for(i = 1; i < tr.numModels; i++)
{
mod = tr.models[i];
if(mod->type == MOD_MESH)
{
for(j = 0; j < MD3_MAX_LODS; j++)
{
if(mod->mdv[j] && mod->mdv[j] != mod->mdv[j - 1])
{
mdvModel_t *mdvModel;
mdvSurface_t *mdvSurface;
mdvTagName_t *mdvTagName;
mdvModel = mod->mdv[j];
total++;
ri.Printf(PRINT_ALL, "%d.%02d MB '%s' LOD = %i\n", mod->dataSize / (1024 * 1024),
(mod->dataSize % (1024 * 1024)) * 100 / (1024 * 1024),
mod->name, j);
if(showFrames && mdvModel->numFrames > 1)
{
ri.Printf(PRINT_ALL, "\tnumSurfaces = %i\n", mdvModel->numSurfaces);
ri.Printf(PRINT_ALL, "\tnumFrames = %i\n", mdvModel->numFrames);
for(k = 0, mdvSurface = mdvModel->surfaces; k < mdvModel->numSurfaces; k++, mdvSurface++)
{
ri.Printf(PRINT_ALL, "\t\tmesh = '%s'\n", mdvSurface->name);
ri.Printf(PRINT_ALL, "\t\t\tnumVertexes = %i\n", mdvSurface->numVerts);
ri.Printf(PRINT_ALL, "\t\t\tnumTriangles = %i\n", mdvSurface->numTriangles);
}
}
ri.Printf(PRINT_ALL, "\t\tnumTags = %i\n", mdvModel->numTags);
for(k = 0, mdvTagName = mdvModel->tagNames; k < mdvModel->numTags; k++, mdvTagName++)
{
ri.Printf(PRINT_ALL, "\t\t\ttagName = '%s'\n", mdvTagName->name);
}
}
}
}
else
{
ri.Printf(PRINT_ALL, "%d.%02d MB '%s'\n", mod->dataSize / (1024 * 1024),
(mod->dataSize % (1024 * 1024)) * 100 / (1024 * 1024),
mod->name);
total++;
}
totalDataSize += mod->dataSize;
}
ri.Printf(PRINT_ALL, " %d.%02d MB total model memory\n", totalDataSize / (1024 * 1024),
(totalDataSize % (1024 * 1024)) * 100 / (1024 * 1024));
ri.Printf(PRINT_ALL, " %i total models\n\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 int R_GetTag(mdvModel_t * model, int frame, const char *_tagName, int startTagIndex, mdvTag_t ** outTag)
{
int i;
mdvTag_t *tag;
mdvTagName_t *tagName;
// it is possible to have a bad frame while changing models, so don't error
frame = Q_bound(0, frame, model->numFrames - 1);
if(startTagIndex > model->numTags)
{
*outTag = NULL;
return -1;
}
#if 1
tag = model->tags + frame * model->numTags;
tagName = model->tagNames;
for(i = 0; i < model->numTags; i++, tag++, tagName++)
{
if((i >= startTagIndex) && !strcmp(tagName->name, _tagName))
{
*outTag = tag;
return i;
}
}
#endif
*outTag = NULL;
return -1;
}
/*
================
RE_LerpTagQ3A
================
*/
int RE_LerpTagQ3A(orientation_t * tag, qhandle_t handle, int startFrame, int endFrame, float frac, const char *tagNameIn)
{
mdvTag_t *start, *end;
int i;
float frontLerp, backLerp;
model_t *model;
char tagName[MAX_QPATH];
int retval;
Q_strncpyz(tagName, tagNameIn, MAX_QPATH);
model = R_GetModelByHandle(handle);
if(!model->mdv[0])
{
AxisClear(tag->axis);
VectorClear(tag->origin);
return -1;
}
start = end = NULL;
retval = R_GetTag(model->mdv[0], startFrame, tagName, 0, &start);
retval = R_GetTag(model->mdv[0], endFrame, tagName, 0, &end);
if(!start || !end)
{
AxisClear(tag->axis);
VectorClear(tag->origin);
return -1;
}
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 retval;
}
/*
================
RE_LerpTag
================
*/
#if defined(COMPAT_ET)
int RE_LerpTagET(orientation_t * tag, const refEntity_t * refent, const char *tagNameIn, int startIndex)
{
mdvTag_t *start, *end;
int i;
float frontLerp, backLerp;
model_t *model;
char tagName[MAX_QPATH]; //, *ch;
int retval;
qhandle_t handle;
int startFrame, endFrame;
float frac;
handle = refent->hModel;
startFrame = refent->oldframe;
endFrame = refent->frame;
frac = 1.0 - refent->backlerp;
Q_strncpyz(tagName, tagNameIn, MAX_QPATH);
/*
// if the tagName has a space in it, then it is passing through the starting tag number
if (ch = strrchr(tagName, ' ')) {
*ch = 0;
ch++;
startIndex = atoi(ch);
}
*/
model = R_GetModelByHandle(handle);
/*
if(!model->mdv[0]) //if(!model->model.md3[0] && !model->model.mdc[0] && !model->model.mds)
{
AxisClear(tag->axis);
VectorClear(tag->origin);
return -1;
}
*/
frontLerp = frac;
backLerp = 1.0 - frac;
start = end = NULL;
if(model->type == MOD_MESH)
{
// old MD3 style
retval = R_GetTag(model->mdv[0], startFrame, tagName, startIndex, &start);
retval = R_GetTag(model->mdv[0], endFrame, tagName, startIndex, &end);
}
/*
else if(model->type == MOD_MDS)
{
// use bone lerping
retval = R_GetBoneTag(tag, model->model.mds, startIndex, refent, tagNameIn);
if(retval >= 0)
{
return retval;
}
// failed
return -1;
}
*/
else if(model->type == MOD_MDM)
{
// use bone lerping
retval = R_MDM_GetBoneTag(tag, model->mdm, startIndex, refent, tagNameIn);
if(retval >= 0)
{
return retval;
}
// failed
return -1;
}
/*
else
{
// psuedo-compressed MDC tags
mdcTag_t *cstart, *cend;
retval = R_GetMDCTag((byte *) model->model.mdc[0], startFrame, tagName, startIndex, &cstart);
retval = R_GetMDCTag((byte *) model->model.mdc[0], endFrame, tagName, startIndex, &cend);
// uncompress the MDC tags into MD3 style tags
if(cstart && cend)
{
for(i = 0; i < 3; i++)
{
ustart.origin[i] = (float)cstart->xyz[i] * MD3_XYZ_SCALE;
uend.origin[i] = (float)cend->xyz[i] * MD3_XYZ_SCALE;
sangles[i] = (float)cstart->angles[i] * MDC_TAG_ANGLE_SCALE;
eangles[i] = (float)cend->angles[i] * MDC_TAG_ANGLE_SCALE;
}
AnglesToAxis(sangles, ustart.axis);
AnglesToAxis(eangles, uend.axis);
start = &ustart;
end = &uend;
}
else
{
start = NULL;
end = NULL;
}
}
*/
if(!start || !end)
{
AxisClear(tag->axis);
VectorClear(tag->origin);
return -1;
}
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 retval;
}
#endif
/*
================
RE_BoneIndex
================
*/
int RE_BoneIndex(qhandle_t hModel, const char *boneName)
{
int i;
md5Bone_t *bone;
md5Model_t *md5;
model_t *model;
model = R_GetModelByHandle(hModel);
if(!model->md5)
{
return -1;
}
else
{
md5 = model->md5;
}
for(i = 0, bone = md5->bones; i < md5->numBones; i++, bone++)
{
if(!Q_stricmp(bone->name, boneName))
{
return i;
}
}
return -1;
}
/*
====================
R_ModelBounds
====================
*/
void R_ModelBounds(qhandle_t handle, vec3_t mins, vec3_t maxs)
{
model_t *model;
mdvModel_t *header;
mdvFrame_t *frame;
model = R_GetModelByHandle(handle);
if(model->bsp)
{
VectorCopy(model->bsp->bounds[0], mins);
VectorCopy(model->bsp->bounds[1], maxs);
}
else if(model->mdv[0])
{
header = model->mdv[0];
frame = header->frames;
VectorCopy(frame->bounds[0], mins);
VectorCopy(frame->bounds[1], maxs);
}
else if(model->md5)
{
VectorCopy(model->md5->bounds[0], mins);
VectorCopy(model->md5->bounds[1], maxs);
}
else
{
VectorClear(mins);
VectorClear(maxs);
}
}