From 7256502afa0babe60fcafbd2888cd3e33c3f9b6b Mon Sep 17 00:00:00 2001 From: xolatile Date: Wed, 16 Jul 2025 23:07:43 +0200 Subject: Source code, broken... --- src/engine/rendermodel.cpp | 1142 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1142 insertions(+) create mode 100644 src/engine/rendermodel.cpp (limited to 'src/engine/rendermodel.cpp') diff --git a/src/engine/rendermodel.cpp b/src/engine/rendermodel.cpp new file mode 100644 index 0000000..dc05e69 --- /dev/null +++ b/src/engine/rendermodel.cpp @@ -0,0 +1,1142 @@ +#include "engine.h" + +VAR(oqdynent, 0, 1, 1); +VAR(animationinterpolationtime, 0, 150, 1000); + +model *loadingmodel = NULL; + +#include "ragdoll.h" +#include "animmodel.h" +#include "vertmodel.h" +#include "skelmodel.h" + +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); \ +} \ +UNUSED static int __dummy__##modelclass = addmodeltype((modeltype), __loadmodel__##modelclass); + +#include "md2.h" +#include "md3.h" +#include "md5.h" +#include "obj.h" +#include "smd.h" +#include "iqm.h" + +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; } + +void mdlcullface(int *cullface) +{ + checkmdl; + loadingmodel->setcullface(*cullface!=0); +} + +COMMAND(mdlcullface, "i"); + +void mdlcollide(int *collide) +{ + checkmdl; + loadingmodel->collide = *collide!=0; +} + +COMMAND(mdlcollide, "i"); + +void mdlellipsecollide(int *collide) +{ + checkmdl; + loadingmodel->ellipsecollide = *collide!=0; +} + +COMMAND(mdlellipsecollide, "i"); + +void mdlspec(int *percent) +{ + checkmdl; + float spec = 1.0f; + if(*percent>0) spec = *percent/100.0f; + else if(*percent<0) spec = 0.0f; + loadingmodel->setspec(spec); +} + +COMMAND(mdlspec, "i"); + +void mdlambient(int *percent) +{ + checkmdl; + float ambient = 0.3f; + if(*percent>0) ambient = *percent/100.0f; + else if(*percent<0) ambient = 0.0f; + loadingmodel->setambient(ambient); +} + +COMMAND(mdlambient, "i"); + +void mdlalphatest(float *cutoff) +{ + checkmdl; + loadingmodel->setalphatest(max(0.0f, min(1.0f, *cutoff))); +} + +COMMAND(mdlalphatest, "f"); + +void mdlalphablend(int *blend) +{ + checkmdl; + loadingmodel->setalphablend(*blend!=0); +} + +COMMAND(mdlalphablend, "i"); + +void mdlalphadepth(int *depth) +{ + checkmdl; + loadingmodel->alphadepth = *depth!=0; +} + +COMMAND(mdlalphadepth, "i"); + +void mdldepthoffset(int *offset) +{ + checkmdl; + loadingmodel->depthoffset = *offset!=0; +} + +COMMAND(mdldepthoffset, "i"); + +void mdlglow(int *percent, int *delta, float *pulse) +{ + checkmdl; + float glow = 3.0f, glowdelta = *delta/100.0f, glowpulse = *pulse > 0 ? *pulse/1000.0f : 0; + if(*percent>0) glow = *percent/100.0f; + else if(*percent<0) glow = 0.0f; + glowdelta -= glow; + loadingmodel->setglow(glow, glowdelta, glowpulse); +} + +COMMAND(mdlglow, "iif"); + +void mdlglare(float *specglare, float *glowglare) +{ + checkmdl; + loadingmodel->setglare(*specglare, *glowglare); +} + +COMMAND(mdlglare, "ff"); + +void mdlenvmap(float *envmapmax, float *envmapmin, char *envmap) +{ + checkmdl; + loadingmodel->setenvmap(*envmapmin, *envmapmax, envmap[0] ? cubemapload(envmap) : NULL); +} + +COMMAND(mdlenvmap, "ffs"); + +void mdlfullbright(float *fullbright) +{ + checkmdl; + loadingmodel->setfullbright(*fullbright); +} + +COMMAND(mdlfullbright, "f"); + +void mdlshader(char *shader) +{ + checkmdl; + loadingmodel->setshader(lookupshaderbyname(shader)); +} + +COMMAND(mdlshader, "s"); + +void mdlspin(float *yaw, float *pitch) +{ + checkmdl; + loadingmodel->spinyaw = *yaw; + loadingmodel->spinpitch = *pitch; +} + +COMMAND(mdlspin, "ff"); + +void mdlscale(int *percent) +{ + checkmdl; + float scale = 1.0f; + if(*percent>0) scale = *percent/100.0f; + loadingmodel->scale = scale; +} + +COMMAND(mdlscale, "i"); + +void mdltrans(float *x, float *y, float *z) +{ + checkmdl; + loadingmodel->translate = vec(*x, *y, *z); +} + +COMMAND(mdltrans, "fff"); + +void mdlyaw(float *angle) +{ + checkmdl; + loadingmodel->offsetyaw = *angle; +} + +COMMAND(mdlyaw, "f"); + +void mdlpitch(float *angle) +{ + checkmdl; + loadingmodel->offsetpitch = *angle; +} + +COMMAND(mdlpitch, "f"); + +void mdlshadow(int *shadow) +{ + checkmdl; + loadingmodel->shadow = *shadow!=0; +} + +COMMAND(mdlshadow, "i"); + +void mdlbb(float *rad, float *h, float *eyeheight) +{ + checkmdl; + loadingmodel->collidexyradius = *rad; + loadingmodel->collideheight = *h; + loadingmodel->eyeheight = *eyeheight; +} + +COMMAND(mdlbb, "fff"); + +void mdlextendbb(float *x, float *y, float *z) +{ + checkmdl; + loadingmodel->bbextend = vec(*x, *y, *z); +} + +COMMAND(mdlextendbb, "fff"); + +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; + + +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"); + +void rdeye(int *v) +{ + checkragdoll; + ragdoll->eye = *v; +} +COMMAND(rdeye, "i"); + +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"); + +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"); + +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 = max(*maxdist, *mindist); +} +COMMAND(rdlimitdist, "iiff"); + +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.middle = matrix3(quat(*qx, *qy, *qz, *qw)); +} +COMMAND(rdlimitrot, "iifffff"); + +void rdanimjoints(int *on) +{ + checkragdoll; + ragdoll->animjoints = *on!=0; +} +COMMAND(rdanimjoints, "i"); + +// mapmodels + +vector mapmodels; + +void mmodel(char *name) +{ + mapmodelinfo &mmi = mapmodels.add(); + copystring(mmi.name, name); + mmi.m = NULL; +} + +void mapmodelcompat(int *rad, int *h, int *tex, char *name, char *shadow) +{ + mmodel(name); +} + +void mapmodelreset(int *n) +{ + if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return; + mapmodels.shrink(clamp(*n, 0, mapmodels.length())); +} + +mapmodelinfo *getmminfo(int i) { return mapmodels.inrange(i) ? &mapmodels[i] : 0; } +const char *mapmodelname(int i) { return mapmodels.inrange(i) ? mapmodels[i].name : NULL; } + +COMMAND(mmodel, "s"); +COMMANDN(mapmodel, mapmodelcompat, "iiiss"); +COMMAND(mapmodelreset, "i"); +ICOMMAND(mapmodelname, "i", (int *index), { result(mapmodels.inrange(*index) ? mapmodels[*index].name : ""); }); +ICOMMAND(mapmodelloaded, "i", (int *index), { intret(mapmodels.inrange(*index) && mapmodels[*index].m ? 1 : 0); }); +ICOMMAND(nummapmodels, "", (), { intret(mapmodels.length()); }); +ICOMMAND(mapmodelfind, "s", (char *name), { int found = -1; loopv(mapmodels) if(strstr(mapmodels[i].name, name)) { found = i; break; } intret(found); }); + +// model registry + +hashnameset models; +vector preloadmodels; + +void preloadmodel(const char *name) +{ + if(!name || !name[0] || models.access(name)) 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(); + } + } + preloadmodels.deletearrays(); + loadprogress = 0; +} + +void preloadusedmapmodels(bool msg, bool bih) +{ + vector &ents = entities::getents(); + vector mapmodels; + loopv(ents) + { + extentity &e = *ents[i]; + if(e.type==ET_MAPMODEL && e.attr2 >= 0 && mapmodels.find(e.attr2) < 0) mapmodels.add(e.attr2); + } + + loopv(mapmodels) + { + loadprogress = float(i+1)/mapmodels.length(); + int mmindex = mapmodels[i]; + mapmodelinfo *mmi = getmminfo(mmindex); + if(!mmi) { if(msg) conoutf(CON_WARN, "could not find map model: %d", mmindex); } + else if(mmi->name[0] && !loadmodel(NULL, mmindex, msg)) { if(msg) conoutf(CON_WARN, "could not load model: %s", mmi->name); } + else if(mmi->m) + { + if(bih) mmi->m->preloadBIH(); + mmi->m->preloadmeshes(); + } + } + loadprogress = 0; +} + +bool modelloaded(const char *name) +{ + return models.find(name, NULL) != NULL; +} + +model *loadmodel(const char *name, int i, bool msg) +{ + if(!name) + { + if(!mapmodels.inrange(i)) return NULL; + 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 || lightmapping > 1) return NULL; + if(msg) + { + defformatstring(filename, "packages/models/%s", name); + renderprogress(loadprogress, filename); + } + loopi(NUMMODELTYPES) + { + m = modeltypes[i](name); + if(!m) continue; + loadingmodel = m; + if(m->load()) break; + DELETEP(m); + } + loadingmodel = NULL; + if(!m) return NULL; + models.access(m->name, m); + m->preloadshaders(); + } + if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m; + return m; +} + +void preloadmodelshaders(bool force) +{ + if(initing) return; + enumerate(models, model *, m, m->preloadshaders(force)); +} + +void clear_mdls() +{ + enumerate(models, model *, m, delete m); +} + +void cleanupmodels() +{ + enumerate(models, model *, m, m->cleanup()); +} + +void clearmodel(char *name) +{ + model **m = models.access(name); + if(!m) { conoutf(CON_WARN, "model %s is not loaded", name); return; } + loopv(mapmodels) if(mapmodels[i].m==*m) mapmodels[i].m = NULL; + models.remove(name); + (*m)->cleanup(); + delete *m; + conoutf("cleared model %s", name); +} + +COMMAND(clearmodel, "s"); + +bool modeloccluded(const vec ¢er, float radius) +{ + ivec bbmin(vec(center).sub(radius)), bbmax(ivec(center).add(radius+1)); + return pvsoccluded(bbmin, bbmax) || bboccluded(bbmin, bbmax); +} + +VAR(showboundingbox, 0, 0, 2); + +void render2dbox(vec &o, float x, float y, float z) +{ + gle::begin(GL_LINE_LOOP); + gle::attribf(o.x, o.y, o.z); + gle::attribf(o.x, o.y, o.z+z); + gle::attribf(o.x+x, o.y+y, o.z+z); + gle::attribf(o.x+x, o.y+y, o.z); + xtraverts += gle::end(); +} + +void render3dbox(vec &o, float tofloor, float toceil, float xradius, float yradius) +{ + if(yradius<=0) yradius = xradius; + vec c = o; + c.sub(vec(xradius, yradius, tofloor)); + float xsz = xradius*2, ysz = yradius*2; + float h = tofloor+toceil; + gle::colorf(1, 1, 1); + gle::defvertex(); + render2dbox(c, xsz, 0, h); + render2dbox(c, 0, ysz, h); + c.add(vec(xsz, ysz, 0)); + render2dbox(c, -xsz, 0, h); + render2dbox(c, 0, -ysz, h); +} + +void renderellipse(vec &o, float xradius, float yradius, float yaw) +{ + gle::colorf(0.5f, 0.5f, 0.5f); + gle::defvertex(); + gle::begin(GL_LINE_LOOP); + loopi(15) + { + const vec2 &sc = sincos360[i*(360/15)]; + gle::attrib(vec(xradius*sc.x, yradius*sc.y, 0).rotate_around_z((yaw+90)*RAD).add(o)); + } + xtraverts += gle::end(); +} + +struct batchedmodel +{ + vec pos, color, dir; + int anim; + float yaw, pitch, transparent; + int basetime, basetime2, flags; + dynent *d; + int attached; + occludequery *query; +}; +struct modelbatch +{ + model *m; + int flags; + vector batched; +}; +static vector batches; +static vector modelattached; +static int numbatches = -1; +static occludequery *modelquery = NULL; + +void startmodelbatches() +{ + numbatches = 0; + modelattached.setsize(0); +} + +modelbatch &addbatchedmodel(model *m) +{ + modelbatch *b = NULL; + if(m->batch>=0 && m->batchbatch]->m==m) b = batches[m->batch]; + else + { + if(numbatchesbatched.setsize(0); + } + else b = batches.add(new modelbatch); + b->m = m; + b->flags = 0; + m->batch = numbatches++; + } + return *b; +} + +void renderbatchedmodel(model *m, batchedmodel &b) +{ + modelattach *a = NULL; + if(b.attached>=0) a = &modelattached[b.attached]; + + int anim = b.anim; + if(shadowmapping) + { + anim |= ANIM_NOSKIN; + GLOBALPARAMF(shadowintensity, b.transparent); + } + else + { + if(b.flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT; + if(b.flags&MDL_GHOST) anim |= ANIM_GHOST; + } + + m->render(anim, b.basetime, b.basetime2, b.pos, b.yaw, b.pitch, b.d, a, b.color, b.dir, b.transparent); +} + +struct transparentmodel +{ + model *m; + batchedmodel *batched; + float dist; +}; + +static inline bool sorttransparentmodels(const transparentmodel &x, const transparentmodel &y) +{ + return x.dist < y.dist; +} + +void endmodelbatches() +{ + vector transparent; + loopi(numbatches) + { + modelbatch &b = *batches[i]; + if(b.batched.empty()) continue; + if(b.flags&(MDL_SHADOW|MDL_DYNSHADOW)) + { + vec center, bbradius; + b.m->boundbox(center, bbradius); + loopvj(b.batched) + { + batchedmodel &bm = b.batched[j]; + if(bm.flags&(MDL_SHADOW|MDL_DYNSHADOW)) + renderblob(bm.flags&MDL_DYNSHADOW ? BLOB_DYNAMIC : BLOB_STATIC, bm.d && bm.d->ragdoll ? bm.d->ragdoll->center : bm.pos, bm.d ? bm.d->radius : max(bbradius.x, bbradius.y), bm.transparent); + } + flushblobs(); + } + bool rendered = false; + occludequery *query = NULL; + if(b.flags&MDL_GHOST) + { + loopvj(b.batched) + { + batchedmodel &bm = b.batched[j]; + if((bm.flags&(MDL_CULL_VFC|MDL_GHOST))!=MDL_GHOST || bm.query) continue; + if(!rendered) { b.m->startrender(); rendered = true; } + renderbatchedmodel(b.m, bm); + } + if(rendered) + { + b.m->endrender(); + rendered = false; + } + } + loopvj(b.batched) + { + batchedmodel &bm = b.batched[j]; + if(bm.flags&(MDL_CULL_VFC|MDL_GHOST)) continue; + if(bm.query!=query) + { + if(query) endquery(query); + query = bm.query; + if(query) startquery(query); + } + if(bm.transparent < 1 && (!query || query->owner==bm.d) && !shadowmapping) + { + transparentmodel &tm = transparent.add(); + tm.m = b.m; + tm.batched = &bm; + tm.dist = camera1->o.dist(bm.d && bm.d->ragdoll ? bm.d->ragdoll->center : bm.pos); + continue; + } + if(!rendered) { b.m->startrender(); rendered = true; } + renderbatchedmodel(b.m, bm); + } + if(query) endquery(query); + if(rendered) b.m->endrender(); + } + if(transparent.length()) + { + transparent.sort(sorttransparentmodels); + model *lastmodel = NULL; + occludequery *query = NULL; + loopv(transparent) + { + transparentmodel &tm = transparent[i]; + if(lastmodel!=tm.m) + { + if(lastmodel) lastmodel->endrender(); + (lastmodel = tm.m)->startrender(); + } + if(query!=tm.batched->query) + { + if(query) endquery(query); + query = tm.batched->query; + if(query) startquery(query); + } + renderbatchedmodel(tm.m, *tm.batched); + } + if(query) endquery(query); + if(lastmodel) lastmodel->endrender(); + } + numbatches = -1; +} + +void startmodelquery(occludequery *query) +{ + modelquery = query; +} + +void endmodelquery() +{ + int querybatches = 0; + loopi(numbatches) + { + modelbatch &b = *batches[i]; + if(b.batched.empty() || b.batched.last().query!=modelquery) continue; + querybatches++; + } + if(querybatches<=1) + { + if(!querybatches) modelquery->fragments = 0; + modelquery = NULL; + return; + } + int minattached = modelattached.length(); + startquery(modelquery); + loopi(numbatches) + { + modelbatch &b = *batches[i]; + if(b.batched.empty() || b.batched.last().query!=modelquery) continue; + b.m->startrender(); + do + { + batchedmodel &bm = b.batched.pop(); + if(bm.attached>=0) minattached = min(minattached, bm.attached); + renderbatchedmodel(b.m, bm); + } + while(b.batched.length() && b.batched.last().query==modelquery); + b.m->endrender(); + } + endquery(modelquery); + modelquery = NULL; + modelattached.setsize(minattached); +} + +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 = NULL; + 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 = NULL, bool shadow = false) +{ + if(flags&MDL_CULL_DIST && center.dist(camera1->o)/radius>maxmodelradiusdistance) return MDL_CULL_DIST; + if(flags&MDL_CULL_VFC) + { + if(reflecting || refracting) + { + if(reflecting || refracting>0) + { + if(center.z+radius<=reflectz) return MDL_CULL_VFC; + } + else + { + if(fogging && center.z+radius=reflectz) return MDL_CULL_VFC; + } + if(center.dist(camera1->o)-radius>reflectdist) return MDL_CULL_VFC; + } + if(isfoggedsphere(radius, center)) return MDL_CULL_VFC; + if(shadowmapping && !isshadowmapcaster(center, radius)) return MDL_CULL_VFC; + } + if(shadowmapping) + { + if(d) + { + if(flags&MDL_CULL_OCCLUDED && d->occluded>=OCCLUDE_PARENT) return MDL_CULL_OCCLUDED; + if(flags&MDL_CULL_QUERY && d->occluded+1>=OCCLUDE_BB && d->query && d->query->owner==d && checkquery(d->query)) return MDL_CULL_QUERY; + } + if(!addshadowmapcaster(center, radius, radius)) return MDL_CULL_VFC; + } + else if(flags&MDL_CULL_OCCLUDED && modeloccluded(center, radius)) + { + if(!reflecting && !refracting && d) d->occluded = OCCLUDE_PARENT; + return MDL_CULL_OCCLUDED; + } + else if(flags&MDL_CULL_QUERY && d->query && d->query->owner==d && checkquery(d->query)) + { + if(!reflecting && !refracting && d->occludedoccluded++; + return MDL_CULL_QUERY; + } + return 0; +} + +void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, float yaw, float pitch, int flags, dynent *d, modelattach *a, int basetime, int basetime2, float trans) +{ + if(shadowmapping && !(flags&(MDL_SHADOW|MDL_DYNSHADOW))) return; + model *m = loadmodel(mdl); + if(!m) return; + vec center(0, 0, 0), bbradius(0, 0, 0); + float radius = 0; + bool shadow = !shadowmap && !glaring && (flags&(MDL_SHADOW|MDL_DYNSHADOW)) && showblobs; + + if(flags&(MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED|MDL_CULL_QUERY|MDL_SHADOW|MDL_DYNSHADOW)) + { + if(flags&MDL_CULL_QUERY) + { + if(!oqfrags || !oqdynent || !d) flags &= ~MDL_CULL_QUERY; + } + + m->boundbox(center, bbradius); + radius = bbradius.magnitude(); + if(d && d->ragdoll) + { + radius = max(radius, d->ragdoll->radius); + center = d->ragdoll->center; + } + else + { + center.rotate_around_z(yaw*RAD); + center.add(o); + } + + int culled = cullmodel(m, center, radius, flags, d, shadow); + if(culled) + { + if(culled&(MDL_CULL_OCCLUDED|MDL_CULL_QUERY) && flags&MDL_CULL_QUERY && !reflecting && !refracting) + { + enablecullmodelquery(); + rendercullmodelquery(m, d, center, radius); + disablecullmodelquery(); + } + return; + } + + if(reflecting || refracting || shadowmapping) flags &= ~MDL_CULL_QUERY; + } + + if(flags&MDL_NORENDER) anim |= ANIM_NORENDER; + else if(showboundingbox && !shadowmapping && !reflecting && !refracting && editmode) + { + notextureshader->set(); + if(d && showboundingbox==1) + { + render3dbox(d->o, d->eyeheight, d->aboveeye, d->radius); + renderellipse(d->o, d->xradius, d->yradius, d->yaw); + } + else + { + vec center, radius; + if(showboundingbox==1) m->collisionbox(center, radius); + else m->boundbox(center, radius); + rotatebb(center, radius, int(yaw)); + center.add(o); + render3dbox(center, radius.z, radius.z, radius.x, radius.y); + } + } + + vec lightcolor(1, 1, 1), lightdir(0, 0, 1); + if(!shadowmapping) + { + vec pos = o; + if(d) + { + if(!reflecting && !refracting) d->occluded = OCCLUDE_NOTHING; + if(!light) light = &d->light; + if(flags&MDL_LIGHT && light->millis!=lastmillis) + { + if(d->ragdoll) + { + pos = d->ragdoll->center; + pos.z += radius/2; + } + else if(d->type < ENT_CAMERA) pos.z += 0.75f*(d->eyeheight + d->aboveeye); + lightreaching(pos, light->color, light->dir, (flags&MDL_LIGHT_FAST)!=0); + dynlightreaching(pos, light->color, light->dir, (flags&MDL_HUD)!=0); + game::lighteffects(d, light->color, light->dir); + light->millis = lastmillis; + } + } + else if(flags&MDL_LIGHT) + { + if(!light) + { + lightreaching(pos, lightcolor, lightdir, (flags&MDL_LIGHT_FAST)!=0); + dynlightreaching(pos, lightcolor, lightdir, (flags&MDL_HUD)!=0); + } + else if(light->millis!=lastmillis) + { + lightreaching(pos, light->color, light->dir, (flags&MDL_LIGHT_FAST)!=0); + dynlightreaching(pos, light->color, light->dir, (flags&MDL_HUD)!=0); + light->millis = lastmillis; + } + } + if(light) { lightcolor = light->color; lightdir = light->dir; } + if(flags&MDL_DYNLIGHT) dynlightreaching(pos, lightcolor, lightdir, (flags&MDL_HUD)!=0); + } + + if(a) for(int i = 0; a[i].tag; i++) + { + if(a[i].name) a[i].m = loadmodel(a[i].name); + //if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL; + } + + if(numbatches>=0) + { + modelbatch &mb = addbatchedmodel(m); + batchedmodel &b = mb.batched.add(); + b.query = modelquery; + b.pos = o; + b.color = lightcolor; + b.dir = lightdir; + b.anim = anim; + b.yaw = yaw; + b.pitch = pitch; + b.basetime = basetime; + b.basetime2 = basetime2; + b.transparent = trans; + b.flags = flags & ~(MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); + if(!shadow || reflecting || refracting>0) + { + b.flags &= ~(MDL_SHADOW|MDL_DYNSHADOW); + if((flags&MDL_CULL_VFC) && refracting<0 && center.z-radius>=reflectz) b.flags |= MDL_CULL_VFC; + } + mb.flags |= b.flags; + 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; } + if(flags&MDL_CULL_QUERY) d->query = b.query = newquery(d); + return; + } + + if(shadow && !reflecting && refracting<=0) + { + renderblob(flags&MDL_DYNSHADOW ? BLOB_DYNAMIC : BLOB_STATIC, d && d->ragdoll ? center : o, d ? d->radius : max(bbradius.x, bbradius.y), trans); + flushblobs(); + if((flags&MDL_CULL_VFC) && refracting<0 && center.z-radius>=reflectz) return; + } + + m->startrender(); + + if(shadowmapping) + { + anim |= ANIM_NOSKIN; + GLOBALPARAMF(shadowintensity, trans); + } + else + { + if(flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT; + if(flags&MDL_GHOST) anim |= ANIM_GHOST; + } + + if(flags&MDL_CULL_QUERY) + { + d->query = newquery(d); + if(d->query) startquery(d->query); + } + + m->render(anim, basetime, basetime2, o, yaw, pitch, d, a, lightcolor, lightdir, trans); + + if(flags&MDL_CULL_QUERY && d->query) endquery(d->query); + + m->endrender(); +} + +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; +} + +void findanims(const char *pattern, vector &anims) +{ + loopi(sizeof(animnames)/sizeof(animnames[0])) if(matchanim(animnames[i], pattern)) anims.add(i); +} + +ICOMMAND(findanims, "s", (char *name), +{ + vector anims; + 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()); +}); + +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, "packages/models/%s", dir); + defformatstring(maltdir, "packages/models/%s", altdir); + masks = notexture; + tryload(skin, NULL, NULL, "skin"); + tryload(masks, NULL, NULL, "masks"); +} + +// convenient function that covers the usual anims for players/monsters/npcs + +VAR(animoverride, -1, 0, NUMANIMS-1); +VAR(testanims, 0, 0, 1); +VAR(testpitch, -90, 0, 90); + +void renderclient(dynent *d, const char *mdlname, modelattach *attachments, int hold, int attack, int attackdelay, int lastaction, int lastpain, float fade, bool ragdoll) +{ + int anim = hold ? hold : ANIM_IDLE|ANIM_LOOP; + float yaw = testanims && d==player ? 0 : d->yaw+90, + pitch = testpitch && d==player ? testpitch : d->pitch; + vec o = d->feetpos(); + int basetime = 0; + if(animoverride) anim = (animoverride<0 ? ANIM_ALL : animoverride)|ANIM_LOOP; + else if(d->state==CS_DEAD) + { + anim = ANIM_DYING|ANIM_NOPITCH; + basetime = lastpain; + if(ragdoll) + { + if(!d->ragdoll || d->ragdoll->millis < basetime) + { + DELETEP(d->ragdoll); + anim |= ANIM_RAGDOLL; + } + } + else if(lastmillis-basetime>1000) anim = ANIM_DEAD|ANIM_LOOP|ANIM_NOPITCH; + } + else if(d->state==CS_EDITING || d->state==CS_SPECTATOR) anim = ANIM_EDIT|ANIM_LOOP; + else if(d->state==CS_LAGGED) anim = ANIM_LAG|ANIM_LOOP; + else + { + if(lastmillis-lastpain < 300) + { + anim = ANIM_PAIN; + basetime = lastpain; + } + else if(lastpain < lastaction && (attack < 0 || (d->type != ENT_AI && lastmillis-lastaction < attackdelay))) + { + anim = attack < 0 ? -attack : attack; + basetime = lastaction; + } + + if(d->inwater && d->physstate<=PHYS_FALL) anim |= (((game::allowmove(d) && (d->move || d->strafe)) || d->vel.z+d->falling.z>0 ? ANIM_SWIM : ANIM_SINK)|ANIM_LOOP)<timeinair>100) anim |= (ANIM_JUMP|ANIM_END)<move || d->strafe)) + { + if(d->move>0) anim |= (ANIM_FORWARD|ANIM_LOOP)<strafe) + { + if(d->move<0) anim |= ((d->strafe>0 ? ANIM_RIGHT : ANIM_LEFT)|ANIM_REVERSE|ANIM_LOOP)<strafe>0 ? ANIM_LEFT : ANIM_RIGHT)|ANIM_LOOP)<move<0) anim |= (ANIM_BACKWARD|ANIM_LOOP)<>ANIM_SECONDARY)&ANIM_INDEX) anim >>= ANIM_SECONDARY; + } + if(d->ragdoll && (!ragdoll || (anim&ANIM_INDEX)!=ANIM_DYING)) DELETEP(d->ragdoll); + if(!((anim>>ANIM_SECONDARY)&ANIM_INDEX)) anim |= (ANIM_IDLE|ANIM_LOOP)<type==ENT_PLAYER) flags |= MDL_FULLBRIGHT; + else flags |= MDL_CULL_DIST; + if(d->state==CS_LAGGED) fade = min(fade, 0.3f); + else flags |= MDL_DYNSHADOW; + if(drawtex == DRAWTEX_MODELPREVIEW) flags &= ~(MDL_LIGHT | MDL_FULLBRIGHT | MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY | MDL_CULL_DIST | MDL_DYNSHADOW); + rendermodel(NULL, mdlname, anim, o, yaw, pitch, flags, d, attachments, basetime, 0, fade); +} + +void setbbfrommodel(dynent *d, const char *mdl) +{ + model *m = loadmodel(mdl); + if(!m) return; + vec center, radius; + m->collisionbox(center, radius); + if(d->type==ENT_INANIMATE && !m->ellipsecollide) + 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) : 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; + } +} + -- cgit v1.2.3