raze/polymer/eduke32/build/src/mdsprite.c
hendricks266 e00115c043 Introduce USE_GLEXT macro and use it to conditionally compile out all GL ARB function calls when compiling for GL ES platforms.
This replaces the previous hack where we kept the dynamic pointers null and simply never tried calling any of them by a conspiracy of default runtime settings and disabled menu options.

Code affected: VBO support for models, VPX GLSL decoding, texture compression (texture cache), and multitexturing (glow/detail maps).

This commit also replaces EDUKE32_GLES conditionals with USE_GLEXT where appropriate.

I didn't touch polymer.c because it depends too heavily on extensions for conditionally compiling them to make sense.

git-svn-id: https://svn.eduke32.com/eduke32@5526 1a8010ca-5511-0410-912e-c29ae57300e0
2016-01-08 01:33:20 +00:00

2706 lines
84 KiB
C

//------------------------------------- MD2/MD3 LIBRARY BEGINS -------------------------------------
#ifdef USE_OPENGL
#include "compat.h"
#include "build.h"
#include "glbuild.h"
#include "pragmas.h"
#include "baselayer.h"
#include "engine_priv.h"
#include "hightile.h"
#include "polymost.h"
#include "texcache.h"
#include "mdsprite.h"
#include "cache1d.h"
#include "kplib.h"
#include "common.h"
#include <math.h>
#include <float.h>
static int32_t curextra=MAXTILES;
#define MIN_CACHETIME_PRINT 10
static void QuitOnFatalError(const char *msg)
{
if (msg)
initprintf("%s\n", msg);
uninitengine();
Bexit(1);
}
static int32_t addtileP(int32_t model,int32_t tile,int32_t pallet)
{
// tile >= 0 && tile < MAXTILES
UNREFERENCED_PARAMETER(model);
if (curextra==MAXTILES+EXTRATILES-1)
{
initprintf("warning: max EXTRATILES reached\n");
return curextra;
}
if (tile2model[tile].modelid==-1)
{
tile2model[tile].pal=pallet;
return tile;
}
if (tile2model[tile].pal==pallet)
return tile;
while (tile2model[tile].nexttile!=-1)
{
tile=tile2model[tile].nexttile;
if (tile2model[tile].pal==pallet)
return tile;
}
tile2model[tile].nexttile=curextra;
tile2model[curextra].pal=pallet;
return curextra++;
}
int32_t Ptile2tile(int32_t tile,int32_t palette)
{
int t = tile;
while ((tile = tile2model[tile].nexttile) != -1)
if (tile2model[tile].pal == palette)
{
t = tile;
break;
}
return t;
}
#define MODELALLOCGROUP 256
static int32_t nummodelsalloced = 0;
static int32_t maxmodelverts = 0, allocmodelverts = 0;
static int32_t maxmodeltris = 0, allocmodeltris = 0;
static vec3f_t *vertlist = NULL; //temp array to store interpolated vertices for drawing
#ifdef USE_GLEXT
static int32_t allocvbos = 0, curvbo = 0;
static GLuint *vertvbos = NULL;
static GLuint *indexvbos = NULL;
#endif
#ifdef POLYMER
static int32_t *tribuf = NULL;
static int32_t tribufverts = 0;
#endif
static mdmodel_t *mdload(const char *);
static void mdfree(mdmodel_t *);
int32_t globalnoeffect=0;
extern int32_t timerticspersec;
#ifdef USE_GLEXT
void md_freevbos()
{
int32_t i;
for (i=0; i<nextmodelid; i++)
if (models[i]->mdnum == 3)
{
md3model_t *m = (md3model_t *)models[i];
if (m->vbos)
{
// OSD_Printf("freeing model %d vbo\n",i);
bglDeleteBuffersARB(m->head.numsurfs, m->vbos);
DO_FREE_AND_NULL(m->vbos);
}
}
if (allocvbos)
{
bglDeleteBuffersARB(allocvbos, indexvbos);
bglDeleteBuffersARB(allocvbos, vertvbos);
allocvbos = 0;
}
}
#endif
void freeallmodels()
{
int32_t i;
if (models)
{
for (i=0; i<nextmodelid; i++) mdfree(models[i]);
DO_FREE_AND_NULL(models);
nummodelsalloced = 0;
nextmodelid = 0;
}
Bmemset(tile2model,-1,sizeof(tile2model));
for (i=0; i<MAXTILES; i++)
Bmemset(tile2model[i].hudmem, 0, sizeof(tile2model[i].hudmem));
curextra=MAXTILES;
if (vertlist)
{
DO_FREE_AND_NULL(vertlist);
allocmodelverts = maxmodelverts = 0;
allocmodeltris = maxmodeltris = 0;
}
#ifdef USE_GLEXT
md_freevbos();
#endif
#ifdef POLYMER
DO_FREE_AND_NULL(tribuf);
#endif
}
// Skin texture names can be aliased! This is ugly, but at least correct.
static void nullskintexids(GLuint texid)
{
int32_t i, j;
for (i=0; i<nextmodelid; i++)
{
mdmodel_t *m = models[i];
if (m->mdnum == 2 || m->mdnum == 3)
{
mdskinmap_t *sk;
md2model_t *m2 = (md2model_t *)m;
for (j=0; j<m2->numskins*(HICEFFECTMASK+1); j++)
if (m2->texid[j] == texid)
m2->texid[j] = 0;
for (sk=m2->skinmap; sk; sk=sk->next)
for (j=0; j<(HICEFFECTMASK+1); j++)
if (sk->texid[j] == texid)
sk->texid[j] = 0;
}
}
}
void clearskins(int32_t type)
{
int32_t i, j;
for (i=0; i<nextmodelid; i++)
{
mdmodel_t *m = models[i];
if (m->mdnum == 1)
{
voxmodel_t *v = (voxmodel_t *)m;
for (j=0; j<MAXPALOOKUPS; j++)
if (v->texid[j])
{
bglDeleteTextures(1, &v->texid[j]);
v->texid[j] = 0;
}
}
else if ((m->mdnum == 2 || m->mdnum == 3) && type == INVALIDATE_ALL)
{
mdskinmap_t *sk;
md2model_t *m2 = (md2model_t *)m;
for (j=0; j<m2->numskins*(HICEFFECTMASK+1); j++)
if (m2->texid[j])
{
GLuint otexid = m2->texid[j];
bglDeleteTextures(1, &m2->texid[j]);
m2->texid[j] = 0;
nullskintexids(otexid);
}
for (sk=m2->skinmap; sk; sk=sk->next)
for (j=0; j<(HICEFFECTMASK+1); j++)
if (sk->texid[j])
{
GLuint otexid = sk->texid[j];
bglDeleteTextures(1, &sk->texid[j]);
sk->texid[j] = 0;
nullskintexids(otexid);
}
}
}
for (i=0; i<MAXVOXELS; i++)
{
voxmodel_t *v = voxmodels[i];
if (!v) continue;
for (j=0; j<MAXPALOOKUPS; j++)
if (v->texid[j])
{
bglDeleteTextures(1, &v->texid[j]);
v->texid[j] = 0;
}
}
}
void mdinit()
{
freeallmodels();
mdinited = 1;
}
int32_t md_loadmodel(const char *fn)
{
mdmodel_t *vm, **ml;
if (!mdinited) mdinit();
if (nextmodelid >= nummodelsalloced)
{
ml = (mdmodel_t **)Xrealloc(models,(nummodelsalloced+MODELALLOCGROUP)*sizeof(void *));
models = ml; nummodelsalloced += MODELALLOCGROUP;
}
vm = mdload(fn); if (!vm) return(-1);
models[nextmodelid++] = vm;
return(nextmodelid-1);
}
int32_t md_setmisc(int32_t modelid, float scale, int32_t shadeoff, float zadd, float yoffset, int32_t flags)
{
mdmodel_t *m;
if (!mdinited) mdinit();
if ((uint32_t)modelid >= (uint32_t)nextmodelid) return -1;
m = models[modelid];
m->bscale = scale;
m->shadeoff = shadeoff;
m->zadd = zadd;
m->yoffset = yoffset;
m->flags = flags;
return 0;
}
static int32_t framename2index(mdmodel_t *vm, const char *nam)
{
int32_t i = 0;
switch (vm->mdnum)
{
case 2:
{
md2model_t *m = (md2model_t *)vm;
md2frame_t *fr;
for (i=0; i<m->numframes; i++)
{
fr = (md2frame_t *)&m->frames[i*m->framebytes];
if (!Bstrcmp(fr->name, nam)) break;
}
}
break;
case 3:
{
md3model_t *m = (md3model_t *)vm;
for (i=0; i<m->numframes; i++)
if (!Bstrcmp(m->head.frames[i].nam,nam)) break;
}
break;
}
return(i);
}
int32_t md_defineframe(int32_t modelid, const char *framename, int32_t tilenume, int32_t skinnum, float smoothduration, int32_t pal)
{
md2model_t *m;
int32_t i;
if (!mdinited) mdinit();
if ((uint32_t)modelid >= (uint32_t)nextmodelid) return(-1);
if ((uint32_t)tilenume >= (uint32_t)MAXTILES) return(-2);
if (!framename) return(-3);
tilenume=addtileP(modelid,tilenume,pal);
m = (md2model_t *)models[modelid];
if (m->mdnum == 1)
{
tile2model[tilenume].modelid = modelid;
tile2model[tilenume].framenum = tile2model[tilenume].skinnum = 0;
return 0;
}
i = framename2index((mdmodel_t *)m,framename);
if (i == m->numframes) return(-3); // frame name invalid
tile2model[tilenume].modelid = modelid;
tile2model[tilenume].framenum = i;
tile2model[tilenume].skinnum = skinnum;
tile2model[tilenume].smoothduration = Blrintf((float)UINT16_MAX * smoothduration);
return i;
}
int32_t md_defineanimation(int32_t modelid, const char *framestart, const char *frameend, int32_t fpssc, int32_t flags)
{
md2model_t *m;
mdanim_t ma, *map;
int32_t i;
if (!mdinited) mdinit();
if ((uint32_t)modelid >= (uint32_t)nextmodelid) return(-1);
Bmemset(&ma, 0, sizeof(ma));
m = (md2model_t *)models[modelid];
if (m->mdnum < 2) return 0;
//find index of start frame
i = framename2index((mdmodel_t *)m,framestart);
if (i == m->numframes) return -2;
ma.startframe = i;
//find index of finish frame which must trail start frame
i = framename2index((mdmodel_t *)m,frameend);
if (i == m->numframes) return -3;
ma.endframe = i;
ma.fpssc = fpssc;
ma.flags = flags;
map = (mdanim_t *)Xmalloc(sizeof(mdanim_t));
Bmemcpy(map, &ma, sizeof(ma));
map->next = m->animations;
m->animations = map;
return(0);
}
#if 0
// FIXME: CURRENTLY DISABLED: interpolation may access frames we consider 'unused'?
int32_t md_thinoutmodel(int32_t modelid, uint8_t *usedframebitmap)
{
md3model_t *m;
md3surf_t *s;
mdanim_t *anm;
int32_t i, surfi, sub, usedframes;
static int16_t otonframe[1024];
if ((uint32_t)modelid >= (uint32_t)nextmodelid) return -1;
m = (md3model_t *)models[modelid];
if (m->mdnum != 3) return -2;
for (anm=m->animations; anm; anm=anm->next)
{
if (anm->endframe <= anm->startframe)
{
// initprintf("backward anim %d-%d\n", anm->startframe, anm->endframe);
return -3;
}
for (i=anm->startframe; i<anm->endframe; i++)
usedframebitmap[i>>3] |= (1<<(i&7));
}
sub = 0;
for (i=0; i<m->numframes; i++)
{
if (!(usedframebitmap[i>>3]&(1<<(i&7))))
{
sub++;
otonframe[i] = -1;
continue;
}
otonframe[i] = i-sub;
}
usedframes = m->numframes - sub;
if (usedframes==0 || usedframes==m->numframes)
return usedframes;
//// THIN OUT! ////
for (i=0; i<m->numframes; i++)
{
if (otonframe[i]>=0 && otonframe[i] != i)
{
if (m->muladdframes)
Bmemcpy(&m->muladdframes[2*otonframe[i]], &m->muladdframes[2*i], 2*sizeof(vec3f_t));
Bmemcpy(&m->head.frames[otonframe[i]], &m->head.frames[i], sizeof(md3frame_t));
}
}
for (surfi=0; surfi < m->head.numsurfs; surfi++)
{
s = &m->head.surfs[surfi];
for (i=0; i<m->numframes; i++)
if (otonframe[i]>=0 && otonframe[i] != i)
Bmemcpy(&s->xyzn[otonframe[i]*s->numverts], &s->xyzn[i*s->numverts], s->numverts*sizeof(md3xyzn_t));
}
////// tweak frame indices in various places
for (anm=m->animations; anm; anm=anm->next)
{
if (otonframe[anm->startframe]==-1 || otonframe[anm->endframe-1]==-1)
initprintf("md %d WTF: anm %d %d\n", modelid, anm->startframe, anm->endframe);
anm->startframe = otonframe[anm->startframe];
anm->endframe = otonframe[anm->endframe-1];
}
for (i=0; i<MAXTILES+EXTRATILES; i++)
if (tile2model[i].modelid == modelid)
{
if (otonframe[tile2model[i].framenum]==-1)
initprintf("md %d WTF: tile %d, fr %d\n", modelid, i, tile2model[i].framenum);
tile2model[i].framenum = otonframe[tile2model[i].framenum];
}
////// realloc & change "numframes" everywhere
if (m->muladdframes)
m->muladdframes = Xrealloc(m->muladdframes, 2*sizeof(vec3f_t)*usedframes);
m->head.frames = Xrealloc(m->head.frames, sizeof(md3frame_t)*usedframes);
for (surfi=0; surfi < m->head.numsurfs; surfi++)
{
m->head.surfs[surfi].numframes = usedframes;
// CAN'T do that because xyzn is offset from a larger block when loaded from md3:
// m->head.surfs[surfi].xyzn = Xrealloc(m->head.surfs[surfi].xyzn, s->numverts*usedframes*sizeof(md3xyzn_t));
}
m->head.numframes = usedframes;
m->numframes = usedframes;
////////////
return usedframes;
}
#endif
int32_t md_defineskin(int32_t modelid, const char *skinfn, int32_t palnum, int32_t skinnum, int32_t surfnum, float param, float specpower, float specfactor, int32_t flags)
{
mdskinmap_t *sk, *skl;
md2model_t *m;
if (!mdinited) mdinit();
if ((uint32_t)modelid >= (uint32_t)nextmodelid) return -1;
if (!skinfn) return -2;
if ((unsigned)palnum >= (unsigned)MAXPALOOKUPS) return -3;
m = (md2model_t *)models[modelid];
if (m->mdnum < 2) return 0;
if (m->mdnum == 2) surfnum = 0;
skl = NULL;
for (sk = m->skinmap; sk; skl = sk, sk = sk->next)
if (sk->palette == (uint8_t)palnum && skinnum == sk->skinnum && surfnum == sk->surfnum)
break;
if (!sk)
{
sk = (mdskinmap_t *)Xcalloc(1,sizeof(mdskinmap_t));
if (!skl) m->skinmap = sk;
else skl->next = sk;
}
else Bfree(sk->fn);
sk->palette = (uint8_t)palnum;
sk->flags = (uint8_t)flags;
sk->skinnum = skinnum;
sk->surfnum = surfnum;
sk->param = param;
sk->specpower = specpower;
sk->specfactor = specfactor;
sk->fn = Xstrdup(skinfn);
return 0;
}
int32_t md_definehud(int32_t modelid, int32_t tilex, vec3f_t add, int32_t angadd, int32_t flags, int32_t fov)
{
if (!mdinited) mdinit();
if ((uint32_t)modelid >= (uint32_t)nextmodelid) return -1;
if ((uint32_t)tilex >= (uint32_t)MAXTILES) return -2;
tile2model[tilex].hudmem[(flags>>2)&1] = (hudtyp *)Xmalloc(sizeof(hudtyp));
hudtyp * const hud = tile2model[tilex].hudmem[(flags>>2)&1];
hud->add = add;
hud->angadd = ((int16_t)angadd)|2048;
hud->flags = (int16_t)flags;
hud->fov = (int16_t)fov;
return 0;
}
int32_t md_undefinetile(int32_t tile)
{
if (!mdinited) return 0;
if ((unsigned)tile >= (unsigned)MAXTILES) return -1;
tile2model[tile].modelid = -1;
tile2model[tile].nexttile = -1;
DO_FREE_AND_NULL(tile2model[tile].hudmem[0]);
DO_FREE_AND_NULL(tile2model[tile].hudmem[1]);
return 0;
}
/* this function is problematic, it leaves NULL holes in model[]
* (which runs from 0 to nextmodelid-1) */
int32_t md_undefinemodel(int32_t modelid)
{
int32_t i;
if (!mdinited) return 0;
if ((uint32_t)modelid >= (uint32_t)nextmodelid) return -1;
for (i=MAXTILES+EXTRATILES-1; i>=0; i--)
if (tile2model[i].modelid == modelid)
{
tile2model[i].modelid = -1;
DO_FREE_AND_NULL(tile2model[i].hudmem[0]);
DO_FREE_AND_NULL(tile2model[i].hudmem[1]);
}
if (models)
{
mdfree(models[modelid]);
models[modelid] = NULL;
}
return 0;
}
static int32_t daskinloader(int32_t filh, intptr_t *fptr, int32_t *bpl, int32_t *sizx, int32_t *sizy,
int32_t *osizx, int32_t *osizy, char *hasalpha, int32_t pal, char effect)
{
char *cptr,al=255;
coltype *pic;
int32_t xsiz, ysiz, tsizx = 0, tsizy = 0;
int32_t r, g, b;
int32_t j, y, x;
int32_t isart = 0;
int32_t const picfillen = kpzbufloadfil(filh);
// tsizx/y = replacement texture's natural size
// xsiz/y = 2^x size of replacement
#ifdef WITHKPLIB
kpgetdim(kpzbuf,picfillen,&tsizx,&tsizy);
#endif
if (tsizx == 0 || tsizy == 0)
{
if (E_CheckUnitArtFileHeader((uint8_t *)kpzbuf, picfillen))
return -2;
tsizx = B_LITTLE16(B_UNBUF16(&kpzbuf[16]));
tsizy = B_LITTLE16(B_UNBUF16(&kpzbuf[18]));
if (tsizx == 0 || tsizy == 0)
return -2;
isart = 1;
}
if (!glinfo.texnpot)
{
for (xsiz=1; xsiz<tsizx; xsiz+=xsiz);
for (ysiz=1; ysiz<tsizy; ysiz+=ysiz);
}
else
{
xsiz = tsizx;
ysiz = tsizy;
}
*osizx = tsizx; *osizy = tsizy;
if (isart)
{
if (tsizx * tsizy + ARTv1_UNITOFFSET > picfillen)
return -2;
}
int32_t const bytesperline = xsiz * sizeof(coltype);
pic = (coltype *)Xcalloc(ysiz, bytesperline);
if (isart)
{
E_RenderArtDataIntoBuffer((palette_t *)pic, (uint8_t *)&kpzbuf[ARTv1_UNITOFFSET], xsiz, tsizx, tsizy);
}
#ifdef WITHKPLIB
else
{
if (kprender(kpzbuf,picfillen,(intptr_t)pic,bytesperline,xsiz,ysiz))
{
Bfree(pic);
return -2;
}
}
#endif
cptr = &britable[gammabrightness ? 0 : curbrightness][0];
r=(glinfo.bgra)?hictinting[pal].b:hictinting[pal].r;
g=hictinting[pal].g;
b=(glinfo.bgra)?hictinting[pal].r:hictinting[pal].b;
for (y=0,j=0; y<tsizy; y++,j+=xsiz)
{
coltype *rpptr = &pic[j], tcol;
for (x=0; x<tsizx; x++)
{
tcol.b = cptr[rpptr[x].b];
tcol.g = cptr[rpptr[x].g];
tcol.r = cptr[rpptr[x].r];
if (effect & HICTINT_GRAYSCALE)
{
tcol.g = tcol.r = tcol.b = (uint8_t)((tcol.r * GRAYSCALE_COEFF_RED) + (tcol.g * GRAYSCALE_COEFF_GREEN) +
(tcol.b * GRAYSCALE_COEFF_BLUE));
}
if (effect & HICTINT_INVERT)
{
tcol.b = 255-tcol.b;
tcol.g = 255-tcol.g;
tcol.r = 255-tcol.r;
}
if (effect & HICTINT_COLORIZE)
{
tcol.b = min((int32_t)(tcol.b)*b/64,255);
tcol.g = min((int32_t)(tcol.g)*g/64,255);
tcol.r = min((int32_t)(tcol.r)*r/64,255);
}
switch (effect & HICTINT_BLENDMASK)
{
case HICTINT_BLEND_SCREEN:
tcol.b = 255 - (((255 - tcol.b) * (255 - b)) >> 8);
tcol.g = 255 - (((255 - tcol.g) * (255 - g)) >> 8);
tcol.r = 255 - (((255 - tcol.r) * (255 - r)) >> 8);
break;
case HICTINT_BLEND_OVERLAY:
tcol.b = tcol.b < 128 ? (tcol.b * b) >> 7 : 255 - (((255 - tcol.b) * (255 - b)) >> 7);
tcol.g = tcol.g < 128 ? (tcol.g * g) >> 7 : 255 - (((255 - tcol.g) * (255 - g)) >> 7);
tcol.r = tcol.r < 128 ? (tcol.r * r) >> 7 : 255 - (((255 - tcol.r) * (255 - r)) >> 7);
break;
case HICTINT_BLEND_HARDLIGHT:
tcol.b = b < 128 ? (tcol.b * b) >> 7 : 255 - (((255 - tcol.b) * (255 - b)) >> 7);
tcol.g = g < 128 ? (tcol.g * g) >> 7 : 255 - (((255 - tcol.g) * (255 - g)) >> 7);
tcol.r = r < 128 ? (tcol.r * r) >> 7 : 255 - (((255 - tcol.r) * (255 - r)) >> 7);
break;
}
rpptr[x].b = tcol.b;
rpptr[x].g = tcol.g;
rpptr[x].r = tcol.r;
al &= rpptr[x].a;
}
}
if (!glinfo.bgra)
{
for (j=xsiz*ysiz-1; j>=0; j--)
{
swapchar(&pic[j].r, &pic[j].b);
}
}
*sizx = xsiz;
*sizy = ysiz;
*bpl = xsiz;
*fptr = (intptr_t)pic;
*hasalpha = (al != 255);
return 0;
}
static inline int32_t hicfxmask(int32_t pal)
{
return globalnoeffect ? 0 : (hictinting[pal].f & HICEFFECTMASK);
}
//Note: even though it says md2model, it works for both md2model&md3model
int32_t mdloadskin(md2model_t *m, int32_t number, int32_t pal, int32_t surf)
{
int32_t i, bpl, osizx, osizy, texfmt = GL_RGBA, intexfmt = GL_RGBA;
char *skinfile, hasalpha, fn[BMAX_PATH];
GLuint *texidx = NULL;
mdskinmap_t *sk, *skzero = NULL;
int32_t doalloc = 1, filh;
int32_t gotcache, picfillen;
texcacheheader cachead;
int32_t startticks, willprint=0;
vec2_t siz = { 0, 0 };
if (m->mdnum == 2)
surf = 0;
if ((unsigned)pal >= (unsigned)MAXPALOOKUPS)
return 0;
i = -1;
for (sk = m->skinmap; sk; sk = sk->next)
{
if (sk->palette == pal && sk->skinnum == number && sk->surfnum == surf)
{
skinfile = sk->fn;
texidx = &sk->texid[hicfxmask(pal)];
Bstrncpyz(fn, skinfile, BMAX_PATH);
//OSD_Printf("Using exact match skin (pal=%d,skinnum=%d,surfnum=%d) %s\n",pal,number,surf,skinfile);
break;
}
//If no match, give highest priority to number, then pal.. (Parkar's request, 02/27/2005)
else if ((sk->palette == 0) && (sk->skinnum == number) && (sk->surfnum == surf) && (i < 5)) { i = 5; skzero = sk; }
else if ((sk->palette == pal) && (sk->skinnum == 0) && (sk->surfnum == surf) && (i < 4)) { i = 4; skzero = sk; }
else if ((sk->palette == 0) && (sk->skinnum == 0) && (sk->surfnum == surf) && (i < 3)) { i = 3; skzero = sk; }
else if ((sk->palette == 0) && (sk->skinnum == number) && (i < 2)) { i = 2; skzero = sk; }
else if ((sk->palette == pal) && (sk->skinnum == 0) && (i < 1)) { i = 1; skzero = sk; }
else if ((sk->palette == 0) && (sk->skinnum == 0) && (i < 0)) { i = 0; skzero = sk; }
}
if (!sk)
{
if (pal >= (MAXPALOOKUPS - RESERVEDPALS))
return (0);
if (skzero)
{
skinfile = skzero->fn;
texidx = &skzero->texid[hicfxmask(pal)];
Bstrncpyz(fn, skinfile, BMAX_PATH);
//OSD_Printf("Using def skin 0,0 as fallback, pal=%d\n", pal);
}
else
return 0;
#if 0
{
// fall back to the model-defined texture
if ((unsigned)number >= (unsigned)m->numskins)
number = 0;
// m->skinfn is undefined when md3model_t is cast to md2model_t --> crash
skinfile = m->skinfn + number*64;
texidx = &m->texid[number*(HICEFFECTMASK+1) + hicfxmask(pal)];
Bstrncpyz(fn, m->basepath, BMAX_PATH);
if ((Bstrlen(fn) + Bstrlen(skinfile)) < BMAX_PATH)
Bstrcat(fn,skinfile);
//OSD_Printf("Using MD2/MD3 skin (%d) %s, pal=%d\n",number,skinfile,pal);
}
#endif
}
if (!skinfile[0])
return 0;
if (*texidx)
return *texidx;
// possibly fetch an already loaded multitexture :_)
if (pal >= (MAXPALOOKUPS - RESERVEDPALS))
for (i=0; i<nextmodelid; i++)
for (skzero = ((md2model_t *)models[i])->skinmap; skzero; skzero = skzero->next)
if (!Bstrcasecmp(skzero->fn, sk->fn) && skzero->texid[hicfxmask(pal)])
{
int32_t f = hicfxmask(pal);
sk->texid[f] = skzero->texid[f];
return sk->texid[f];
}
// for sk->flags below
if (!sk)
sk = skzero;
*texidx = 0;
if ((filh = kopen4load(fn, 0)) < 0)
{
OSD_Printf("Skin \"%s\" not found.\n",fn);
skinfile[0] = 0;
return 0;
}
picfillen = kfilelength(filh);
kclose(filh); // FIXME: shouldn't have to do this. bug in cache1d.c
startticks = getticks();
gotcache = texcache_readtexheader(fn, picfillen, pal<<8, hicfxmask(pal), &cachead, 1);
if (gotcache && !texcache_loadskin(&cachead, &doalloc, texidx, &siz.x, &siz.y))
{
osizx = cachead.xdim;
osizy = cachead.ydim;
hasalpha = (cachead.flags & CACHEAD_HASALPHA) ? 1 : 0;
if (pal < (MAXPALOOKUPS - RESERVEDPALS))
m->usesalpha = hasalpha;
//kclose(filh); // FIXME: uncomment when cache1d.c is fixed
}
else
{
int32_t ret;
intptr_t fptr=0;
gotcache = 0; // the compressed version will be saved to disk
if ((filh = kopen4load(fn, 0)) < 0)
return -1;
ret = daskinloader(filh,&fptr,&bpl,&siz.x,&siz.y,&osizx,&osizy,&hasalpha,pal,hicfxmask(pal));
if (ret)
{
kclose(filh);
OSD_Printf("Failed loading skin file \"%s\": error %d\n", fn, ret);
if (ret==-1)
QuitOnFatalError("OUT OF MEMORY in daskinloader!");
skinfile[0] = 0;
return(0);
}
else kclose(filh);
willprint = 1;
if (pal < (MAXPALOOKUPS - RESERVEDPALS))
m->usesalpha = hasalpha;
if ((doalloc&3)==1)
bglGenTextures(1, texidx);
bglBindTexture(GL_TEXTURE_2D, *texidx);
//gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA,xsiz,ysiz,GL_BGRA_EXT,GL_UNSIGNED_BYTE,(char *)fptr);
#if !defined EDUKE32_GLES
if (glinfo.texcompr && glusetexcompr && !(sk->flags & HICR_NOSAVE))
intexfmt = hasalpha ? GL_COMPRESSED_RGBA_ARB : GL_COMPRESSED_RGB_ARB;
else
#endif
if (!hasalpha)
intexfmt = GL_RGB;
if (glinfo.bgra)
texfmt = GL_BGRA;
uploadtexture((doalloc&1), siz, intexfmt, texfmt, (coltype *)fptr, siz, DAMETH_HI | (sk->flags & HICR_NOCOMPRESS ? DAMETH_NOCOMPRESS : 0));
Bfree((void *)fptr);
}
if (!m->skinloaded)
{
if (siz.x != osizx || siz.y != osizy)
{
float fx, fy;
fx = ((float)osizx)/((float)siz.x);
fy = ((float)osizy)/((float)siz.y);
if (m->mdnum == 2)
{
int32_t *lptr;
for (lptr=m->glcmds; (i=*lptr++);)
for (i=labs(i); i>0; i--,lptr+=3)
{
((float *)lptr)[0] *= fx;
((float *)lptr)[1] *= fy;
}
}
else if (m->mdnum == 3)
{
md3model_t *m3 = (md3model_t *)m;
md3surf_t *s;
int32_t surfi;
for (surfi=0; surfi<m3->head.numsurfs; surfi++)
{
s = &m3->head.surfs[surfi];
for (i=s->numverts-1; i>=0; i--)
{
s->uv[i].u *= fx;
s->uv[i].v *= fy;
}
}
}
}
m->skinloaded = 1+number;
}
int32_t const filter = sk->flags & HICR_FORCEFILTER ? TEXFILTER_ON : gltexfiltermode;
bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,glfiltermodes[filter].mag);
bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,glfiltermodes[filter].min);
#ifdef USE_GLEXT
if (glinfo.maxanisotropy > 1.0)
bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_ANISOTROPY_EXT,glanisotropy);
#endif
bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
if (glinfo.texcompr && glusetexcompr && glusetexcache)
if (!gotcache && !(sk->flags & HICR_NOSAVE))
{
const int32_t nonpow2 = check_nonpow2(siz.x) || check_nonpow2(siz.y);
// save off the compressed version
cachead.quality = (sk->flags & HICR_NOCOMPRESS) ? 0 : r_downsize;
cachead.xdim = osizx>>cachead.quality;
cachead.ydim = osizy>>cachead.quality;
cachead.flags = nonpow2*CACHEAD_NONPOW2 | (hasalpha ? CACHEAD_HASALPHA : 0) |
(sk->flags & HICR_NOCOMPRESS ? CACHEAD_NOCOMPRESS : 0);
/// OSD_Printf("Caching \"%s\"\n",fn);
texcache_writetex(fn, picfillen, pal<<8, hicfxmask(pal), &cachead);
if (willprint)
{
int32_t etime = getticks()-startticks;
if (etime>=MIN_CACHETIME_PRINT)
OSD_Printf("Load skin: p%d-e%d \"%s\"... cached... %d ms\n", pal, hicfxmask(pal), fn, etime);
willprint = 0;
}
else
OSD_Printf("Cached skin \"%s\"\n", fn);
}
if (willprint)
{
int32_t etime = getticks()-startticks;
if (etime>=MIN_CACHETIME_PRINT)
OSD_Printf("Load skin: p%d-e%d \"%s\"... %d ms\n", pal, hicfxmask(pal), fn, etime);
}
return(*texidx);
}
//Note: even though it says md2model, it works for both md2model&md3model
void updateanimation(md2model_t *m, const tspritetype *tspr, uint8_t lpal)
{
const mdanim_t *anim;
int32_t i, j, k;
int32_t fps;
int32_t tile, smoothdurationp;
spritesmooth_t *smooth;
spriteext_t *sprext;
if (m->numframes < 2)
{
m->interpol = 0;
return;
}
tile = Ptile2tile(tspr->picnum,lpal);
m->cframe = m->nframe = tile2model[tile].framenum;
#ifdef DEBUGGINGAIDS
if (m->cframe >= m->numframes)
OSD_Printf("1: c > n\n");
#endif
smoothdurationp = (r_animsmoothing && (tile2model[tile].smoothduration != 0));
smooth = ((unsigned)tspr->owner < MAXSPRITES+MAXUNIQHUDID) ? &spritesmooth[tspr->owner] : NULL;
sprext = ((unsigned)tspr->owner < MAXSPRITES+MAXUNIQHUDID) ? &spriteext[tspr->owner] : NULL;
for (anim = m->animations; anim && anim->startframe != m->cframe; anim = anim->next)
{
/* do nothing */;
}
if (!anim)
{
if (!smoothdurationp || ((smooth->mdoldframe == m->cframe) && (smooth->mdcurframe == m->cframe)))
{
m->interpol = 0;
return;
}
// assert(smoothdurationp && ((smooth->mdoldframe != m->cframe) || (smooth->mdcurframe != m->cframe)))
if (smooth->mdoldframe != m->cframe)
{
if (smooth->mdsmooth == 0)
{
sprext->mdanimtims = mdtims;
m->interpol = 0;
smooth->mdsmooth = 1;
smooth->mdcurframe = m->cframe;
}
if (smooth->mdcurframe != m->cframe)
{
sprext->mdanimtims = mdtims;
m->interpol = 0;
smooth->mdsmooth = 1;
smooth->mdoldframe = smooth->mdcurframe;
smooth->mdcurframe = m->cframe;
}
}
else // if (smooth->mdcurframe != m->cframe)
{
sprext->mdanimtims = mdtims;
m->interpol = 0;
smooth->mdsmooth = 1;
smooth->mdoldframe = smooth->mdcurframe;
smooth->mdcurframe = m->cframe;
}
}
else if (/* anim && */ sprext->mdanimcur != anim->startframe)
{
//if (sprext->flags & SPREXT_NOMDANIM) OSD_Printf("SPREXT_NOMDANIM\n");
//OSD_Printf("smooth launched ! oldanim %i new anim %i\n", sprext->mdanimcur, anim->startframe);
sprext->mdanimcur = (int16_t)anim->startframe;
sprext->mdanimtims = mdtims;
m->interpol = 0;
if (!smoothdurationp)
{
m->cframe = m->nframe = anim->startframe;
#ifdef DEBUGGINGAIDS
if (m->cframe >= m->numframes)
OSD_Printf("2: c > n\n");
#endif
goto prep_return;
}
m->nframe = anim->startframe;
m->cframe = smooth->mdoldframe;
#ifdef DEBUGGINGAIDS
if (m->cframe >= m->numframes)
OSD_Printf("3: c > n\n");
#endif
smooth->mdsmooth = 1;
goto prep_return;
}
fps = smooth->mdsmooth ?
Blrintf((1.0f / ((float)tile2model[tile].smoothduration * (1.f / (float)UINT16_MAX))) * 66.f) :
anim->fpssc;
i = (mdtims - sprext->mdanimtims)*((fps*timerticspersec)/120);
if (smooth->mdsmooth)
j = 65536;
else
j = ((anim->endframe+1-anim->startframe)<<16);
// XXX: Just in case you play the game for a VERY long time...
if (i < 0) { i = 0; sprext->mdanimtims = mdtims; }
//compare with j*2 instead of j to ensure i stays > j-65536 for MDANIM_ONESHOT
if (anim && (i >= j+j) && (fps) && !mdpause) //Keep mdanimtims close to mdtims to avoid the use of MOD
sprext->mdanimtims += j/((fps*timerticspersec)/120);
k = i;
if (anim && (anim->flags&MDANIM_ONESHOT))
{ if (i > j-65536) i = j-65536; }
else { if (i >= j) { i -= j; if (i >= j) i %= j; } }
if (r_animsmoothing && smooth->mdsmooth)
{
m->nframe = anim ? anim->startframe : smooth->mdcurframe;
m->cframe = smooth->mdoldframe;
#ifdef DEBUGGINGAIDS
if (m->cframe >= m->numframes)
OSD_Printf("4: c > n\n");
#endif
//OSD_Printf("smoothing... cframe %i nframe %i\n", m->cframe, m->nframe);
if (k > 65535)
{
sprext->mdanimtims = mdtims;
m->interpol = 0;
smooth->mdsmooth = 0;
m->cframe = m->nframe; // = anim ? anim->startframe : smooth->mdcurframe;
#ifdef DEBUGGINGAIDS
if (m->cframe >= m->numframes)
OSD_Printf("5: c > n\n");
#endif
smooth->mdoldframe = m->cframe;
//OSD_Printf("smooth stopped !\n");
goto prep_return;
}
}
else
{
m->cframe = (i>>16)+anim->startframe;
#ifdef DEBUGGINGAIDS
if (m->cframe >= m->numframes)
OSD_Printf("6: c > n\n");
#endif
m->nframe = m->cframe+1;
if (m->nframe > anim->endframe) // VERIFY: (!(r_animsmoothing && smooth->mdsmooth)) implies (anim!=NULL) ?
m->nframe = anim->startframe;
smooth->mdoldframe = m->cframe;
//OSD_Printf("not smoothing... cframe %i nframe %i\n", m->cframe, m->nframe);
}
m->interpol = ((float)(i&65535))/65536.f;
//OSD_Printf("interpol %f\n", m->interpol);
prep_return:
if (m->cframe >= m->numframes)
m->cframe = 0;
if (m->nframe >= m->numframes)
m->nframe = 0;
}
#ifdef USE_GLEXT
// VBO generation and allocation
static void mdloadvbos(md3model_t *m)
{
int32_t i;
m->vbos = (GLuint *)Xmalloc(m->head.numsurfs * sizeof(GLuint));
bglGenBuffersARB(m->head.numsurfs, m->vbos);
i = 0;
while (i < m->head.numsurfs)
{
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, m->vbos[i]);
bglBufferDataARB(GL_ARRAY_BUFFER_ARB, m->head.surfs[i].numverts * sizeof(md3uv_t), m->head.surfs[i].uv, GL_STATIC_DRAW_ARB);
i++;
}
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}
#endif
//--------------------------------------- MD2 LIBRARY BEGINS ---------------------------------------
static md2model_t *md2load(int32_t fil, const char *filnam)
{
md2model_t *m;
md3model_t *m3;
md3surf_t *s;
md2frame_t *f;
md2head_t head;
char st[BMAX_PATH];
int32_t i, j, k;
int32_t ournumskins, ournumglcmds;
m = (md2model_t *)Xcalloc(1,sizeof(md2model_t));
m->mdnum = 2; m->scale = .01f;
kread(fil,(char *)&head,sizeof(md2head_t));
#if B_BIG_ENDIAN != 0
head.id = B_LITTLE32(head.id); head.vers = B_LITTLE32(head.vers);
head.skinxsiz = B_LITTLE32(head.skinxsiz); head.skinysiz = B_LITTLE32(head.skinysiz);
head.framebytes = B_LITTLE32(head.framebytes); head.numskins = B_LITTLE32(head.numskins);
head.numverts = B_LITTLE32(head.numverts); head.numuv = B_LITTLE32(head.numuv);
head.numtris = B_LITTLE32(head.numtris); head.numglcmds = B_LITTLE32(head.numglcmds);
head.numframes = B_LITTLE32(head.numframes); head.ofsskins = B_LITTLE32(head.ofsskins);
head.ofsuv = B_LITTLE32(head.ofsuv); head.ofstris = B_LITTLE32(head.ofstris);
head.ofsframes = B_LITTLE32(head.ofsframes); head.ofsglcmds = B_LITTLE32(head.ofsglcmds);
head.ofseof = B_LITTLE32(head.ofseof);
#endif
if ((head.id != IDP2_MAGIC) || (head.vers != 8)) { Bfree(m); return(0); } //"IDP2"
ournumskins = head.numskins ? head.numskins : 1;
ournumglcmds = head.numglcmds ? head.numglcmds : 1;
m->numskins = head.numskins;
m->numframes = head.numframes;
m->numverts = head.numverts;
m->numglcmds = head.numglcmds;
m->framebytes = head.framebytes;
m->frames = (char *)Xmalloc(m->numframes*m->framebytes);
m->glcmds = (int32_t *)Xmalloc(ournumglcmds*sizeof(int32_t));
m->tris = (md2tri_t *)Xmalloc(head.numtris*sizeof(md2tri_t));
m->uv = (md2uv_t *)Xmalloc(head.numuv*sizeof(md2uv_t));
klseek(fil,head.ofsframes,SEEK_SET);
if (kread(fil,(char *)m->frames,m->numframes*m->framebytes) != m->numframes*m->framebytes)
{ Bfree(m->uv); Bfree(m->tris); Bfree(m->glcmds); Bfree(m->frames); Bfree(m); return(0); }
if (m->numglcmds > 0)
{
klseek(fil,head.ofsglcmds,SEEK_SET);
if (kread(fil,(char *)m->glcmds,m->numglcmds*sizeof(int32_t)) != (int32_t)(m->numglcmds*sizeof(int32_t)))
{ Bfree(m->uv); Bfree(m->tris); Bfree(m->glcmds); Bfree(m->frames); Bfree(m); return(0); }
}
klseek(fil,head.ofstris,SEEK_SET);
if (kread(fil,(char *)m->tris,head.numtris*sizeof(md2tri_t)) != (int32_t)(head.numtris*sizeof(md2tri_t)))
{ Bfree(m->uv); Bfree(m->tris); Bfree(m->glcmds); Bfree(m->frames); Bfree(m); return(0); }
klseek(fil,head.ofsuv,SEEK_SET);
if (kread(fil,(char *)m->uv,head.numuv*sizeof(md2uv_t)) != (int32_t)(head.numuv*sizeof(md2uv_t)))
{ Bfree(m->uv); Bfree(m->tris); Bfree(m->glcmds); Bfree(m->frames); Bfree(m); return(0); }
#if B_BIG_ENDIAN != 0
{
char *f = (char *)m->frames;
int32_t *l,j;
md2frame_t *fr;
for (i = m->numframes-1; i>=0; i--)
{
fr = (md2frame_t *)f;
l = (int32_t *)&fr->mul;
for (j=5; j>=0; j--) l[j] = B_LITTLE32(l[j]);
f += m->framebytes;
}
for (i = m->numglcmds-1; i>=0; i--)
{
m->glcmds[i] = B_LITTLE32(m->glcmds[i]);
}
for (i = head.numtris-1; i>=0; i--)
{
m->tris[i].v[0] = B_LITTLE16(m->tris[i].v[0]);
m->tris[i].v[1] = B_LITTLE16(m->tris[i].v[1]);
m->tris[i].v[2] = B_LITTLE16(m->tris[i].v[2]);
m->tris[i].u[0] = B_LITTLE16(m->tris[i].u[0]);
m->tris[i].u[1] = B_LITTLE16(m->tris[i].u[1]);
m->tris[i].u[2] = B_LITTLE16(m->tris[i].u[2]);
}
for (i = head.numuv-1; i>=0; i--)
{
m->uv[i].u = B_LITTLE16(m->uv[i].u);
m->uv[i].v = B_LITTLE16(m->uv[i].v);
}
}
#endif
Bstrcpy(st,filnam);
for (i=strlen(st)-1; i>0; i--)
if ((st[i] == '/') || (st[i] == '\\')) { i++; break; }
if (i<0) i=0;
st[i] = 0;
m->basepath = (char *)Xmalloc(i+1);
Bstrcpy(m->basepath, st);
m->skinfn = (char *)Xmalloc(ournumskins*64);
if (m->numskins > 0)
{
klseek(fil,head.ofsskins,SEEK_SET);
if (kread(fil,m->skinfn,64*m->numskins) != 64*m->numskins)
{ Bfree(m->glcmds); Bfree(m->frames); Bfree(m); return(0); }
}
m->texid = (GLuint *)Xcalloc(ournumskins, sizeof(GLuint) * (HICEFFECTMASK+1));
maxmodelverts = max(maxmodelverts, m->numverts);
maxmodeltris = max(maxmodeltris, head.numtris);
//return(m);
// the MD2 is now loaded internally - let's begin the MD3 conversion process
//OSD_Printf("Beginning md3 conversion.\n");
m3 = (md3model_t *)Xcalloc(1, sizeof(md3model_t));
m3->mdnum = 3; m3->texid = 0; m3->scale = m->scale;
m3->head.id = IDP3_MAGIC; m3->head.vers = 15;
m3->head.flags = 0;
m3->head.numframes = m->numframes;
m3->head.numtags = 0; m3->head.numsurfs = 1;
m3->head.numskins = 0;
m3->numskins = m3->head.numskins;
m3->numframes = m3->head.numframes;
m3->head.frames = (md3frame_t *)Xcalloc(m3->head.numframes, sizeof(md3frame_t));
m3->muladdframes = (vec3f_t *)Xcalloc(m->numframes * 2, sizeof(vec3f_t));
f = (md2frame_t *)(m->frames);
// frames converting
i = 0;
while (i < m->numframes)
{
f = (md2frame_t *)&m->frames[i*m->framebytes];
Bstrcpy(m3->head.frames[i].nam, f->name);
//OSD_Printf("Copied frame %s.\n", m3->head.frames[i].nam);
m3->muladdframes[i*2] = f->mul;
m3->muladdframes[i*2+1] = f->add;
i++;
}
m3->head.tags = NULL;
m3->head.surfs = (md3surf_t *)Xcalloc(1, sizeof(md3surf_t));
s = m3->head.surfs;
// model converting
s->id = IDP3_MAGIC; s->flags = 0;
s->numframes = m->numframes; s->numshaders = 0;
s->numtris = head.numtris;
s->numverts = head.numtris * 3; // oh man talk about memory effectiveness :((((
// MD2 is actually more accurate than MD3 in term of uv-mapping, because each triangle has a triangle counterpart on the UV-map.
// In MD3, each vertex unique UV coordinates, meaning that you have to duplicate vertices if you need non-seamless UV-mapping.
maxmodelverts = max(maxmodelverts, s->numverts);
Bstrcpy(s->nam, "Dummy surface from MD2");
s->shaders = NULL;
s->tris = (md3tri_t *)Xcalloc(head.numtris, sizeof(md3tri_t));
s->uv = (md3uv_t *)Xcalloc(s->numverts, sizeof(md3uv_t));
s->xyzn = (md3xyzn_t *)Xcalloc(s->numverts * m->numframes, sizeof(md3xyzn_t));
//memoryusage += (s->numverts * m->numframes * sizeof(md3xyzn_t));
//OSD_Printf("Current model geometry memory usage : %i.\n", memoryusage);
//OSD_Printf("Number of frames : %i\n", m->numframes);
//OSD_Printf("Number of triangles : %i\n", head.numtris);
//OSD_Printf("Number of vertices : %i\n", s->numverts);
// triangle converting
i = 0;
while (i < head.numtris)
{
j = 0;
//OSD_Printf("Triangle : %i\n", i);
while (j < 3)
{
// triangle vertex indexes
s->tris[i].i[j] = i*3 + j;
// uv coords
s->uv[i*3+j].u = (float)(m->uv[m->tris[i].u[j]].u) / (float)(head.skinxsiz);
s->uv[i*3+j].v = (float)(m->uv[m->tris[i].u[j]].v) / (float)(head.skinysiz);
// vertices for each frame
k = 0;
while (k < m->numframes)
{
f = (md2frame_t *)&m->frames[k*m->framebytes];
s->xyzn[(k*s->numverts) + (i*3) + j].x = (int16_t) (((f->verts[m->tris[i].v[j]].v[0] * f->mul.x) + f->add.x) * 64.f);
s->xyzn[(k*s->numverts) + (i*3) + j].y = (int16_t) (((f->verts[m->tris[i].v[j]].v[1] * f->mul.y) + f->add.y) * 64.f);
s->xyzn[(k*s->numverts) + (i*3) + j].z = (int16_t) (((f->verts[m->tris[i].v[j]].v[2] * f->mul.z) + f->add.z) * 64.f);
k++;
}
j++;
}
//OSD_Printf("End triangle.\n");
i++;
}
//OSD_Printf("Finished md3 conversion.\n");
{
mdskinmap_t *sk;
sk = (mdskinmap_t *)Xcalloc(1,sizeof(mdskinmap_t));
sk->palette = 0;
sk->skinnum = 0;
sk->surfnum = 0;
if (m->numskins > 0)
{
sk->fn = (char *)Xmalloc(strlen(m->basepath)+strlen(m->skinfn)+1);
Bstrcpy(sk->fn, m->basepath);
Bstrcat(sk->fn, m->skinfn);
}
m3->skinmap = sk;
}
m3->indexes = (uint16_t *)Xmalloc(sizeof(uint16_t) * s->numtris);
m3->vindexes = (uint16_t *)Xmalloc(sizeof(uint16_t) * s->numtris * 3);
m3->maxdepths = (float *)Xmalloc(sizeof(float) * s->numtris);
m3->vbos = NULL;
// die MD2 ! DIE !
Bfree(m->texid); Bfree(m->skinfn); Bfree(m->basepath); Bfree(m->uv); Bfree(m->tris); Bfree(m->glcmds); Bfree(m->frames); Bfree(m);
return((md2model_t *)m3);
}
//---------------------------------------- MD2 LIBRARY ENDS ----------------------------------------
// DICHOTOMIC RECURSIVE SORTING - USED BY MD3DRAW
int32_t partition(uint16_t *indexes, float *depths, int32_t f, int32_t l)
{
int32_t up = f, down = l;
float piv = depths[f];
uint16_t piv2 = indexes[f];
do
{
while ((depths[up] <= piv) && (up < l))
up++;
while ((depths[down] > piv) && (down > f))
down--;
if (up < down)
{
swapfloat(&depths[up], &depths[down]);
swapshort(&indexes[up], &indexes[down]);
}
}
while (down > up);
depths[f] = depths[down], depths[down] = piv;
indexes[f] = indexes[down], indexes[down] = piv2;
return down;
}
static inline void quicksort(uint16_t *indexes, float *depths, int32_t first, int32_t last)
{
int32_t pivIndex;
if (first >= last) return;
pivIndex = partition(indexes, depths, first, last);
if (first < (pivIndex-1)) quicksort(indexes, depths, first, (pivIndex-1));
if ((pivIndex+1) >= last) return;
quicksort(indexes, depths, (pivIndex+1), last);
}
// END OF QUICKSORT LIB
//--------------------------------------- MD3 LIBRARY BEGINS ---------------------------------------
static md3model_t *md3load(int32_t fil)
{
int32_t i, surfi, ofsurf, offs[4], leng[4];
int32_t maxtrispersurf;
md3model_t *m;
md3surf_t *s;
m = (md3model_t *)Xcalloc(1,sizeof(md3model_t));
m->mdnum = 3; m->texid = 0; m->scale = .01f;
m->muladdframes = NULL;
kread(fil,&m->head,SIZEOF_MD3HEAD_T);
#if B_BIG_ENDIAN != 0
m->head.id = B_LITTLE32(m->head.id); m->head.vers = B_LITTLE32(m->head.vers);
m->head.flags = B_LITTLE32(m->head.flags); m->head.numframes = B_LITTLE32(m->head.numframes);
m->head.numtags = B_LITTLE32(m->head.numtags); m->head.numsurfs = B_LITTLE32(m->head.numsurfs);
m->head.numskins = B_LITTLE32(m->head.numskins); m->head.ofsframes = B_LITTLE32(m->head.ofsframes);
m->head.ofstags = B_LITTLE32(m->head.ofstags); m->head.ofssurfs = B_LITTLE32(m->head.ofssurfs);
m->head.eof = B_LITTLE32(m->head.eof);
#endif
if ((m->head.id != IDP3_MAGIC) && (m->head.vers != 15)) { Bfree(m); return(0); } //"IDP3"
m->numskins = m->head.numskins; //<- dead code?
m->numframes = m->head.numframes;
ofsurf = m->head.ofssurfs;
klseek(fil,m->head.ofsframes,SEEK_SET); i = m->head.numframes*sizeof(md3frame_t);
m->head.frames = (md3frame_t *)Xmalloc(i);
kread(fil,m->head.frames,i);
if (m->head.numtags == 0) m->head.tags = NULL;
else
{
klseek(fil,m->head.ofstags,SEEK_SET); i = m->head.numtags*sizeof(md3tag_t);
m->head.tags = (md3tag_t *)Xmalloc(i);
kread(fil,m->head.tags,i);
}
klseek(fil,m->head.ofssurfs,SEEK_SET);
m->head.surfs = (md3surf_t *)Xcalloc(m->head.numsurfs, sizeof(md3surf_t));
// NOTE: We assume that NULL is represented by all-zeros.
// surfs[0].geometry is for POLYMER_MD_PROCESS_CHECK (else: crashes).
// surfs[i].geometry is for FREE_SURFS_GEOMETRY.
Bassert(m->head.surfs[0].geometry == NULL);
#if B_BIG_ENDIAN != 0
{
int32_t j, *l;
for (i = m->head.numframes-1; i>=0; i--)
{
l = (int32_t *)&m->head.frames[i].min;
for (j=3+3+3+1-1; j>=0; j--) l[j] = B_LITTLE32(l[j]);
}
for (i = m->head.numtags-1; i>=0; i--)
{
l = (int32_t *)&m->head.tags[i].p;
for (j=3+3+3+3-1; j>=0; j--) l[j] = B_LITTLE32(l[j]);
}
}
#endif
maxtrispersurf = 0;
for (surfi=0; surfi<m->head.numsurfs; surfi++)
{
s = &m->head.surfs[surfi];
klseek(fil,ofsurf,SEEK_SET); kread(fil,s,SIZEOF_MD3SURF_T);
#if B_BIG_ENDIAN != 0
{
int32_t j, *l;
s->id = B_LITTLE32(s->id);
l = (int32_t *)&s->flags;
for (j=1+1+1+1+1+1+1+1+1+1-1; j>=0; j--) l[j] = B_LITTLE32(l[j]);
}
#endif
offs[0] = ofsurf+s->ofstris;
offs[1] = ofsurf+s->ofsshaders;
offs[2] = ofsurf+s->ofsuv;
offs[3] = ofsurf+s->ofsxyzn;
leng[0] = s->numtris*sizeof(md3tri_t);
leng[1] = s->numshaders*sizeof(md3shader_t);
leng[2] = s->numverts*sizeof(md3uv_t);
leng[3] = s->numframes*s->numverts*sizeof(md3xyzn_t);
//memoryusage += (s->numverts * s->numframes * sizeof(md3xyzn_t));
//OSD_Printf("Current model geometry memory usage : %i.\n", memoryusage);
s->tris = (md3tri_t *)Xmalloc((leng[0] + leng[1]) + (leng[2] + leng[3]));
s->shaders = (md3shader_t *)(((intptr_t)s->tris)+leng[0]);
s->uv = (md3uv_t *)(((intptr_t)s->shaders)+leng[1]);
s->xyzn = (md3xyzn_t *)(((intptr_t)s->uv)+leng[2]);
klseek(fil,offs[0],SEEK_SET); kread(fil,s->tris ,leng[0]);
klseek(fil,offs[1],SEEK_SET); kread(fil,s->shaders,leng[1]);
klseek(fil,offs[2],SEEK_SET); kread(fil,s->uv ,leng[2]);
klseek(fil,offs[3],SEEK_SET); kread(fil,s->xyzn ,leng[3]);
#if B_BIG_ENDIAN != 0
{
int32_t j, *l;
for (i=s->numtris-1; i>=0; i--)
{
for (j=2; j>=0; j--) s->tris[i].i[j] = B_LITTLE32(s->tris[i].i[j]);
}
for (i=s->numshaders-1; i>=0; i--)
{
s->shaders[i].i = B_LITTLE32(s->shaders[i].i);
}
for (i=s->numverts-1; i>=0; i--)
{
l = (int32_t *)&s->uv[i].u;
l[0] = B_LITTLE32(l[0]);
l[1] = B_LITTLE32(l[1]);
}
for (i=s->numframes*s->numverts-1; i>=0; i--)
{
s->xyzn[i].x = (int16_t)B_LITTLE16((uint16_t)s->xyzn[i].x);
s->xyzn[i].y = (int16_t)B_LITTLE16((uint16_t)s->xyzn[i].y);
s->xyzn[i].z = (int16_t)B_LITTLE16((uint16_t)s->xyzn[i].z);
}
}
#endif
maxmodelverts = max(maxmodelverts, s->numverts);
maxmodeltris = max(maxmodeltris, s->numtris);
maxtrispersurf = max(maxtrispersurf, s->numtris);
ofsurf += s->ofsend;
}
m->indexes = (uint16_t *)Xmalloc(sizeof(uint16_t) * maxtrispersurf);
m->vindexes = (uint16_t *)Xmalloc(sizeof(uint16_t) * maxtrispersurf * 3);
m->maxdepths = (float *)Xmalloc(sizeof(float) * maxtrispersurf);
m->vbos = NULL;
return(m);
}
#ifdef POLYMER
static inline void invertmatrix(float *m, float *out)
{
float det;
det = (m[0] * (m[4]*m[8] - m[5] * m[7]))
- (m[1] * (m[3]*m[8] - m[5] * m[6]))
+ (m[2] * (m[3]*m[7] - m[4] * m[6]));
if (det == 0.0f)
{
Bmemset(out, 0, sizeof(float) * 9);
out[8] = out[4] = out[0] = 1.f;
return;
}
det = 1.0f / det;
out[0] = det * (m[4] * m[8] - m[5] * m[7]);
out[1] = det * (m[2] * m[7] - m[1] * m[8]);
out[2] = det * (m[1] * m[5] - m[2] * m[4]);
out[3] = det * (m[5] * m[6] - m[3] * m[8]);
out[4] = det * (m[0] * m[8] - m[2] * m[6]);
out[5] = det * (m[2] * m[3] - m[0] * m[5]);
out[6] = det * (m[3] * m[7] - m[1] * m[6]);
out[7] = det * (m[1] * m[6] - m[0] * m[7]);
out[8] = det * (m[0] * m[4] - m[1] * m[3]);
}
static inline void normalize(float *vec)
{
float norm;
if ((norm = vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]) == 0.f)
return;
norm = polymost_invsqrt_approximation(norm);
vec[0] *= norm;
vec[1] *= norm;
vec[2] *= norm;
}
#endif
static void md3postload_common(md3model_t *m)
{
int framei, surfi, verti;
md3frame_t *frame;
md3xyzn_t *frameverts;
// apparently we can't trust loaded models bounding box/sphere information,
// so let's compute it ourselves
framei = 0;
do // while (++framei < m->head.numframes);
{
frame = &m->head.frames[framei];
Bmemset(&frame->min, 0, sizeof(vec3f_t) * 2);
frame->r = 0.0f;
surfi = 0;
do // while (++surfi < m->head.numsurfs);
{
frameverts = &m->head.surfs[surfi].xyzn[framei * m->head.surfs[surfi].numverts];
verti = 0;
if (!surfi)
{
frame->min.x = (float)frameverts[0].x;
frame->min.y = (float)frameverts[0].y;
frame->min.z = (float)frameverts[0].z;
frame->max = frame->min;
}
do // while(++verti < m->head.surfs[surfi].numverts);
{
vec3f_t f = { (float)frameverts[verti].x, (float)frameverts[verti].y, (float)frameverts[verti].z };
if (!verti && !surfi)
continue;
frame->min.x = max(frame->min.x, f.x);
frame->min.y = max(frame->min.y, f.y);
frame->min.z = max(frame->min.z, f.z);
frame->max.x = max(frame->max.x, f.x);
frame->max.y = max(frame->max.y, f.y);
frame->max.z = max(frame->max.z, f.z);
}
while(++verti < m->head.surfs[surfi].numverts);
}
while (++surfi < m->head.numsurfs);
frame->cen.x = (frame->min.x + frame->max.x) * .5f;
frame->cen.y = (frame->min.y + frame->max.y) * .5f;
frame->cen.z = (frame->min.z + frame->max.z) * .5f;
surfi = 0;
do // while (++surfi < m->head.numsurfs);
{
float vec1[4];
frameverts = &m->head.surfs[surfi].xyzn[framei * m->head.surfs[surfi].numverts];
verti = 0;
do // while (++verti < m->head.surfs[surfi].numverts);
{
vec1[0] = frameverts[verti].x - frame->cen.x;
vec1[1] = frameverts[verti].y - frame->cen.y;
vec1[2] = frameverts[verti].z - frame->cen.z;
vec1[3] = (vec1[0] * vec1[0]) + (vec1[1] * vec1[1]) + (vec1[2] * vec1[2]);
frame->r = max(vec1[3], frame->r);
}
while (++verti < m->head.surfs[surfi].numverts);
}
while (++surfi < m->head.numsurfs);
frame->r = Bsqrtf(frame->r);
}
while (++framei < m->head.numframes);
}
#ifdef POLYMER
// pre-check success of conversion since it must not fail later.
// keep in sync with md3postload_polymer!
static int md3postload_polymer_check(md3model_t *m)
{
uint32_t surfi, trii;
md3surf_t *s;
surfi = 0;
do
{
s = &m->head.surfs[surfi];
trii = 0;
do
{
// let the vertices know they're being referenced by a triangle
if ((unsigned)s->tris[trii].i[0] >= (unsigned)s->numverts ||
(unsigned)s->tris[trii].i[1] >= (unsigned)s->numverts ||
(unsigned)s->tris[trii].i[2] >= (unsigned)s->numverts)
{
// corrupt model
OSD_Printf("%s: Triangle index out of bounds!\n", m->head.nam);
return 1;
}
}
while (++trii < (unsigned)s->numtris);
}
while (++surfi < (unsigned)m->head.numsurfs);
return 0;
}
// Precalculated cos/sin arrays.
static float g_mdcos[256], g_mdsin[256];
static int32_t mdtrig_init = 0;
static void init_mdtrig_arrays(void)
{
int32_t i;
static const float acc = ((2.f * (float)PI) * (1.f/255.f));
float ang = 0.f;
for (i=0; i<256; i++)
{
g_mdcos[i] = cosf(ang);
g_mdsin[i] = sinf(ang);
ang += acc;
}
mdtrig_init = 1;
}
#endif
int md3postload_polymer(md3model_t *m)
{
#ifdef POLYMER
int framei, surfi, verti, trii, i;
float vec1[5], vec2[5], mat[9], r;
// POLYMER_MD_PROCESS_CHECK
if (m->head.surfs[0].geometry)
return -1; // already postprocessed
if (!mdtrig_init)
init_mdtrig_arrays();
// let's also repack the geometry to more usable formats
surfi = 0;
do // while (++surfi < m->head.numsurfs)
{
handleevents();
md3surf_t *const s = &m->head.surfs[surfi];
#ifdef DEBUG_MODEL_MEM
i = (m->head.numframes * s->numverts * sizeof(float) * 15);
if (i > 1<<20)
initprintf("size %d (%d fr, %d v): md %s surf %d/%d\n", i, m->head.numframes, s->numverts,
m->head.nam, surfi, m->head.numsurfs);
#endif
s->geometry = (float *)Xcalloc(m->head.numframes * s->numverts * 15, sizeof(float));
if (s->numverts > tribufverts)
{
tribuf = (int32_t *) Xrealloc(tribuf, s->numverts * sizeof(int32_t));
Bmemset(tribuf, 0, s->numverts * sizeof(int32_t));
tribufverts = s->numverts;
}
verti = 0;
do // while (++verti < (m->head.numframes * s->numverts))
{
// normal extraction from packed spherical coordinates
// FIXME: swapping lat and lng because of npherno's compiler
uint8_t lat = s->xyzn[verti].nlng;
uint8_t lng = s->xyzn[verti].nlat;
uint32_t verti15 = (verti<<4)-verti;
s->geometry[verti15 + 0] = s->xyzn[verti].x;
s->geometry[verti15 + 1] = s->xyzn[verti].y;
s->geometry[verti15 + 2] = s->xyzn[verti].z;
s->geometry[verti15 + 3] = g_mdcos[lat] * g_mdsin[lng];
s->geometry[verti15 + 4] = g_mdsin[lat] * g_mdsin[lng];
s->geometry[verti15 + 5] = g_mdcos[lng];
}
while (++verti < (m->head.numframes * s->numverts));
trii = 0;
do // while (++trii < s->numtris)
{
uint32_t tris15[3];
// let the vertices know they're being referenced by a triangle
if ((unsigned) s->tris[trii].i[0] >= (unsigned) s->numverts ||
(unsigned) s->tris[trii].i[1] >= (unsigned) s->numverts ||
(unsigned) s->tris[trii].i[2] >= (unsigned) s->numverts)
{
// corrupt model
return 0;
}
tribuf[s->tris[trii].i[0]]++;
tribuf[s->tris[trii].i[1]]++;
tribuf[s->tris[trii].i[2]]++;
tris15[0] = (s->tris[trii].i[0]<<4)-s->tris[trii].i[0];
tris15[1] = (s->tris[trii].i[1]<<4)-s->tris[trii].i[1];
tris15[2] = (s->tris[trii].i[2]<<4)-s->tris[trii].i[2];
framei = 0;
do // while (++framei < m->head.numframes)
{
const uint32_t verti15 = framei * s->numverts * 15;
vec1[0] = s->geometry[verti15 + tris15[1]] - s->geometry[verti15 + tris15[0]];
vec1[1] = s->geometry[verti15 + tris15[1] + 1] - s->geometry[verti15 + tris15[0] + 1];
vec1[2] = s->geometry[verti15 + tris15[1] + 2] - s->geometry[verti15 + tris15[0] + 2];
vec1[3] = s->uv[s->tris[trii].i[1]].u - s->uv[s->tris[trii].i[0]].u;
vec1[4] = s->uv[s->tris[trii].i[1]].v - s->uv[s->tris[trii].i[0]].v;
vec2[0] = s->geometry[verti15 + tris15[2] ] - s->geometry[verti15 + tris15[1]];
vec2[1] = s->geometry[verti15 + tris15[2] + 1] - s->geometry[verti15 + tris15[1] + 1];
vec2[2] = s->geometry[verti15 + tris15[2] + 2] - s->geometry[verti15 + tris15[1] + 2];
vec2[3] = s->uv[s->tris[trii].i[2]].u - s->uv[s->tris[trii].i[1]].u;
vec2[4] = s->uv[s->tris[trii].i[2]].v - s->uv[s->tris[trii].i[1]].v;
r = (vec1[3] * vec2[4] - vec2[3] * vec1[4]);
if (r != 0.0f)
{
r = 1.f/r;
// tangent
mat[0] = (vec2[4] * vec1[0] - vec1[4] * vec2[0]) * r;
mat[1] = (vec2[4] * vec1[1] - vec1[4] * vec2[1]) * r;
mat[2] = (vec2[4] * vec1[2] - vec1[4] * vec2[2]) * r;
normalize(&mat[0]);
// bitangent
mat[3] = (vec1[3] * vec2[0] - vec2[3] * vec1[0]) * r;
mat[4] = (vec1[3] * vec2[1] - vec2[3] * vec1[1]) * r;
mat[5] = (vec1[3] * vec2[2] - vec2[3] * vec1[2]) * r;
normalize(&mat[3]);
}
else Bmemset(mat, 0, sizeof(float) * 6);
// T and B are shared for the three vertices in that triangle
i = 0;
do
{
const uint32_t offs = (framei * s->numverts * 15) + i + 6;
s->geometry[offs + tris15[0]] += mat[i];
s->geometry[offs + tris15[1]] += mat[i];
s->geometry[offs + tris15[2]] += mat[i];
}
while (++i < 6);
}
while (++framei < m->head.numframes);
}
while (++trii < s->numtris);
// now that we accumulated the TBNs, average and invert them for each vertex
verti = (m->head.numframes * s->numverts)-1;
do // while (verti-- > 0)
{
const int32_t curnumtris = tribuf[verti % s->numverts];
const uint32_t verti15 = (verti<<4) - verti;
if (curnumtris > 0)
{
const float rfcurnumtris = 1.f/(float)curnumtris;
i = 6;
do {
s->geometry[verti15 + i] *= rfcurnumtris;
} while (++i < 12);
}
#ifdef DEBUG_MODEL_MEM
else if (verti == verti%s->numverts)
{
OSD_Printf("%s: vert %d is unused\n", m->head.nam, verti);
}
#endif
// copy N over
Bmemcpy(&s->geometry[verti15 + 12], &s->geometry[verti15 + 3], sizeof(float) * 3);
invertmatrix(&s->geometry[verti15 + 6], mat);
Bmemcpy(&s->geometry[verti15 + 6], mat, sizeof(float) * 9);
}
while (verti-- > 0);
}
while (++surfi < m->head.numsurfs);
#else
UNREFERENCED_PARAMETER(m);
#endif
return 1;
}
void md3_vox_calcmat_common(const tspritetype *tspr, const vec3f_t *a0, float f, float mat[16])
{
float g;
float k0, k1, k2, k3, k4, k5, k6, k7;
k0 = ((float)(tspr->x-globalposx))*f*(1.f/1024.f);
k1 = ((float)(tspr->y-globalposy))*f*(1.f/1024.f);
f = gcosang2*gshang;
g = gsinang2*gshang;
k4 = (float)sintable[(tspr->ang+spriteext[tspr->owner].angoff+1024)&2047] * (1.f/16384.f);
k5 = (float)sintable[(tspr->ang+spriteext[tspr->owner].angoff+ 512)&2047] * (1.f/16384.f);
k2 = k0*(1-k4)+k1*k5;
k3 = k1*(1-k4)-k0*k5;
k6 = f*gstang - gsinang*gctang; k7 = g*gstang + gcosang*gctang;
mat[0] = k4*k6 + k5*k7; mat[4] = gchang*gstang; mat[ 8] = k4*k7 - k5*k6; mat[12] = k2*k6 + k3*k7;
k6 = f*gctang + gsinang*gstang; k7 = g*gctang - gcosang*gstang;
mat[1] = k4*k6 + k5*k7; mat[5] = gchang*gctang; mat[ 9] = k4*k7 - k5*k6; mat[13] = k2*k6 + k3*k7;
k6 = gcosang2*gchang; k7 = gsinang2*gchang;
mat[2] = k4*k6 + k5*k7; mat[6] =-gshang; mat[10] = k4*k7 - k5*k6; mat[14] = k2*k6 + k3*k7;
mat[12] = (mat[12] + a0->y*mat[0]) + (a0->z*mat[4] + a0->x*mat[ 8]);
mat[13] = (mat[13] + a0->y*mat[1]) + (a0->z*mat[5] + a0->x*mat[ 9]);
mat[14] = (mat[14] + a0->y*mat[2]) + (a0->z*mat[6] + a0->x*mat[10]);
}
static void md3draw_handle_triangles(const md3surf_t *s, uint16_t *indexhandle,
int32_t texunits, const md3model_t *M)
{
int32_t i;
if (r_vertexarrays)
{
int32_t k = 0;
if (M == NULL)
{
for (i=s->numtris-1; i>=0; i--, k+=3)
{
indexhandle[k] = s->tris[i].i[0];
indexhandle[k+1] = s->tris[i].i[1];
indexhandle[k+2] = s->tris[i].i[2];
}
return;
}
for (i=s->numtris-1; i>=0; i--, k+=3)
{
uint16_t tri = M->indexes[i];
indexhandle[k] = s->tris[tri].i[0];
indexhandle[k+1] = s->tris[tri].i[1];
indexhandle[k+2] = s->tris[tri].i[2];
}
return;
}
bglBegin(GL_TRIANGLES);
for (i=s->numtris-1; i>=0; i--)
{
uint16_t tri = M ? M->indexes[i] : i;
int32_t j;
for (j=0; j<3; j++)
{
int32_t k = s->tris[tri].i[j];
#ifdef USE_GLEXT
if (texunits > GL_TEXTURE0_ARB)
{
int32_t l = GL_TEXTURE0_ARB;
while (l <= texunits)
bglMultiTexCoord2fARB(l++, s->uv[k].u, s->uv[k].v);
}
else
#endif
bglTexCoord2f(s->uv[k].u, s->uv[k].v);
bglVertex3fv((float *) &vertlist[k]);
}
}
bglEnd();
#ifndef USE_GLEXT
UNREFERENCED_PARAMETER(texunits);
#endif
}
static int32_t polymost_md3draw(md3model_t *m, const tspritetype *tspr)
{
vec3f_t m0, m1, a0;
md3xyzn_t *v0, *v1;
int32_t i, surfi;
float f, g, k0, k1, k2=0, k3=0, mat[16]; // inits: compiler-happy
GLfloat pc[4];
int32_t texunits = GL_TEXTURE0_ARB;
const int32_t owner = tspr->owner;
// PK: XXX: These owner bound checks are redundant because sext is
// dereferenced unconditionally below anyway.
const spriteext_t *const sext = ((unsigned)owner < MAXSPRITES+MAXUNIQHUDID) ? &spriteext[owner] : NULL;
const uint8_t lpal = ((unsigned)owner < MAXSPRITES) ? sprite[tspr->owner].pal : tspr->pal;
const int32_t sizyrep = tilesiz[tspr->picnum].y*tspr->yrepeat;
#ifdef USE_GLEXT
if (r_vbos && (m->vbos == NULL))
mdloadvbos(m);
#endif
// if ((tspr->cstat&48) == 32) return 0;
updateanimation((md2model_t *)m, tspr, lpal);
//create current&next frame's vertex list from whole list
f = m->interpol; g = 1.f - f;
if (m->interpol < 0.f || m->interpol > 1.f ||
(unsigned)m->cframe >= (unsigned)m->numframes ||
(unsigned)m->nframe >= (unsigned)m->numframes)
{
#ifdef DEBUGGINGAIDS
OSD_Printf("%s: mdframe oob: c:%d n:%d total:%d interpol:%.02f\n",
m->head.nam, m->cframe, m->nframe, m->numframes, m->interpol);
#endif
m->interpol = fclamp(m->interpol, 0.f, 1.f);
m->cframe = clamp(m->cframe, 0, m->numframes-1);
m->nframe = clamp(m->nframe, 0, m->numframes-1);
}
m0.z = m0.y = m0.x = g = m->scale * g * (1.f/64.f);
m1.z = m1.y = m1.x = f = m->scale * f * (1.f/64.f);
a0.x = a0.y = 0;
a0.z = m->zadd * m->scale;
// Parkar: Moved up to be able to use k0 for the y-flipping code
k0 = (float)tspr->z;
if ((globalorientation&128) && !((globalorientation&48)==32))
k0 += (float)(sizyrep<<1);
// Parkar: Changed to use the same method as centeroriented sprites
if (globalorientation&8) //y-flipping
{
m0.z = -m0.z; m1.z = -m1.z; a0.z = -a0.z;
k0 -= (float)(sizyrep<<2);
}
if (globalorientation&4) { m0.y = -m0.y; m1.y = -m1.y; a0.y = -a0.y; } //x-flipping
// yoffset differs from zadd in that it does not follow cstat&8 y-flipping
a0.z += m->yoffset*m->scale;
f = ((float)tspr->xrepeat) * (1.f/64.f) * m->bscale;
m0.x *= f; m0.y *= -f;
m1.x *= f; m1.y *= -f;
a0.x *= f; a0.y *= -f;
f = ((float)tspr->yrepeat) * (1.f/64.f) * m->bscale;
m0.z *= f; m1.z *= f; a0.z *= f;
// floor aligned
k1 = (float)tspr->y;
if ((globalorientation&48)==32)
{
m0.z = -m0.z; m1.z = -m1.z; a0.z = -a0.z;
m0.y = -m0.y; m1.y = -m1.y; a0.y = -a0.y;
f = a0.x; a0.x = a0.z; a0.z = f;
k1 += (float)(sizyrep>>3);
}
// Note: These SCREEN_FACTORS will be neutralized in axes offset
// calculations below again, but are needed for the base offsets.
f = (65536.f*512.f)/(fxdimen*fviewingrange);
g = 32.f/(fxdimen*gxyaspect);
m0.y *= f; m1.y *= f; a0.y = (((float)(tspr->x-globalposx))* (1.f/1024.f) + a0.y)*f;
m0.x *=-f; m1.x *=-f; a0.x = ((k1 -fglobalposy) * -(1.f/1024.f) + a0.x)*-f;
m0.z *= g; m1.z *= g; a0.z = ((k0 -fglobalposz) * -(1.f/16384.f) + a0.z)*g;
md3_vox_calcmat_common(tspr, &a0, f, mat);
// floor aligned
if ((globalorientation&48)==32)
{
f = mat[4]; mat[4] = mat[8]*16.f; mat[8] = -f*(1.f/16.f);
f = mat[5]; mat[5] = mat[9]*16.f; mat[9] = -f*(1.f/16.f);
f = mat[6]; mat[6] = mat[10]*16.f; mat[10] = -f*(1.f/16.f);
}
//Mirrors
if (grhalfxdown10x < 0) { mat[0] = -mat[0]; mat[4] = -mat[4]; mat[8] = -mat[8]; mat[12] = -mat[12]; }
//------------
// TSPR_EXTRA_MDHACK is an ugly hack in game.c:G_DoSpriteAnimations() telling md2sprite
// to use Z-buffer hacks to hide overdraw problems with the flat-tsprite-on-floor shadows,
// also disabling detail, glow, normal, and specular maps.
if (tspr->extra&TSPR_EXTRA_MDHACK)
{
#ifdef __arm__ // GL ES has a glDepthRangef and the loss of precision is OK there
float f = (float) (tspr->owner + 1) * (FLT_EPSILON * 8.0);
if (f != 0.0) f *= 1.f/(float) (sepldist(globalposx - tspr->x, globalposy - tspr->y)>>5);
#else
double f = (double) (tspr->owner + 1) * (FLT_EPSILON * 8.0);
if (f != 0.0) f *= 1.0/(double) (sepldist(globalposx - tspr->x, globalposy - tspr->y)>>5);
// bglBlendFunc(GL_SRC_ALPHA, GL_DST_COLOR);
#endif
bglDepthFunc(GL_LEQUAL);
// bglDepthRange(0.0 - f, 1.0 - f);
}
// bglPushAttrib(GL_POLYGON_BIT);
if ((grhalfxdown10x >= 0) ^((globalorientation&8) != 0) ^((globalorientation&4) != 0)) bglFrontFace(GL_CW); else bglFrontFace(GL_CCW);
bglEnable(GL_CULL_FACE);
bglCullFace(GL_BACK);
bglEnable(GL_TEXTURE_2D);
// tinting
pc[0] = pc[1] = pc[2] = ((float)(numshades-min(max((globalshade * shadescale)+m->shadeoff,0),numshades)))/((float)numshades);
if (!(hictinting[globalpal].f & HICTINT_PRECOMPUTED))
{
if (!(m->flags&1))
hictinting_apply(pc, globalpal);
else globalnoeffect=1;
}
// global tinting
if (have_basepal_tint())
hictinting_apply(pc, MAXPALOOKUPS-1);
if (tspr->cstat&2) { if (!(tspr->cstat&512)) pc[3] = 0.66f; else pc[3] = 0.33f; }
else pc[3] = 1.0f;
pc[3] *= 1.0f - sext->alpha;
if (m->usesalpha) //Sprites with alpha in texture
{
// bglEnable(GL_BLEND);// bglBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
// bglEnable(GL_ALPHA_TEST); bglAlphaFunc(GL_GREATER,0.32);
// float al = 0.32;
// PLAG : default cutoff removed
float al = 0.0;
if (alphahackarray[globalpicnum] != 0)
al=alphahackarray[globalpicnum] * (1.f/255.f);
bglEnable(GL_BLEND);
bglEnable(GL_ALPHA_TEST);
bglAlphaFunc(GL_GREATER,al);
}
else
{
if ((tspr->cstat&2) || sext->alpha > 0.f || pc[3] < 1.0f) bglEnable(GL_BLEND); //else bglDisable(GL_BLEND);
}
bglColor4f(pc[0],pc[1],pc[2],pc[3]);
//if (MFLAGS_NOCONV(m))
// bglColor4f(0.0f, 0.0f, 1.0f, 1.0f);
//------------
// PLAG: Cleaner model rotation code
if (sext->pitch || sext->roll)
{
float f = 1.f/(fxdimen * fviewingrange) * (m0.x+m1.x) * (2560.f * (1.f/(65536.f*1280.f)));
Bmemset(&a0, 0, sizeof(a0));
if (sext->offset.x)
a0.x = (float) sext->offset.x * f;
if (sext->offset.y) // Compare with SCREEN_FACTORS above
a0.y = (float) sext->offset.y * f;
if ((sext->offset.z) && !(tspr->extra&TSPR_EXTRA_MDHACK)) // Compare with SCREEN_FACTORS above
a0.z = (float)sext->offset.z / (655360.f * (m0.z+m1.z) * (gxyaspect*fxdimen*(1.f/1280.f)));
k0 = (float)sintable[(sext->pitch+512)&2047] * (1.f/16384.f);
k1 = (float)sintable[sext->pitch&2047] * (1.f/16384.f);
k2 = (float)sintable[(sext->roll+512)&2047] * (1.f/16384.f);
k3 = (float)sintable[sext->roll&2047] * (1.f/16384.f);
}
float const xpanning = (float)sext->xpanning * (1.f/256.f);
float const ypanning = (float)sext->ypanning * (1.f/256.f);
for (surfi=0; surfi<m->head.numsurfs; surfi++)
{
//PLAG : sorting stuff
#ifdef USE_GLEXT
void *vbotemp;
vec3f_t *vertexhandle = NULL;
#endif
uint16_t *indexhandle;
vec3f_t fp;
const md3surf_t *const s = &m->head.surfs[surfi];
v0 = &s->xyzn[m->cframe*s->numverts];
v1 = &s->xyzn[m->nframe*s->numverts];
#ifdef USE_GLEXT
if (r_vertexarrays && r_vbos)
{
if (++curvbo >= r_vbocount)
curvbo = 0;
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, vertvbos[curvbo]);
vbotemp = bglMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
vertexhandle = (vec3f_t *)vbotemp;
}
#endif
if (sext->pitch || sext->roll)
{
vec3f_t fp1, fp2;
for (i=s->numverts-1; i>=0; i--)
{
fp.z = v0[i].x + a0.x;
fp.x = v0[i].y + a0.y;
fp.y = v0[i].z + a0.z;
fp1.x = fp.x*k2 + fp.y*k3;
fp1.y = fp.x*k0*(-k3) + fp.y*k0*k2 + fp.z*(-k1);
fp1.z = fp.x*k1*(-k3) + fp.y*k1*k2 + fp.z*k0;
fp.z = v1[i].x + a0.x;
fp.x = v1[i].y + a0.y;
fp.y = v1[i].z + a0.z;
fp2.x = fp.x*k2 + fp.y*k3;
fp2.y = fp.x*k0*(-k3) + fp.y*k0*k2 + fp.z*(-k1);
fp2.z = fp.x*k1*(-k3) + fp.y*k1*k2 + fp.z*k0;
fp.z = (fp1.z - a0.x)*m0.x + (fp2.z - a0.x)*m1.x;
fp.x = (fp1.x - a0.y)*m0.y + (fp2.x - a0.y)*m1.y;
fp.y = (fp1.y - a0.z)*m0.z + (fp2.y - a0.z)*m1.z;
#ifdef USE_GLEXT
if (r_vertexarrays && r_vbos)
vertexhandle[i] = fp;
#endif
vertlist[i] = fp;
}
}
else
{
for (i=s->numverts-1; i>=0; i--)
{
fp.z = v0[i].x*m0.x + v1[i].x*m1.x;
fp.y = v0[i].z*m0.z + v1[i].z*m1.z;
fp.x = v0[i].y*m0.y + v1[i].y*m1.y;
#ifdef USE_GLEXT
if (r_vertexarrays && r_vbos)
vertexhandle[i] = fp;
#endif
vertlist[i] = fp;
}
}
#ifdef USE_GLEXT
if (r_vertexarrays && r_vbos)
{
bglUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}
#endif
bglMatrixMode(GL_MODELVIEW); //Let OpenGL (and perhaps hardware :) handle the matrix rotation
mat[3] = mat[7] = mat[11] = 0.f; mat[15] = 1.f; bglLoadMatrixf(mat);
// PLAG: End
i = mdloadskin((md2model_t *)m,tile2model[Ptile2tile(tspr->picnum,lpal)].skinnum,globalpal,surfi);
if (!i)
continue;
//i = mdloadskin((md2model *)m,tile2model[Ptile2tile(tspr->picnum,lpal)].skinnum,surfi); //hack for testing multiple surfaces per MD3
bglBindTexture(GL_TEXTURE_2D, i);
bglMatrixMode(GL_TEXTURE);
bglLoadIdentity();
bglTranslatef(xpanning, ypanning, 1.0f);
bglMatrixMode(GL_MODELVIEW);
if (!(tspr->extra&TSPR_EXTRA_MDHACK))
{
#ifdef USE_GLEXT
i = r_detailmapping ? mdloadskin((md2model_t *) m, tile2model[Ptile2tile(tspr->picnum, lpal)].skinnum, DETAILPAL, surfi) : 0;
if (i)
{
mdskinmap_t *sk;
polymost_setupdetailtexture(++texunits, i);
for (sk = m->skinmap; sk; sk = sk->next)
if ((int32_t) sk->palette == DETAILPAL && sk->skinnum == tile2model[Ptile2tile(tspr->picnum, lpal)].skinnum && sk->surfnum == surfi)
f = sk->param;
bglMatrixMode(GL_TEXTURE);
bglLoadIdentity();
bglTranslatef(xpanning, ypanning, 1.0f);
bglScalef(f, f, 1.0f);
bglMatrixMode(GL_MODELVIEW);
}
i = r_glowmapping ? mdloadskin((md2model_t *) m, tile2model[Ptile2tile(tspr->picnum, lpal)].skinnum, GLOWPAL, surfi) : 0;
if (i)
{
polymost_setupglowtexture(++texunits, i);
bglMatrixMode(GL_TEXTURE);
bglLoadIdentity();
bglTranslatef(xpanning, ypanning, 1.0f);
bglMatrixMode(GL_MODELVIEW);
}
if (r_vertexarrays && r_vbos)
{
bglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexvbos[curvbo]);
vbotemp = bglMapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
indexhandle = (uint16_t *) vbotemp;
}
else
#endif
indexhandle = m->vindexes;
//PLAG: delayed polygon-level sorted rendering
if (m->usesalpha)
{
vec3f_t fp, fp1, fp2, vlt0, vlt1, vlt2;
for (i=s->numtris-1; i>=0; i--)
{
vlt0 = vertlist[s->tris[i].i[0]];
vlt1 = vertlist[s->tris[i].i[1]];
vlt2 = vertlist[s->tris[i].i[2]];
// Matrix multiplication - ugly but clear
fp.x = (vlt0.x * mat[0]) + (vlt0.y * mat[4]) + (vlt0.z * mat[8]) + mat[12];
fp.y = (vlt0.x * mat[1]) + (vlt0.y * mat[5]) + (vlt0.z * mat[9]) + mat[13];
fp.z = (vlt0.x * mat[2]) + (vlt0.y * mat[6]) + (vlt0.z * mat[10]) + mat[14];
fp1.x = (vlt1.x * mat[0]) + (vlt1.y * mat[4]) + (vlt1.z * mat[8]) + mat[12];
fp1.y = (vlt1.x * mat[1]) + (vlt1.y * mat[5]) + (vlt1.z * mat[9]) + mat[13];
fp1.z = (vlt1.x * mat[2]) + (vlt1.y * mat[6]) + (vlt1.z * mat[10]) + mat[14];
fp2.x = (vlt2.x * mat[0]) + (vlt2.y * mat[4]) + (vlt2.z * mat[8]) + mat[12];
fp2.y = (vlt2.x * mat[1]) + (vlt2.y * mat[5]) + (vlt2.z * mat[9]) + mat[13];
fp2.z = (vlt2.x * mat[2]) + (vlt2.y * mat[6]) + (vlt2.z * mat[10]) + mat[14];
f = (fp.x * fp.x) + (fp.y * fp.y) + (fp.z * fp.z);
g = (fp1.x * fp1.x) + (fp1.y * fp1.y) + (fp1.z * fp1.z);
if (f > g)
f = g;
g = (fp2.x * fp2.x) + (fp2.y * fp2.y) + (fp2.z * fp2.z);
if (f > g)
f = g;
m->maxdepths[i] = f;
m->indexes[i] = i;
}
// dichotomic recursive sorting - about 100x less iterations than bubblesort
quicksort(m->indexes, m->maxdepths, 0, s->numtris - 1);
}
md3draw_handle_triangles(s, indexhandle, texunits, m->usesalpha ? m : NULL);
}
else
{
#ifdef USE_GLEXT
if (r_vertexarrays && r_vbos)
{
bglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexvbos[curvbo]);
vbotemp = bglMapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
indexhandle = (uint16_t *) vbotemp;
}
else
#endif
indexhandle = m->vindexes;
md3draw_handle_triangles(s, indexhandle, texunits, NULL);
}
if (r_vertexarrays)
{
#ifdef USE_GLEXT
int32_t l;
if (r_vbos)
{
bglUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB);
bglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, m->vbos[surfi]);
l = GL_TEXTURE0_ARB;
do
{
bglClientActiveTextureARB(l++);
bglEnableClientState(GL_TEXTURE_COORD_ARRAY);
bglTexCoordPointer(2, GL_FLOAT, 0, 0);
} while (l <= texunits);
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, vertvbos[curvbo]);
bglVertexPointer(3, GL_FLOAT, 0, 0);
bglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexvbos[curvbo]);
bglDrawElements(GL_TRIANGLES, s->numtris * 3, GL_UNSIGNED_SHORT, 0);
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
bglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
}
else // r_vbos
{
l = GL_TEXTURE0_ARB;
do
{
bglClientActiveTextureARB(l++);
bglEnableClientState(GL_TEXTURE_COORD_ARRAY);
bglTexCoordPointer(2, GL_FLOAT, 0, &(s->uv[0].u));
} while (l <= texunits);
bglVertexPointer(3, GL_FLOAT, 0, &(vertlist[0].x));
bglDrawElements(GL_TRIANGLES, s->numtris * 3, GL_UNSIGNED_SHORT, m->vindexes);
} // r_vbos
while (texunits > GL_TEXTURE0_ARB)
{
bglMatrixMode(GL_TEXTURE);
bglLoadIdentity();
bglMatrixMode(GL_MODELVIEW);
bglTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0f);
bglDisable(GL_TEXTURE_2D);
bglDisableClientState(GL_TEXTURE_COORD_ARRAY);
bglClientActiveTextureARB(texunits - 1);
bglActiveTextureARB(--texunits);
}
#else
bglEnableClientState(GL_TEXTURE_COORD_ARRAY);
bglTexCoordPointer(2, GL_FLOAT, 0, &(s->uv[0].u));
bglVertexPointer(3, GL_FLOAT, 0, &(vertlist[0].x));
bglDrawElements(GL_TRIANGLES, s->numtris * 3, GL_UNSIGNED_SHORT, m->vindexes);
#endif
}
#ifdef USE_GLEXT
else // r_vertexarrays
{
while (texunits > GL_TEXTURE0_ARB)
{
bglMatrixMode(GL_TEXTURE);
bglLoadIdentity();
bglMatrixMode(GL_MODELVIEW);
bglTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0f);
bglDisable(GL_TEXTURE_2D);
bglActiveTextureARB(--texunits);
}
} // r_vertexarrays
#endif
}
//------------
if (m->usesalpha) bglDisable(GL_ALPHA_TEST);
bglDisable(GL_CULL_FACE);
// bglPopAttrib();
bglMatrixMode(GL_TEXTURE);
bglLoadIdentity();
bglMatrixMode(GL_MODELVIEW);
bglLoadIdentity();
globalnoeffect=0;
return 1;
}
static void md3free(md3model_t *m)
{
mdanim_t *anim, *nanim = NULL;
mdskinmap_t *sk, *nsk = NULL;
if (!m) return;
for (anim=m->animations; anim; anim=nanim)
{
nanim = anim->next;
Bfree(anim);
}
for (sk=m->skinmap; sk; sk=nsk)
{
nsk = sk->next;
Bfree(sk->fn);
Bfree(sk);
}
if (m->head.surfs)
{
for (int surfi=m->head.numsurfs-1; surfi>=0; surfi--)
{
md3surf_t *s = &m->head.surfs[surfi];
Bfree(s->tris);
Bfree(s->geometry); // FREE_SURFS_GEOMETRY
}
Bfree(m->head.surfs);
}
Bfree(m->head.tags);
Bfree(m->head.frames);
Bfree(m->texid);
Bfree(m->muladdframes);
Bfree(m->indexes);
Bfree(m->vindexes);
Bfree(m->maxdepths);
#ifdef USE_GLEXT
if (m->vbos)
{
bglDeleteBuffersARB(m->head.numsurfs, m->vbos);
DO_FREE_AND_NULL(m->vbos);
}
#endif
Bfree(m);
}
//---------------------------------------- MD3 LIBRARY ENDS ----------------------------------------
//--------------------------------------- MD LIBRARY BEGINS ---------------------------------------
mdmodel_t *mdload(const char *filnam)
{
mdmodel_t *vm;
int32_t fil;
int32_t i;
vm = (mdmodel_t *)voxload(filnam);
if (vm) return vm;
fil = kopen4load((char *)filnam,0);
if (fil < 0)
return NULL;
kread(fil,&i,4);
klseek(fil,0,SEEK_SET);
switch (B_LITTLE32(i))
{
case IDP2_MAGIC:
// initprintf("Warning: model \"%s\" is version IDP2; wanted version IDP3\n",filnam);
vm = (mdmodel_t *)md2load(fil,filnam);
break; //IDP2
case IDP3_MAGIC:
vm = (mdmodel_t *)md3load(fil);
break; //IDP3
default:
vm = NULL;
break;
}
kclose(fil);
if (vm)
{
md3model_t *vm3 = (md3model_t *)vm;
// smuggle the file name into the model struct.
// head.nam is unused as far as I can tell
Bstrncpyz(vm3->head.nam, filnam, sizeof(vm3->head.nam));
md3postload_common(vm3);
#ifdef POLYMER
if (glrendmode != REND_POLYMER)
if (md3postload_polymer_check(vm3))
{
mdfree(vm);
vm = NULL;
}
#endif
}
return vm;
}
#ifdef USE_GLEXT
void md_allocvbos(void)
{
int32_t i;
indexvbos = (GLuint *) Xrealloc(indexvbos, sizeof(GLuint) * r_vbocount);
vertvbos = (GLuint *) Xrealloc(vertvbos, sizeof(GLuint) * r_vbocount);
if (r_vbocount != allocvbos)
{
bglGenBuffersARB(r_vbocount - allocvbos, &(indexvbos[allocvbos]));
bglGenBuffersARB(r_vbocount - allocvbos, &(vertvbos[allocvbos]));
i = allocvbos;
while (i < r_vbocount)
{
bglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexvbos[i]);
bglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, maxmodeltris * 3 * sizeof(uint16_t), NULL, GL_STREAM_DRAW_ARB);
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, vertvbos[i]);
bglBufferDataARB(GL_ARRAY_BUFFER_ARB, maxmodelverts * sizeof(vec3f_t), NULL, GL_STREAM_DRAW_ARB);
i++;
}
bglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
bglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
allocvbos = r_vbocount;
}
}
#endif
int32_t polymost_mddraw(const tspritetype *tspr)
{
#ifdef USE_GLEXT
if (r_vbos && (r_vbocount > allocvbos))
md_allocvbos();
#endif
if (maxmodelverts > allocmodelverts)
{
vertlist = (vec3f_t *) Xrealloc(vertlist, sizeof(vec3f_t)*maxmodelverts);
allocmodelverts = maxmodelverts;
}
mdmodel_t const *const vm = models[tile2model[Ptile2tile(tspr->picnum,
(tspr->owner >= MAXSPRITES) ? tspr->pal : sprite[tspr->owner].pal)].modelid];
if (vm->mdnum == 1) { return polymost_voxdraw((voxmodel_t *)vm,tspr); }
if (vm->mdnum == 3) { return polymost_md3draw((md3model_t *)vm,tspr); }
return 0;
}
void mdfree(mdmodel_t *vm)
{
if (vm->mdnum == 1) { voxfree((voxmodel_t *)vm); return; }
if (vm->mdnum == 2 || vm->mdnum == 3) { md3free((md3model_t *)vm); return; }
}
#endif
//---------------------------------------- MD LIBRARY ENDS ----------------------------------------