#include "rendermodel.hh" #include #include #include #include #include #include #include #include #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 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 models; static vector preloadmodels; static hashset 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 &ents = entities::getents(); vector 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 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 ¢er, 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 batchedmodels; static vector batches; static vector 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 ¢er, 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 ¢er, 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 ¢er, 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<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 anims; //game::findanims(name, anims); vector 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; } }