OctaCore/src/engine/rendermodel.cc

1161 lines
31 KiB
C++

#include "rendermodel.hh"
#include <climits>
#include <new>
#include <algorithm>
#include <sauerlib/endian.hh>
#include <sauerlib/encoding.hh>
#include <sauerlib/stream.hh>
#include <shared/command.hh>
#include <shared/glemu.hh>
#include <shared/igame.hh>
#include "aa.hh"
#include "command.hh" // identflags
#include "console.hh" /* conoutf */
#include "main.hh" // loadprogress, renderprogress, timings
#include "octarender.hh"
#include "physics.hh" // collidewall
#include "pvs.hh"
#include "rendergl.hh"
#include "renderva.hh"
#include "shader.hh"
#include "texture.hh"
VAR(oqdynent, 0, 1, 1);
VAR(animationinterpolationtime, 0, 200, 1000);
model *loadingmodel = nullptr;
#include "ragdoll.hh"
#include "animmodel.hh"
#include "vertmodel.hh"
#include "skelmodel.hh"
#include "hitzone.hh"
static model *(__cdecl *modeltypes[NUMMODELTYPES])(const char *);
static int addmodeltype(int type, model *(__cdecl *loader)(const char *))
{
modeltypes[type] = loader;
return type;
}
#define MODELTYPE(modeltype, modelclass) \
static model *__loadmodel__##modelclass(const char *filename) \
{ \
return new modelclass(filename); \
} \
[[maybe_unused]] static int __dummy__##modelclass = addmodeltype((modeltype), __loadmodel__##modelclass);
static void loadskin(const char *dir, const char *altdir, Texture *&skin, Texture *&masks);
#include "md2.hh"
#include "md3.hh"
#include "md5.hh"
#include "obj.hh"
#include "smd.hh"
#include "iqm.hh"
MODELTYPE(MDL_MD2, md2);
MODELTYPE(MDL_MD3, md3);
MODELTYPE(MDL_MD5, md5);
MODELTYPE(MDL_OBJ, obj);
MODELTYPE(MDL_SMD, smd);
MODELTYPE(MDL_IQM, iqm);
#define checkmdl if(!loadingmodel) { conoutf(CON_ERROR, "not loading a model"); return; }
static void mdlcullface(int *cullface)
{
checkmdl;
loadingmodel->setcullface(*cullface);
}
COMMAND(mdlcullface, "i");
static void mdlcolor(float *r, float *g, float *b)
{
checkmdl;
loadingmodel->setcolor(vec(*r, *g, *b));
}
COMMAND(mdlcolor, "fff");
static void mdlcollide(int *collide)
{
checkmdl;
loadingmodel->collide = *collide!=0 ? (loadingmodel->collide ? loadingmodel->collide : COLLIDE_OBB) : COLLIDE_NONE;
}
COMMAND(mdlcollide, "i");
static void mdlellipsecollide(int *collide)
{
checkmdl;
loadingmodel->collide = *collide!=0 ? COLLIDE_ELLIPSE : COLLIDE_NONE;
}
COMMAND(mdlellipsecollide, "i");
static void mdltricollide(char *collide)
{
checkmdl;
DELETEA(loadingmodel->collidemodel);
char *end = nullptr;
int val = strtol(collide, &end, 0);
if(*end) { val = 1; loadingmodel->collidemodel = newstring(collide); }
loadingmodel->collide = val ? COLLIDE_TRI : COLLIDE_NONE;
}
COMMAND(mdltricollide, "s");
static void mdlspec(float *percent)
{
checkmdl;
float spec = *percent > 0 ? *percent/100.0f : 0.0f;
loadingmodel->setspec(spec);
}
COMMAND(mdlspec, "f");
static void mdlgloss(int *gloss)
{
checkmdl;
loadingmodel->setgloss(std::clamp(*gloss, 0, 2));
}
COMMAND(mdlgloss, "i");
static void mdlalphatest(float *cutoff)
{
checkmdl;
loadingmodel->setalphatest(std::max(0.0f, std::min(1.0f, *cutoff)));
}
COMMAND(mdlalphatest, "f");
static void mdldepthoffset(int *offset)
{
checkmdl;
loadingmodel->depthoffset = *offset!=0;
}
COMMAND(mdldepthoffset, "i");
static void mdlglow(float *percent, float *delta, float *pulse)
{
checkmdl;
float glow = *percent > 0 ? *percent/100.0f : 0.0f, glowdelta = *delta/100.0f, glowpulse = *pulse > 0 ? *pulse/1000.0f : 0;
glowdelta -= glow;
loadingmodel->setglow(glow, glowdelta, glowpulse);
}
COMMAND(mdlglow, "fff");
static void mdlenvmap(float *envmapmax, float *envmapmin, char *envmap)
{
checkmdl;
loadingmodel->setenvmap(*envmapmin, *envmapmax, envmap[0] ? cubemapload(envmap) : nullptr);
}
COMMAND(mdlenvmap, "ffs");
static void mdlfullbright(float *fullbright)
{
checkmdl;
loadingmodel->setfullbright(*fullbright);
}
COMMAND(mdlfullbright, "f");
static void mdlshader(char *shader)
{
checkmdl;
loadingmodel->setshader(lookupshaderbyname(shader));
}
COMMAND(mdlshader, "s");
static void mdlspin(float *yaw, float *pitch, float *roll)
{
checkmdl;
loadingmodel->spinyaw = *yaw;
loadingmodel->spinpitch = *pitch;
loadingmodel->spinroll = *roll;
}
COMMAND(mdlspin, "fff");
static void mdlscale(float *percent)
{
checkmdl;
float scale = *percent > 0 ? *percent/100.0f : 1.0f;
loadingmodel->scale = scale;
}
COMMAND(mdlscale, "f");
static void mdltrans(float *x, float *y, float *z)
{
checkmdl;
loadingmodel->translate = vec(*x, *y, *z);
}
COMMAND(mdltrans, "fff");
static void mdlyaw(float *angle)
{
checkmdl;
loadingmodel->offsetyaw = *angle;
}
COMMAND(mdlyaw, "f");
static void mdlpitch(float *angle)
{
checkmdl;
loadingmodel->offsetpitch = *angle;
}
COMMAND(mdlpitch, "f");
static void mdlroll(float *angle)
{
checkmdl;
loadingmodel->offsetroll = *angle;
}
COMMAND(mdlroll, "f");
static void mdlshadow(int *shadow)
{
checkmdl;
loadingmodel->shadow = *shadow!=0;
}
COMMAND(mdlshadow, "i");
static void mdlalphashadow(int *alphashadow)
{
checkmdl;
loadingmodel->alphashadow = *alphashadow!=0;
}
COMMAND(mdlalphashadow, "i");
static void mdlbb(float *rad, float *h, float *eyeheight)
{
checkmdl;
loadingmodel->collidexyradius = *rad;
loadingmodel->collideheight = *h;
loadingmodel->eyeheight = *eyeheight;
}
COMMAND(mdlbb, "fff");
static void mdlextendbb(float *x, float *y, float *z)
{
checkmdl;
loadingmodel->bbextend = vec(*x, *y, *z);
}
COMMAND(mdlextendbb, "fff");
static void mdlname()
{
checkmdl;
result(loadingmodel->name);
}
COMMAND(mdlname, "");
#define checkragdoll \
checkmdl; \
if(!loadingmodel->skeletal()) { conoutf(CON_ERROR, "not loading a skeletal model"); return; } \
skelmodel *m = (skelmodel *)loadingmodel; \
if(m->parts.empty()) return; \
skelmodel::skelmeshgroup *meshes = (skelmodel::skelmeshgroup *)m->parts.last()->meshes; \
if(!meshes) return; \
skelmodel::skeleton *skel = meshes->skel; \
if(!skel->ragdoll) skel->ragdoll = new ragdollskel; \
ragdollskel *ragdoll = skel->ragdoll; \
if(ragdoll->loaded) return;
static void rdvert(float *x, float *y, float *z, float *radius)
{
checkragdoll;
ragdollskel::vert &v = ragdoll->verts.add();
v.pos = vec(*x, *y, *z);
v.radius = *radius > 0 ? *radius : 1;
}
COMMAND(rdvert, "ffff");
static void rdeye(int *v)
{
checkragdoll;
ragdoll->eye = *v;
}
COMMAND(rdeye, "i");
static void rdtri(int *v1, int *v2, int *v3)
{
checkragdoll;
ragdollskel::tri &t = ragdoll->tris.add();
t.vert[0] = *v1;
t.vert[1] = *v2;
t.vert[2] = *v3;
}
COMMAND(rdtri, "iii");
static void rdjoint(int *n, int *t, int *v1, int *v2, int *v3)
{
checkragdoll;
if(*n < 0 || *n >= skel->numbones) return;
ragdollskel::joint &j = ragdoll->joints.add();
j.bone = *n;
j.tri = *t;
j.vert[0] = *v1;
j.vert[1] = *v2;
j.vert[2] = *v3;
}
COMMAND(rdjoint, "iibbb");
static void rdlimitdist(int *v1, int *v2, float *mindist, float *maxdist)
{
checkragdoll;
ragdollskel::distlimit &d = ragdoll->distlimits.add();
d.vert[0] = *v1;
d.vert[1] = *v2;
d.mindist = *mindist;
d.maxdist = std::max(*maxdist, *mindist);
}
COMMAND(rdlimitdist, "iiff");
static void rdlimitrot(int *t1, int *t2, float *maxangle, float *qx, float *qy, float *qz, float *qw)
{
checkragdoll;
ragdollskel::rotlimit &r = ragdoll->rotlimits.add();
r.tri[0] = *t1;
r.tri[1] = *t2;
r.maxangle = *maxangle * RAD;
r.maxtrace = 1 + 2*cos(r.maxangle);
r.middle = matrix3(quat(*qx, *qy, *qz, *qw));
}
COMMAND(rdlimitrot, "iifffff");
static void rdanimjoints(int *on)
{
checkragdoll;
ragdoll->animjoints = *on!=0;
}
COMMAND(rdanimjoints, "i");
// mapmodels
vector<mapmodelinfo> mapmodels;
static const char * const mmprefix = "mapmodel/";
static const int mmprefixlen = strlen(mmprefix);
static void mapmodel(char *name)
{
mapmodelinfo &mmi = mapmodels.add();
if(name[0]) formatstring(mmi.name, "%s%s", mmprefix, name);
else mmi.name[0] = '\0';
mmi.m = mmi.collide = nullptr;
}
static void mapmodelreset(int *n)
{
if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return;
mapmodels.shrink(std::clamp(*n, 0, mapmodels.length()));
}
const char *mapmodelname(int i) { return mapmodels.inrange(i) ? mapmodels[i].name : nullptr; }
ICOMMAND(mmodel, "s", (char *name), mapmodel(name));
COMMAND(mapmodel, "s");
COMMAND(mapmodelreset, "i");
ICOMMAND(mapmodelname, "ii", (int *index, int *prefix), { if(mapmodels.inrange(*index)) result(mapmodels[*index].name[0] ? mapmodels[*index].name + (*prefix ? 0 : mmprefixlen) : ""); });
ICOMMAND(mapmodelloaded, "i", (int *index), { intret(mapmodels.inrange(*index) && mapmodels[*index].m ? 1 : 0); });
ICOMMAND(nummapmodels, "", (), { intret(mapmodels.length()); });
// model registry
static hashnameset<model *> models;
static vector<const char *> preloadmodels;
static hashset<char *> failedmodels;
void preloadmodel(const char *name)
{
if(!name || !name[0] || models.access(name) || preloadmodels.htfind(name) >= 0) return;
preloadmodels.add(newstring(name));
}
void flushpreloadedmodels(bool msg)
{
loopv(preloadmodels)
{
loadprogress = float(i+1)/preloadmodels.length();
model *m = loadmodel(preloadmodels[i], -1, msg);
if(!m) { if(msg) conoutf(CON_WARN, "could not load model: %s", preloadmodels[i]); }
else
{
m->preloadmeshes();
m->preloadshaders();
}
}
preloadmodels.deletearrays();
loadprogress = 0;
}
void preloadusedmapmodels(bool msg, bool bih)
{
vector<extentity *> &ents = entities::getents();
vector<int> used;
loopv(ents)
{
extentity &e = *ents[i];
if(e.type==ET_MAPMODEL && e.attr1 >= 0 && used.find(e.attr1) < 0) used.add(e.attr1);
}
vector<const char *> col;
loopv(used)
{
loadprogress = float(i+1)/used.length();
int mmindex = used[i];
if(!mapmodels.inrange(mmindex)) { if(msg) conoutf(CON_WARN, "could not find map model: %d", mmindex); continue; }
mapmodelinfo &mmi = mapmodels[mmindex];
if(!mmi.name[0]) continue;
model *m = loadmodel(nullptr, mmindex, msg);
if(!m) { if(msg) conoutf(CON_WARN, "could not load map model: %s", mmi.name); }
else
{
if(bih) m->preloadBIH();
else if(m->collide == COLLIDE_TRI && !m->collidemodel && m->bih) m->setBIH();
m->preloadmeshes();
m->preloadshaders();
if(m->collidemodel && col.htfind(m->collidemodel) < 0) col.add(m->collidemodel);
}
}
loopv(col)
{
loadprogress = float(i+1)/col.length();
model *m = loadmodel(col[i], -1, msg);
if(!m) { if(msg) conoutf(CON_WARN, "could not load collide model: %s", col[i]); }
else if(!m->bih) m->setBIH();
}
loadprogress = 0;
}
model *loadmodel(const char *name, int i, bool msg)
{
if(!name)
{
if(!mapmodels.inrange(i)) return nullptr;
mapmodelinfo &mmi = mapmodels[i];
if(mmi.m) return mmi.m;
name = mmi.name;
}
model **mm = models.access(name);
model *m;
if(mm) m = *mm;
else
{
if(!name[0] || loadingmodel || failedmodels.find(name, nullptr)) return nullptr;
if(msg)
{
defformatstring(filename, "media/model/%s", name);
renderprogress(loadprogress, filename);
}
loopi(NUMMODELTYPES)
{
m = modeltypes[i](name);
if(!m) continue;
loadingmodel = m;
if(m->load()) break;
DELETEP(m);
}
loadingmodel = nullptr;
if(!m)
{
failedmodels.add(newstring(name));
return nullptr;
}
models.access(m->name, m);
}
if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m;
return m;
}
void clear_models()
{
enumerate(models, model *, m, delete m);
}
void cleanupmodels()
{
enumerate(models, model *, m, m->cleanup());
}
static void clearmodel(char *name)
{
model *m = models.find(name, nullptr);
if(!m) { conoutf("model %s is not loaded", name); return; }
loopv(mapmodels)
{
mapmodelinfo &mmi = mapmodels[i];
if(mmi.m == m) mmi.m = nullptr;
if(mmi.collide == m) mmi.collide = nullptr;
}
models.remove(name);
m->cleanup();
delete m;
conoutf("cleared model %s", name);
}
COMMAND(clearmodel, "s");
static bool modeloccluded(const vec &center, float radius)
{
ivec bbmin(vec(center).sub(radius)), bbmax(vec(center).add(radius+1));
return pvsoccluded(bbmin, bbmax) || bboccluded(bbmin, bbmax);
}
struct batchedmodel
{
vec pos, center;
float radius, yaw, pitch, roll, sizescale;
vec4 colorscale;
int anim, basetime, basetime2, flags, attached;
union
{
int visible;
int culled;
};
dynent *d;
int next;
};
struct modelbatch
{
model *m;
int flags, batched;
};
static vector<batchedmodel> batchedmodels;
static vector<modelbatch> batches;
static vector<modelattach> modelattached;
void resetmodelbatches()
{
batchedmodels.setsize(0);
batches.setsize(0);
modelattached.setsize(0);
}
static void addbatchedmodel(model *m, batchedmodel &bm, int idx)
{
modelbatch *b = nullptr;
if(batches.inrange(m->batch))
{
b = &batches[m->batch];
if(b->m == m && (b->flags & MDL_MAPMODEL) == (bm.flags & MDL_MAPMODEL))
goto foundbatch;
}
m->batch = batches.length();
b = &batches.add();
b->m = m;
b->flags = 0;
b->batched = -1;
foundbatch:
b->flags |= bm.flags;
bm.next = b->batched;
b->batched = idx;
}
static inline void renderbatchedmodel(model *m, const batchedmodel &b)
{
modelattach *a = nullptr;
if(b.attached>=0) a = &modelattached[b.attached];
int anim = b.anim;
if(shadowmapping > SM_REFLECT)
{
anim |= ANIM_NOSKIN;
}
else
{
if(b.flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT;
}
m->render(anim, b.basetime, b.basetime2, b.pos, b.yaw, b.pitch, b.roll, b.d, a, b.sizescale, b.colorscale);
}
VAR(maxmodelradiusdistance, 10, 200, 1000);
static inline void enablecullmodelquery()
{
startbb();
}
static inline void rendercullmodelquery(model *m, dynent *d, const vec &center, float radius)
{
if(fabs(camera1->o.x-center.x) < radius+1 &&
fabs(camera1->o.y-center.y) < radius+1 &&
fabs(camera1->o.z-center.z) < radius+1)
{
d->query = nullptr;
return;
}
d->query = newquery(d);
if(!d->query) return;
startquery(d->query);
int br = int(radius*2)+1;
drawbb(ivec(int(center.x-radius), int(center.y-radius), int(center.z-radius)), ivec(br, br, br));
endquery(d->query);
}
static inline void disablecullmodelquery()
{
endbb();
}
static inline int cullmodel(model *m, const vec &center, float radius, int flags, dynent *d = nullptr)
{
if(flags&MDL_CULL_DIST && center.dist(camera1->o)/radius>maxmodelradiusdistance) return MDL_CULL_DIST;
if(flags&MDL_CULL_VFC && isfoggedsphere(radius, center)) return MDL_CULL_VFC;
if(flags&MDL_CULL_OCCLUDED && modeloccluded(center, radius)) return MDL_CULL_OCCLUDED;
else if(flags&MDL_CULL_QUERY && d->query && d->query->owner==d && checkquery(d->query)) return MDL_CULL_QUERY;
return 0;
}
static inline int shadowmaskmodel(const vec &center, float radius)
{
switch(shadowmapping)
{
case SM_REFLECT:
return calcspherersmsplits(center, radius);
case SM_CUBEMAP:
{
vec scenter = vec(center).sub(shadoworigin);
float sradius = radius + shadowradius;
if(scenter.squaredlen() >= sradius*sradius) return 0;
return calcspheresidemask(scenter, radius, shadowbias);
}
case SM_CASCADE:
return calcspherecsmsplits(center, radius);
case SM_SPOT:
{
vec scenter = vec(center).sub(shadoworigin);
float sradius = radius + shadowradius;
return scenter.squaredlen() < sradius*sradius && sphereinsidespot(shadowdir, shadowspot, scenter, radius) ? 1 : 0;
}
}
return 0;
}
void shadowmaskbatchedmodels(bool dynshadow)
{
loopv(batchedmodels)
{
batchedmodel &b = batchedmodels[i];
if(b.flags&(MDL_MAPMODEL | MDL_NOSHADOW)) break;
b.visible = dynshadow && (b.colorscale.a >= 1 || b.flags&(MDL_ONLYSHADOW | MDL_FORCESHADOW)) ? shadowmaskmodel(b.center, b.radius) : 0;
}
}
int batcheddynamicmodels()
{
int visible = 0;
loopv(batchedmodels)
{
batchedmodel &b = batchedmodels[i];
if(b.flags&MDL_MAPMODEL) break;
visible |= b.visible;
}
loopv(batches)
{
modelbatch &b = batches[i];
if(!(b.flags&MDL_MAPMODEL) || !b.m->animated()) continue;
for(int j = b.batched; j >= 0;)
{
batchedmodel &bm = batchedmodels[j];
j = bm.next;
visible |= bm.visible;
}
}
return visible;
}
int batcheddynamicmodelbounds(int mask, vec &bbmin, vec &bbmax)
{
int vis = 0;
loopv(batchedmodels)
{
batchedmodel &b = batchedmodels[i];
if(b.flags&MDL_MAPMODEL) break;
if(b.visible&mask)
{
bbmin.min(vec(b.center).sub(b.radius));
bbmax.max(vec(b.center).add(b.radius));
++vis;
}
}
loopv(batches)
{
modelbatch &b = batches[i];
if(!(b.flags&MDL_MAPMODEL) || !b.m->animated()) continue;
for(int j = b.batched; j >= 0;)
{
batchedmodel &bm = batchedmodels[j];
j = bm.next;
if(bm.visible&mask)
{
bbmin.min(vec(bm.center).sub(bm.radius));
bbmax.max(vec(bm.center).add(bm.radius));
++vis;
}
}
}
return vis;
}
void rendershadowmodelbatches(bool dynmodel)
{
loopv(batches)
{
modelbatch &b = batches[i];
if(!b.m->shadow || (!dynmodel && (!(b.flags&MDL_MAPMODEL) || b.m->animated()))) continue;
bool rendered = false;
for(int j = b.batched; j >= 0;)
{
batchedmodel &bm = batchedmodels[j];
j = bm.next;
if(!(bm.visible&(1<<shadowside))) continue;
if(!rendered) { b.m->startrender(); rendered = true; }
renderbatchedmodel(b.m, bm);
}
if(rendered) b.m->endrender();
}
}
void rendermapmodelbatches()
{
enableaamask();
loopv(batches)
{
modelbatch &b = batches[i];
if(!(b.flags&MDL_MAPMODEL)) continue;
b.m->startrender();
setaamask(b.m->animated());
for(int j = b.batched; j >= 0;)
{
batchedmodel &bm = batchedmodels[j];
renderbatchedmodel(b.m, bm);
j = bm.next;
}
b.m->endrender();
}
disableaamask();
}
float transmdlsx1 = -1, transmdlsy1 = -1, transmdlsx2 = 1, transmdlsy2 = 1;
uint transmdltiles[LIGHTTILE_MAXH];
void rendermodelbatches()
{
transmdlsx1 = transmdlsy1 = 1;
transmdlsx2 = transmdlsy2 = -1;
memset(transmdltiles, 0, sizeof(transmdltiles));
enableaamask();
loopv(batches)
{
modelbatch &b = batches[i];
if(b.flags&MDL_MAPMODEL) continue;
bool rendered = false;
for(int j = b.batched; j >= 0;)
{
batchedmodel &bm = batchedmodels[j];
j = bm.next;
bm.culled = cullmodel(b.m, bm.center, bm.radius, bm.flags, bm.d);
if(bm.culled || bm.flags&MDL_ONLYSHADOW) continue;
if(bm.colorscale.a < 1 || bm.flags&MDL_FORCETRANSPARENT)
{
float sx1, sy1, sx2, sy2;
ivec bbmin(vec(bm.center).sub(bm.radius)), bbmax(vec(bm.center).add(bm.radius+1));
if(calcbbscissor(bbmin, bbmax, sx1, sy1, sx2, sy2))
{
transmdlsx1 = std::min(transmdlsx1, sx1);
transmdlsy1 = std::min(transmdlsy1, sy1);
transmdlsx2 = std::max(transmdlsx2, sx2);
transmdlsy2 = std::max(transmdlsy2, sy2);
masktiles(transmdltiles, sx1, sy1, sx2, sy2);
}
continue;
}
if(!rendered)
{
b.m->startrender();
rendered = true;
setaamask(true);
}
if(bm.flags&MDL_CULL_QUERY)
{
bm.d->query = newquery(bm.d);
if(bm.d->query)
{
startquery(bm.d->query);
renderbatchedmodel(b.m, bm);
endquery(bm.d->query);
continue;
}
}
renderbatchedmodel(b.m, bm);
}
if(rendered) b.m->endrender();
if(b.flags&MDL_CULL_QUERY)
{
bool queried = false;
for(int j = b.batched; j >= 0;)
{
batchedmodel &bm = batchedmodels[j];
j = bm.next;
if(bm.culled&(MDL_CULL_OCCLUDED|MDL_CULL_QUERY) && bm.flags&MDL_CULL_QUERY)
{
if(!queried)
{
if(rendered) setaamask(false);
enablecullmodelquery();
queried = true;
}
rendercullmodelquery(b.m, bm.d, bm.center, bm.radius);
}
}
if(queried) disablecullmodelquery();
}
}
disableaamask();
}
void rendertransparentmodelbatches(int stencil)
{
enableaamask(stencil);
loopv(batches)
{
modelbatch &b = batches[i];
if(b.flags&MDL_MAPMODEL) continue;
bool rendered = false;
for(int j = b.batched; j >= 0;)
{
batchedmodel &bm = batchedmodels[j];
j = bm.next;
bm.culled = cullmodel(b.m, bm.center, bm.radius, bm.flags, bm.d);
if(bm.culled || !(bm.colorscale.a < 1 || bm.flags&MDL_FORCETRANSPARENT) || bm.flags&MDL_ONLYSHADOW) continue;
if(!rendered)
{
b.m->startrender();
rendered = true;
setaamask(true);
}
if(bm.flags&MDL_CULL_QUERY)
{
bm.d->query = newquery(bm.d);
if(bm.d->query)
{
startquery(bm.d->query);
renderbatchedmodel(b.m, bm);
endquery(bm.d->query);
continue;
}
}
renderbatchedmodel(b.m, bm);
}
if(rendered) b.m->endrender();
}
disableaamask();
}
static occludequery *modelquery = nullptr;
static int modelquerybatches = -1, modelquerymodels = -1, modelqueryattached = -1;
void startmodelquery(occludequery *query)
{
modelquery = query;
modelquerybatches = batches.length();
modelquerymodels = batchedmodels.length();
modelqueryattached = modelattached.length();
}
void endmodelquery()
{
if(batchedmodels.length() == modelquerymodels)
{
modelquery->fragments = 0;
modelquery = nullptr;
return;
}
enableaamask();
startquery(modelquery);
loopv(batches)
{
modelbatch &b = batches[i];
int j = b.batched;
if(j < modelquerymodels) continue;
b.m->startrender();
setaamask(!(b.flags&MDL_MAPMODEL) || b.m->animated());
do
{
batchedmodel &bm = batchedmodels[j];
renderbatchedmodel(b.m, bm);
j = bm.next;
}
while(j >= modelquerymodels);
b.batched = j;
b.m->endrender();
}
endquery(modelquery);
modelquery = nullptr;
batches.setsize(modelquerybatches);
batchedmodels.setsize(modelquerymodels);
modelattached.setsize(modelqueryattached);
disableaamask();
}
void clearbatchedmapmodels()
{
loopv(batches)
{
modelbatch &b = batches[i];
if(b.flags&MDL_MAPMODEL)
{
batchedmodels.setsize(b.batched);
batches.setsize(i);
break;
}
}
}
void rendermapmodel(int idx, int anim, const vec &o, float yaw, float pitch, float roll, int flags, int basetime, float size)
{
if(!mapmodels.inrange(idx)) return;
mapmodelinfo &mmi = mapmodels[idx];
model *m = mmi.m ? mmi.m : loadmodel(mmi.name);
if(!m) return;
vec center, bbradius;
m->boundbox(center, bbradius);
float radius = bbradius.magnitude();
center.mul(size);
if(roll) center.rotate_around_y(-roll*RAD);
if(pitch && m->pitched()) center.rotate_around_x(pitch*RAD);
center.rotate_around_z(yaw*RAD);
center.add(o);
radius *= size;
int visible = 0;
if(shadowmapping)
{
if(!m->shadow) return;
visible = shadowmaskmodel(center, radius);
if(!visible) return;
}
else if(flags&(MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED) && cullmodel(m, center, radius, flags))
return;
batchedmodel &b = batchedmodels.add();
b.pos = o;
b.center = center;
b.radius = radius;
b.anim = anim;
b.yaw = yaw;
b.pitch = pitch;
b.roll = roll;
b.basetime = basetime;
b.basetime2 = 0;
b.sizescale = size;
b.colorscale = vec4(1, 1, 1, 1);
b.flags = flags | MDL_MAPMODEL;
b.visible = visible;
b.d = nullptr;
b.attached = -1;
addbatchedmodel(m, b, batchedmodels.length()-1);
}
void rendermodel(const char *mdl, int anim, const vec &o, float yaw, float pitch, float roll, int flags, dynent *d, modelattach *a, int basetime, int basetime2, float size, const vec4 &color)
{
model *m = loadmodel(mdl);
if(!m) return;
vec center, bbradius;
m->boundbox(center, bbradius);
float radius = bbradius.magnitude();
if(d)
{
if(d->ragdoll)
{
if(anim&ANIM_RAGDOLL && d->ragdoll->millis >= basetime)
{
radius = std::max(radius, d->ragdoll->radius);
center = d->ragdoll->center;
goto hasboundbox;
}
DELETEP(d->ragdoll);
}
if(anim&ANIM_RAGDOLL) flags &= ~(MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY);
}
center.mul(size);
if(roll) center.rotate_around_y(-roll*RAD);
if(pitch && m->pitched()) center.rotate_around_x(pitch*RAD);
center.rotate_around_z(yaw*RAD);
center.add(o);
hasboundbox:
radius *= size;
if(flags&MDL_NORENDER) anim |= ANIM_NORENDER;
if(a) for(int i = 0; a[i].tag; i++)
{
if(a[i].name) a[i].m = loadmodel(a[i].name);
}
if(flags&MDL_CULL_QUERY)
{
if(!oqfrags || !oqdynent || !d) flags &= ~MDL_CULL_QUERY;
}
if(flags&MDL_NOBATCH)
{
int culled = cullmodel(m, center, radius, flags, d);
if(culled)
{
if(culled&(MDL_CULL_OCCLUDED|MDL_CULL_QUERY) && flags&MDL_CULL_QUERY)
{
enablecullmodelquery();
rendercullmodelquery(m, d, center, radius);
disablecullmodelquery();
}
return;
}
enableaamask();
if(flags&MDL_CULL_QUERY)
{
d->query = newquery(d);
if(d->query) startquery(d->query);
}
m->startrender();
setaamask(true);
if(flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT;
m->render(anim, basetime, basetime2, o, yaw, pitch, roll, d, a, size, color);
m->endrender();
if(flags&MDL_CULL_QUERY && d->query) endquery(d->query);
disableaamask();
return;
}
batchedmodel &b = batchedmodels.add();
b.pos = o;
b.center = center;
b.radius = radius;
b.anim = anim;
b.yaw = yaw;
b.pitch = pitch;
b.roll = roll;
b.basetime = basetime;
b.basetime2 = basetime2;
b.sizescale = size;
b.colorscale = color;
b.flags = flags;
b.visible = 0;
b.d = d;
b.attached = a ? modelattached.length() : -1;
if(a) for(int i = 0;; i++) { modelattached.add(a[i]); if(!a[i].tag) break; }
addbatchedmodel(m, b, batchedmodels.length()-1);
}
int intersectmodel(const char *mdl, int anim, const vec &pos, float yaw, float pitch, float roll, const vec &o, const vec &ray, float &dist, int mode, dynent *d, modelattach *a, int basetime, int basetime2, float size)
{
model *m = loadmodel(mdl);
if(!m) return -1;
if(d && d->ragdoll && (!(anim&ANIM_RAGDOLL) || d->ragdoll->millis < basetime)) DELETEP(d->ragdoll);
if(a) for(int i = 0; a[i].tag; i++)
{
if(a[i].name) a[i].m = loadmodel(a[i].name);
}
return m->intersect(anim, basetime, basetime2, pos, yaw, pitch, roll, d, a, size, o, ray, dist, mode);
}
void abovemodel(vec &o, const char *mdl)
{
model *m = loadmodel(mdl);
if(!m) return;
o.z += m->above();
}
bool matchanim(const char *name, const char *pattern)
{
for(;; pattern++)
{
const char *s = name;
char c;
for(;; pattern++)
{
c = *pattern;
if(!c || c=='|') break;
else if(c=='*')
{
if(!*s || iscubespace(*s)) break;
do s++; while(*s && !iscubespace(*s));
}
else if(c!=*s) break;
else s++;
}
if(!*s && (!c || c=='|')) return true;
pattern = strchr(pattern, '|');
if(!pattern) break;
}
return false;
}
ICOMMAND(findanims, "s", (char *name),
{
vector<int> anims;
//game::findanims(name, anims);
vector<char> buf;
string num;
loopv(anims)
{
formatstring(num, "%d", anims[i]);
if(i > 0) buf.add(' ');
buf.put(num, strlen(num));
}
buf.add('\0');
result(buf.getbuf());
});
static void loadskin(const char *dir, const char *altdir, Texture *&skin, Texture *&masks) // model skin sharing
{
#define ifnoload(tex, path) if((tex = textureload(path, 0, true, false))==notexture)
#define tryload(tex, prefix, cmd, name) \
ifnoload(tex, makerelpath(mdir, name ".jpg", prefix, cmd)) \
{ \
ifnoload(tex, makerelpath(mdir, name ".png", prefix, cmd)) \
{ \
ifnoload(tex, makerelpath(maltdir, name ".jpg", prefix, cmd)) \
{ \
ifnoload(tex, makerelpath(maltdir, name ".png", prefix, cmd)) return; \
} \
} \
}
defformatstring(mdir, "media/model/%s", dir);
defformatstring(maltdir, "media/model/%s", altdir);
masks = notexture;
tryload(skin, nullptr, nullptr, "skin");
tryload(masks, nullptr, nullptr, "masks");
}
void setbbfrommodel(dynent *d, const char *mdl)
{
model *m = loadmodel(mdl);
if(!m) return;
vec center, radius;
m->collisionbox(center, radius);
if(m->collide != COLLIDE_ELLIPSE) d->collidetype = COLLIDE_OBB;
d->xradius = radius.x + fabs(center.x);
d->yradius = radius.y + fabs(center.y);
d->radius = d->collidetype==COLLIDE_OBB ? sqrtf(d->xradius*d->xradius + d->yradius*d->yradius) : std::max(d->xradius, d->yradius);
d->eyeheight = (center.z-radius.z) + radius.z*2*m->eyeheight;
d->aboveeye = radius.z*2*(1.0f-m->eyeheight);
if (d->aboveeye + d->eyeheight <= 0.5f)
{
float zrad = (0.5f - (d->aboveeye + d->eyeheight)) / 2;
d->aboveeye += zrad;
d->eyeheight += zrad;
}
}