LCOV - code coverage report
Current view: top level - engine/model - animmodel.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 15.3 % 1046 160
Test Date: 2026-03-06 06:39:57 Functions: 32.7 % 113 37

            Line data    Source code
       1              : /**
       2              :  * @brief implementation for animated models
       3              :  *
       4              :  * animmodel.cpp implements the animmodel object in animmodel.h and is the
       5              :  * working form of an an animated model loaded from a file. The animmodel object
       6              :  * supports skeletal animation along with the basics it inherits from model.h;
       7              :  * see that file for non-animated functionality (e.g. normal, specular, etc mapping,
       8              :  * position, orientation, etc).
       9              :  *
      10              :  */
      11              : #include "../libprimis-headers/cube.h"
      12              : #include "../../shared/geomexts.h"
      13              : #include "../../shared/glemu.h"
      14              : #include "../../shared/glexts.h"
      15              : 
      16              : #include <memory>
      17              : #include <optional>
      18              : 
      19              : #include "interface/console.h"
      20              : #include "interface/control.h"
      21              : 
      22              : #include "render/radiancehints.h"
      23              : #include "render/rendergl.h"
      24              : #include "render/renderlights.h"
      25              : #include "render/rendermodel.h"
      26              : #include "render/renderparticles.h"
      27              : #include "render/shader.h"
      28              : #include "render/shaderparam.h"
      29              : #include "render/texture.h"
      30              : 
      31              : #include "world/entities.h"
      32              : #include "world/bih.h"
      33              : 
      34              : #include "model.h"
      35              : #include "ragdoll.h"
      36              : #include "animmodel.h"
      37              : 
      38              : //animmodel
      39              : static VARP(fullbrightmodels, 0, 0, 200); //sets minimum amount of brightness for a model: 200 is 100% brightness
      40              : VAR(testtags, 0, 0, 1);            //not used by animmodel object, used by children vert/skelmodel
      41            0 : VARF(debugcolmesh, 0, 0, 1,
      42              : {
      43              :     cleanupmodels();
      44              : });
      45              : 
      46              : static VAR(animationinterpolationtime, 0, 200, 1000);
      47              : 
      48              : std::unordered_map<std::string, animmodel::meshgroup *> animmodel::meshgroups;
      49              : 
      50              : bool animmodel::enabletc = false,
      51              :      animmodel::enabletangents = false,
      52              :      animmodel::enablebones = false,
      53              :      animmodel::enablecullface = true,
      54              :      animmodel::enabledepthoffset = false;
      55              : 
      56              : std::stack<matrix4> animmodel::matrixstack;
      57              : float animmodel::sizescale = 1;
      58              : 
      59              : vec4<float> animmodel::colorscale(1, 1, 1, 1);
      60              : 
      61              : GLuint animmodel::lastvbuf = 0,
      62              :        animmodel::lasttcbuf = 0,
      63              :        animmodel::lastxbuf = 0,
      64              :        animmodel::lastbbuf = 0,
      65              :        animmodel::lastebuf = 0;
      66              : 
      67              : const Texture *animmodel::lasttex = nullptr,
      68              :               *animmodel::lastdecal = nullptr,
      69              :               *animmodel::lastmasks = nullptr,
      70              :               *animmodel::lastnormalmap = nullptr;
      71              : 
      72              : template <>
      73              : struct std::hash<animmodel::shaderparams>
      74              : {
      75           11 :     size_t operator()(const animmodel::shaderparams &k) const
      76              :     {
      77           11 :         return memhash(&k, sizeof(k));
      78              :     }
      79              : };
      80              : 
      81              : std::unordered_map<animmodel::shaderparams, animmodel::skin::ShaderParamsKey> animmodel::skin::ShaderParamsKey::keys;
      82              : 
      83              : int animmodel::skin::ShaderParamsKey::firstversion = 0,
      84              :     animmodel::skin::ShaderParamsKey::lastversion = 1;
      85              : 
      86              : //animmodel
      87              : 
      88           19 : animmodel::animmodel(std::string name) : model(std::move(name))
      89              : {
      90           19 : }
      91              : 
      92           19 : animmodel::~animmodel()
      93              : {
      94           38 :     for(part * i : parts)
      95              :     {
      96           19 :         delete i;
      97              :     }
      98           19 :     parts.clear();
      99           19 : }
     100              : 
     101              : // AnimPos
     102              : 
     103            0 : void animmodel::AnimPos::setframes(const animinfo &info)
     104              : {
     105            0 :     anim = info.anim;
     106            0 :     if(info.range<=1)
     107              :     {
     108            0 :         fr1 = 0;
     109            0 :         t = 0;
     110              :     }
     111              :     else
     112              :     {
     113            0 :         int time = info.anim & Anim_SetTime ? info.basetime : lastmillis - info.basetime;
     114            0 :         fr1 = static_cast<int>(time/info.speed); // round to full frames
     115            0 :         t = (time-fr1*info.speed)/info.speed; // progress of the frame, value from 0.0f to 1.0f
     116              :     }
     117            0 :     if(info.anim & Anim_Loop)
     118              :     {
     119            0 :         fr1 = fr1%info.range+info.frame;
     120            0 :         fr2 = fr1+1;
     121            0 :         if(fr2>=info.frame+info.range)
     122              :         {
     123            0 :             fr2 = info.frame;
     124              :         }
     125              :     }
     126              :     else
     127              :     {
     128            0 :         fr1 = std::min(fr1, info.range-1)+info.frame;
     129            0 :         fr2 = std::min(fr1+1, info.frame+info.range-1);
     130              :     }
     131            0 :     if(info.anim & Anim_Reverse)
     132              :     {
     133            0 :         fr1 = (info.frame+info.range-1)-(fr1-info.frame);
     134            0 :         fr2 = (info.frame+info.range-1)-(fr2-info.frame);
     135              :     }
     136            0 : }
     137              : 
     138            6 : bool animmodel::AnimPos::operator==(const AnimPos &a) const
     139              : {
     140            6 :     return fr1==a.fr1 && fr2==a.fr2 && (fr1==fr2 || t==a.t);
     141              : }
     142            6 : bool animmodel::AnimPos::operator!=(const AnimPos &a) const
     143              : {
     144            6 :     return fr1!=a.fr1 || fr2!=a.fr2 || (fr1!=fr2 && t!=a.t);
     145              : }
     146              : 
     147              : // AnimState
     148              : 
     149            3 : bool animmodel::AnimState::operator==(const AnimState &a) const
     150              : {
     151            3 :     return cur==a.cur && (interp<1 ? interp==a.interp && prev==a.prev : a.interp>=1);
     152              : }
     153            3 : bool animmodel::AnimState::operator!=(const AnimState &a) const
     154              : {
     155            3 :     return cur!=a.cur || (interp<1 ? interp!=a.interp || prev!=a.prev : a.interp<1);
     156              : }
     157              : 
     158              : //ShaderParams
     159              : 
     160           15 : bool animmodel::shaderparams::operator==(const animmodel::shaderparams &y) const
     161              : {
     162           15 :     return spec == y.spec
     163           14 :         && gloss == y.gloss
     164           14 :         && glow == y.glow
     165           14 :         && glowdelta == y.glowdelta
     166           14 :         && glowpulse == y.glowpulse
     167           14 :         && fullbright == y.fullbright
     168           13 :         && scrollu == y.scrollu
     169           13 :         && scrollv == y.scrollv
     170           13 :         && alphatest == y.alphatest
     171           29 :         && color == y.color;
     172              : }
     173              : 
     174              : // ShaderParamsKey
     175              : 
     176            0 : bool animmodel::skin::ShaderParamsKey::checkversion()
     177              : {
     178            0 :     if(version >= firstversion)
     179              :     {
     180            0 :         return true;
     181              :     }
     182            0 :     version = lastversion;
     183            0 :     if(++lastversion <= 0)
     184              :     {
     185            0 :         for(auto &[k, t] : keys)
     186              :         {
     187            0 :             t.version = -1;
     188              :         }
     189            0 :         firstversion = 0;
     190            0 :         lastversion = 1;
     191            0 :         version = 0;
     192              :     }
     193            0 :     return false;
     194              : }
     195              : 
     196           31 : animmodel::shaderparams::shaderparams() : spec(1.0f), gloss(1), glow(3.0f), glowdelta(0), glowpulse(0), fullbright(0), scrollu(0), scrollv(0), alphatest(0.9f), color(1, 1, 1)
     197              : {
     198           31 : }
     199              : 
     200              : //skin
     201              : 
     202            0 : bool animmodel::skin::masked() const
     203              : {
     204            0 :     return masks != notexture;
     205              : }
     206              : 
     207            0 : bool animmodel::skin::bumpmapped() const
     208              : {
     209            0 :     return normalmap != nullptr;
     210              : }
     211              : 
     212            1 : bool animmodel::skin::alphatested() const
     213              : {
     214            1 :     return alphatest > 0 && tex->type&Texture::ALPHA;
     215              : }
     216              : 
     217            0 : bool animmodel::skin::decaled() const
     218              : {
     219            0 :     return decal != nullptr;
     220              : }
     221              : 
     222           11 : void animmodel::skin::setkey()
     223              : {
     224           11 :     key = &ShaderParamsKey::keys[*this];
     225           11 : }
     226              : 
     227            0 : void animmodel::skin::setshaderparams(const AnimState *as, bool skinned)
     228              : {
     229            0 :     if(!Shader::lastshader)
     230              :     {
     231            0 :         return;
     232              :     }
     233            0 :     if(key->checkversion() && Shader::lastshader->owner == key)
     234              :     {
     235            0 :         return;
     236              :     }
     237            0 :     Shader::lastshader->owner = key;
     238              : 
     239            0 :     LOCALPARAMF(texscroll, scrollu*lastmillis/1000.0f, scrollv*lastmillis/1000.0f);
     240            0 :     if(alphatested())
     241              :     {
     242            0 :         LOCALPARAMF(alphatest, alphatest);
     243              :     }
     244              : 
     245            0 :     if(!skinned)
     246              :     {
     247            0 :         return;
     248              :     }
     249              : 
     250            0 :     if(color.r() < 0)
     251              :     {
     252            0 :         LOCALPARAM(colorscale, colorscale);
     253              :     }
     254              :     else
     255              :     {
     256            0 :         LOCALPARAMF(colorscale, color.r(), color.g(), color.b(), colorscale.a());
     257              :     }
     258            0 :     if(fullbright)
     259              :     {
     260            0 :         LOCALPARAMF(fullbright, 0.0f, fullbright);
     261              :     }
     262              :     else
     263              :     {
     264            0 :         LOCALPARAMF(fullbright, 1.0f, as->cur.anim & Anim_FullBright ? 0.5f * fullbrightmodels / 100.0f : 0.0f);
     265              :     }
     266            0 :     float curglow = glow;
     267            0 :     if(glowpulse > 0)
     268              :     {
     269            0 :         float curpulse = lastmillis*glowpulse;
     270            0 :         curpulse -= std::floor(curpulse);
     271            0 :         curglow += glowdelta*2*std::fabs(curpulse - 0.5f);
     272              :     }
     273            0 :     LOCALPARAMF(maskscale, spec, gloss, curglow);
     274              : }
     275              : 
     276            0 : Shader *animmodel::skin::loadshader()
     277              : {
     278            0 :     if(shadowmapping == ShadowMap_Reflect)
     279              :     {
     280            0 :         if(rsmshader)
     281              :         {
     282            0 :             return rsmshader;
     283              :         }
     284            0 :         std::string opts;
     285            0 :         if(alphatested())
     286              :         {
     287            0 :             opts.push_back('a');
     288              :         }
     289            0 :         if(!cullface)
     290              :         {
     291            0 :             opts.push_back('c');
     292              :         }
     293              : 
     294            0 :         std::string name = std::string("rsmmodel").append(opts);
     295            0 :         rsmshader = generateshader(name, "rsmmodelshader \"%s\"", opts.c_str());
     296            0 :         return rsmshader;
     297            0 :     }
     298            0 :     if(shader)
     299              :     {
     300            0 :         return shader;
     301              :     }
     302            0 :     std::string opts;
     303            0 :     if(alphatested())
     304              :     {
     305            0 :         opts.push_back('a');
     306              :     }
     307            0 :     if(decaled())
     308              :     {
     309            0 :         opts.push_back(decal->type&Texture::ALPHA ? 'D' : 'd');
     310              :     }
     311            0 :     if(bumpmapped())
     312              :     {
     313            0 :         opts.push_back('n');
     314              :     }
     315            0 :     else if(masked())
     316              :     {
     317            0 :         opts.push_back('m');
     318              :     }
     319            0 :     if(!cullface)
     320              :     {
     321            0 :         opts.push_back('c');
     322              :     }
     323              : 
     324            0 :     std::string name = std::string("model").append(opts);
     325            0 :     shader = generateshader(name, "modelshader \"%s\"", opts.c_str());
     326            0 :     return shader;
     327            0 : }
     328              : 
     329            2 : void animmodel::skin::cleanup()
     330              : {
     331            2 :     if(shader && shader->standard)
     332              :     {
     333            0 :         shader = nullptr;
     334              :     }
     335            2 : }
     336              : 
     337            1 : void animmodel::skin::preloadBIH() const
     338              : {
     339            1 :     if(alphatested() && !tex->alphamask)
     340              :     {
     341            0 :         tex->loadalphamask();
     342              :     }
     343            1 : }
     344              : 
     345            0 : void animmodel::skin::preloadshader()
     346              : {
     347            0 :     loadshader();
     348            0 :     useshaderbyname(alphatested() && owner->model->alphashadow ? "alphashadowmodel" : "shadowmodel");
     349            0 :     if(useradiancehints())
     350              :     {
     351            0 :         useshaderbyname(alphatested() ? "rsmalphamodel" : "rsmmodel");
     352              :     }
     353            0 : }
     354              : 
     355            0 : void animmodel::skin::setshader(Mesh &m, bool usegpuskel, int vweights)
     356              : {
     357            0 :     m.setshader(loadshader(), usegpuskel, vweights, gbuf.istransparentlayer());
     358            0 : }
     359              : 
     360            0 : void animmodel::skin::bind(Mesh &b, const AnimState *as, bool usegpuskel, int vweights)
     361              : {
     362            0 :     if(cullface > 0)
     363              :     {
     364            0 :         if(!enablecullface)
     365              :         {
     366            0 :             glEnable(GL_CULL_FACE);
     367            0 :             enablecullface = true;
     368              :         }
     369              :     }
     370            0 :     else if(enablecullface)
     371              :     {
     372            0 :         glDisable(GL_CULL_FACE);
     373            0 :         enablecullface = false;
     374              :     }
     375              : 
     376            0 :     if(as->cur.anim & Anim_NoSkin)
     377              :     {
     378            0 :         if(alphatested() && owner->model->alphashadow)
     379              :         {
     380            0 :             if(tex!=lasttex)
     381              :             {
     382            0 :                 glBindTexture(GL_TEXTURE_2D, tex->id);
     383            0 :                 lasttex = tex;
     384              :             }
     385              :             static Shader *alphashadowmodelshader = nullptr;
     386            0 :             if(!alphashadowmodelshader)
     387              :             {
     388            0 :                 alphashadowmodelshader = useshaderbyname("alphashadowmodel");
     389              :             }
     390            0 :             b.setshader(alphashadowmodelshader, usegpuskel, vweights);
     391            0 :             setshaderparams(as, false);
     392              :         }
     393              :         else
     394              :         {
     395              :             static Shader *shadowmodelshader = nullptr;
     396            0 :             if(!shadowmodelshader)
     397              :             {
     398            0 :                 shadowmodelshader = useshaderbyname("shadowmodel");
     399              :             }
     400            0 :             b.setshader(shadowmodelshader, usegpuskel, vweights);
     401              :         }
     402            0 :         return;
     403              :     }
     404            0 :     int activetmu = 0;
     405            0 :     if(tex!=lasttex)
     406              :     {
     407            0 :         glBindTexture(GL_TEXTURE_2D, tex->id);
     408            0 :         lasttex = tex;
     409              :     }
     410            0 :     if(bumpmapped() && normalmap!=lastnormalmap)
     411              :     {
     412            0 :         glActiveTexture(GL_TEXTURE3);
     413            0 :         activetmu = 3;
     414            0 :         glBindTexture(GL_TEXTURE_2D, normalmap->id);
     415            0 :         lastnormalmap = normalmap;
     416              :     }
     417            0 :     if(decaled() && decal!=lastdecal)
     418              :     {
     419            0 :         glActiveTexture(GL_TEXTURE4);
     420            0 :         activetmu = 4;
     421            0 :         glBindTexture(GL_TEXTURE_2D, decal->id);
     422            0 :         lastdecal = decal;
     423              :     }
     424            0 :     if(masked() && masks!=lastmasks)
     425              :     {
     426            0 :         glActiveTexture(GL_TEXTURE1);
     427            0 :         activetmu = 1;
     428            0 :         glBindTexture(GL_TEXTURE_2D, masks->id);
     429            0 :         lastmasks = masks;
     430              :     }
     431            0 :     if(activetmu != 0)
     432              :     {
     433            0 :         glActiveTexture(GL_TEXTURE0);
     434              :     }
     435            0 :     setshader(b, usegpuskel, vweights);
     436            0 :     setshaderparams(as);
     437              : }
     438              : 
     439            0 : void animmodel::skin::invalidateshaderparams()
     440              : {
     441            0 :     ShaderParamsKey::invalidate();
     442            0 : }
     443              : 
     444              : //Mesh
     445              : 
     446            0 : void animmodel::Mesh::genBIH(const skin &s, std::vector<BIH::mesh> &bih, const matrix4x3 &t) const
     447              : {
     448            0 :     bih.emplace_back();
     449            0 :     BIH::mesh &m = bih.back();
     450            0 :     m.xform = t;
     451            0 :     m.tex = s.tex;
     452            0 :     if(canrender)
     453              :     {
     454            0 :         m.flags |= BIH::Mesh_Render;
     455              :     }
     456            0 :     if(cancollide)
     457              :     {
     458            0 :         m.flags |= BIH::Mesh_Collide;
     459              :     }
     460            0 :     if(s.alphatested())
     461              :     {
     462            0 :         m.flags |= BIH::Mesh_Alpha;
     463              :     }
     464            0 :     if(noclip)
     465              :     {
     466            0 :         m.flags |= BIH::Mesh_NoClip;
     467              :     }
     468            0 :     if(s.cullface > 0)
     469              :     {
     470            0 :         m.flags |= BIH::Mesh_CullFace;
     471              :     }
     472            0 :     genBIH(m);
     473            0 :     while(bih.back().numtris > BIH::mesh::maxtriangles)
     474              :     {
     475            0 :         bih.push_back(bih.back());
     476            0 :         BIH::mesh &overflow = bih.back();
     477            0 :         overflow.tris += BIH::mesh::maxtriangles;
     478            0 :         overflow.numtris -= BIH::mesh::maxtriangles;
     479            0 :         bih[bih.size()-2].numtris = BIH::mesh::maxtriangles;
     480              :     }
     481            0 : }
     482              : 
     483          438 : void animmodel::Mesh::fixqtangent(quat &q, float bt)
     484              : {
     485              :     static constexpr float bias = -1.5f/65535;
     486              :     static const float biasscale = sqrtf(1 - bias*bias); //cannot be constexpr, sqrtf is not compile time
     487          438 :     if(bt < 0)
     488              :     {
     489           23 :         if(q.w >= 0)
     490              :         {
     491           21 :             q.neg();
     492              :         }
     493           23 :         if(q.w > bias)
     494              :         {
     495            0 :             q.mul3(biasscale);
     496            0 :             q.w = bias;
     497              :         }
     498              :     }
     499          415 :     else if(q.w < 0)
     500              :     {
     501           71 :         q.neg();
     502              :     }
     503          438 : }
     504              : 
     505              : //meshgroup
     506              : 
     507            3 : animmodel::meshgroup::meshgroup()
     508              : {
     509            3 : }
     510              : 
     511            2 : animmodel::meshgroup::~meshgroup()
     512              : {
     513            2 :     for(Mesh * i : meshes)
     514              :     {
     515            0 :         delete i;
     516              :     }
     517            2 :     meshes.clear();
     518            2 : }
     519              : 
     520            0 : std::vector<std::vector<animmodel::Mesh *>::const_iterator> animmodel::meshgroup::getmeshes(std::string_view meshname) const
     521              : {
     522            0 :     std::vector<std::vector<animmodel::Mesh *>::const_iterator> meshlist;
     523            0 :     for(std::vector<animmodel::Mesh *>::const_iterator i = meshes.begin(); i != meshes.end(); ++i)
     524              :     {
     525            0 :         const animmodel::Mesh &tempmesh = **i;
     526            0 :         if((meshname == "*") || (tempmesh.name.size() && (tempmesh.name == meshname)))
     527              :         {
     528            0 :             meshlist.push_back(i);
     529              :         }
     530              :     }
     531            0 :     return meshlist;
     532            0 : }
     533              : 
     534            8 : std::vector<size_t> animmodel::meshgroup::getskins(std::string_view meshname) const
     535              : {
     536            8 :     std::vector<size_t> skinlist;
     537           16 :     for(size_t i = 0; i < meshes.size(); i++)
     538              :     {
     539            8 :         const animmodel::Mesh &m = *(meshes[i]);
     540            8 :         if((meshname == "*") || (m.name.size() && (m.name == meshname)))
     541              :         {
     542            8 :             skinlist.push_back(i);
     543              :         }
     544              :     }
     545            8 :     return skinlist;
     546            0 : }
     547              : 
     548            0 : void animmodel::meshgroup::calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &t) const
     549              : {
     550            0 :     auto rendermeshes = getrendermeshes();
     551            0 :     for(auto i : rendermeshes)
     552              :     {
     553            0 :         (*i)->calcbb(bbmin, bbmax, t);
     554              :     }
     555            0 : }
     556              : 
     557            0 : void animmodel::meshgroup::genBIH(const std::vector<skin> &skins, std::vector<BIH::mesh> &bih, const matrix4x3 &t) const
     558              : {
     559            0 :     for(size_t i = 0; i < meshes.size(); i++)
     560              :     {
     561            0 :         meshes[i]->genBIH(skins[i], bih, t);
     562              :     }
     563            0 : }
     564              : 
     565            0 : void animmodel::meshgroup::genshadowmesh(std::vector<triangle> &tris, const matrix4x3 &t) const
     566              : {
     567            0 :     auto rendermeshes = getrendermeshes();
     568            0 :     for(auto i : rendermeshes)
     569              :     {
     570            0 :         (*i)->genshadowmesh(tris, t);
     571              :     }
     572            0 : }
     573              : 
     574            0 : bool animmodel::meshgroup::hasframe(int i) const
     575              : {
     576            0 :     return i>=0 && i<totalframes();
     577              : }
     578              : 
     579            2 : bool animmodel::meshgroup::hasframes(int i, int n) const
     580              : {
     581            2 :     return i>=0 && i+n<=totalframes();
     582              : }
     583              : 
     584            0 : int animmodel::meshgroup::clipframes(int i, int n) const
     585              : {
     586            0 :     return std::min(n, totalframes() - i);
     587              : }
     588              : 
     589            1 : const std::string &animmodel::meshgroup::groupname() const
     590              : {
     591            1 :     return name;
     592              : }
     593              : 
     594            0 : std::vector<std::vector<animmodel::Mesh *>::const_iterator> animmodel::meshgroup::getrendermeshes() const
     595              : {
     596            0 :     std::vector<std::vector<animmodel::Mesh *>::const_iterator> rendermeshes;
     597            0 :     for(std::vector<animmodel::Mesh *>::const_iterator i = meshes.begin(); i != meshes.end(); ++i)
     598              :     {
     599            0 :         if((*i)->canrender || debugcolmesh)
     600              :         {
     601            0 :             rendermeshes.push_back(i);
     602              :         }
     603              :     }
     604            0 :     return rendermeshes;
     605            0 : }
     606              : 
     607              : //identical to above but non-const iterator and non const this
     608            1 : std::vector<std::vector<animmodel::Mesh *>::iterator> animmodel::meshgroup::getrendermeshes()
     609              : {
     610            1 :     std::vector<std::vector<animmodel::Mesh *>::iterator> rendermeshes;
     611            2 :     for(std::vector<animmodel::Mesh *>::iterator i = meshes.begin(); i != meshes.end(); ++i)
     612              :     {
     613            1 :         if((*i)->canrender || debugcolmesh)
     614              :         {
     615            1 :             rendermeshes.push_back(i);
     616              :         }
     617              :     }
     618            1 :     return rendermeshes;
     619            0 : }
     620              : 
     621            0 : void animmodel::meshgroup::bindpos(GLuint ebuf, GLuint vbuf, const void *v, int stride, int type, int size)
     622              : {
     623            0 :     if(lastebuf!=ebuf)
     624              :     {
     625            0 :         gle::bindebo(ebuf);
     626            0 :         lastebuf = ebuf;
     627              :     }
     628            0 :     if(lastvbuf!=vbuf)
     629              :     {
     630            0 :         gle::bindvbo(vbuf);
     631            0 :         if(!lastvbuf)
     632              :         {
     633            0 :             gle::enablevertex();
     634              :         }
     635            0 :         gle::vertexpointer(stride, v, type, size);
     636            0 :         lastvbuf = vbuf;
     637              :     }
     638            0 : }
     639            0 : void animmodel::meshgroup::bindpos(GLuint ebuf, GLuint vbuf, const vec *v, int stride)
     640              : {
     641            0 :     bindpos(ebuf, vbuf, v, stride, GL_FLOAT, 3);
     642            0 : }
     643              : 
     644            0 : void animmodel::meshgroup::bindpos(GLuint ebuf, GLuint vbuf, const vec4<half> *v, int stride)
     645              : {
     646            0 :     bindpos(ebuf, vbuf, v, stride, GL_HALF_FLOAT, 4);
     647            0 : }
     648              : 
     649            0 : void animmodel::meshgroup::bindtc(const void *v, int stride)
     650              : {
     651            0 :     if(!enabletc)
     652              :     {
     653            0 :         gle::enabletexcoord0();
     654            0 :         enabletc = true;
     655              :     }
     656            0 :     if(lasttcbuf!=lastvbuf)
     657              :     {
     658            0 :         gle::texcoord0pointer(stride, v, GL_HALF_FLOAT);
     659            0 :         lasttcbuf = lastvbuf;
     660              :     }
     661            0 : }
     662              : 
     663            0 : void animmodel::meshgroup::bindtangents(const void *v, int stride)
     664              : {
     665            0 :     if(!enabletangents)
     666              :     {
     667            0 :         gle::enabletangent();
     668            0 :         enabletangents = true;
     669              :     }
     670            0 :     if(lastxbuf!=lastvbuf)
     671              :     {
     672            0 :         gle::tangentpointer(stride, v, GL_SHORT);
     673            0 :         lastxbuf = lastvbuf;
     674              :     }
     675            0 : }
     676              : 
     677            0 : void animmodel::meshgroup::bindbones(const void *wv, const void *bv, int stride)
     678              : {
     679            0 :     if(!enablebones)
     680              :     {
     681            0 :         gle::enableboneweight();
     682            0 :         gle::enableboneindex();
     683            0 :         enablebones = true;
     684              :     }
     685            0 :     if(lastbbuf!=lastvbuf)
     686              :     {
     687            0 :         gle::boneweightpointer(stride, wv);
     688            0 :         gle::boneindexpointer(stride, bv);
     689            0 :         lastbbuf = lastvbuf;
     690              :     }
     691            0 : }
     692              : 
     693              : //part
     694              : 
     695           19 : animmodel::part::part(const animmodel *model, int index) : model(model), index(index), meshes(nullptr), numanimparts(1), pitchscale(1), pitchoffset(0), pitchmin(0), pitchmax(0)
     696              : {
     697           76 :     for(int i = 0; i < maxanimparts; ++i)
     698              :     {
     699           57 :         anims[i] = nullptr;
     700              :     }
     701           19 : }
     702              : 
     703           19 : animmodel::part::~part()
     704              : {
     705           76 :     for(int i = 0; i < maxanimparts; ++i)
     706              :     {
     707           59 :         delete[] anims[i];
     708              :     }
     709           19 : }
     710              : 
     711            0 : void animmodel::part::cleanup()
     712              : {
     713            0 :     if(meshes)
     714              :     {
     715            0 :         meshes->cleanup();
     716              :     }
     717            0 :     for(skin &i : skins)
     718              :     {
     719            0 :         i.cleanup();
     720              :     }
     721            0 : }
     722              : 
     723           11 : void animmodel::part::disablepitch()
     724              : {
     725           11 :     pitchscale = pitchoffset = pitchmin = pitchmax = 0;
     726           11 : }
     727              : 
     728            0 : void animmodel::part::calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m, float modelscale) const
     729              : {
     730            0 :     matrix4x3 t = m;
     731            0 :     t.scale(modelscale);
     732            0 :     meshes->calcbb(bbmin, bbmax, t);
     733            0 :     for(const linkedpart &i : links)
     734              :     {
     735            0 :         matrix4x3 n;
     736            0 :         meshes->concattagtransform(i.tag, m, n);
     737            0 :         n.translate(i.translate, modelscale);
     738            0 :         i.p->calcbb(bbmin, bbmax, n, modelscale);
     739              :     }
     740            0 : }
     741              : 
     742            0 : void animmodel::part::genBIH(std::vector<BIH::mesh> &bih, const matrix4x3 &m, float modelscale) const
     743              : {
     744            0 :     matrix4x3 t = m;
     745            0 :     t.scale(modelscale);
     746            0 :     meshes->genBIH(skins, bih, t);
     747            0 :     for(const linkedpart &i : links)
     748              :     {
     749            0 :         matrix4x3 n;
     750            0 :         meshes->concattagtransform(i.tag, m, n);
     751            0 :         n.translate(i.translate, modelscale);
     752            0 :         i.p->genBIH(bih, n, modelscale);
     753              :     }
     754            0 : }
     755              : 
     756            0 : void animmodel::part::genshadowmesh(std::vector<triangle> &tris, const matrix4x3 &m, float modelscale) const
     757              : {
     758            0 :     matrix4x3 t = m;
     759            0 :     t.scale(modelscale);
     760            0 :     meshes->genshadowmesh(tris, t);
     761            0 :     for(const linkedpart &i : links)
     762              :     {
     763            0 :         matrix4x3 n;
     764            0 :         meshes->concattagtransform(i.tag, m, n);
     765            0 :         n.translate(i.translate, modelscale);
     766            0 :         i.p->genshadowmesh(tris, n, modelscale);
     767              :     }
     768            0 : }
     769              : 
     770            0 : bool animmodel::part::link(part *p, std::string_view tag, const vec &translate, int anim, int basetime, vec *pos)
     771              : {
     772            0 :     std::optional<size_t> i = meshes ? meshes->findtag(tag) : std::nullopt;
     773            0 :     if(i<0)
     774              :     {
     775            0 :         for(const linkedpart &i : links)
     776              :         {
     777            0 :             if(i.p && i.p->link(p, tag, translate, anim, basetime, pos))
     778              :             {
     779            0 :                 return true;
     780              :             }
     781              :         }
     782            0 :         return false;
     783              :     }
     784            0 :     links.emplace_back(p, *i, anim, basetime, translate, pos, matrix4());
     785            0 :     return true;
     786              : }
     787              : 
     788            0 : bool animmodel::part::unlink(const part *p)
     789              : {
     790            0 :     for(int i = links.size(); --i >=0;) //note reverse iteration
     791              :     {
     792            0 :         if(links[i].p==p)
     793              :         {
     794            0 :             links.erase(links.begin() + i);
     795            0 :             return true;
     796              :         }
     797              :     }
     798            0 :     for(const linkedpart &i : links)
     799              :     {
     800            0 :         if(i.p && i.p->unlink(p))
     801              :         {
     802            0 :             return true;
     803              :         }
     804              :     }
     805            0 :     return false;
     806              : }
     807              : 
     808           21 : void animmodel::part::initskins(Texture *tex, Texture *masks, uint limit)
     809              : {
     810           21 :     if(!limit)
     811              :     {
     812           20 :         if(!meshes)
     813              :         {
     814            0 :             return;
     815              :         }
     816           20 :         limit = meshes->meshes.size();
     817              :     }
     818           38 :     while(skins.size() < limit)
     819              :     {
     820           17 :         skins.emplace_back(this, tex, masks);
     821              :     }
     822              : }
     823              : 
     824            0 : bool animmodel::part::alphatested() const
     825              : {
     826            0 :     for(const skin &i : skins)
     827              :     {
     828            0 :         if(i.alphatested())
     829              :         {
     830            0 :             return true;
     831              :         }
     832              :     }
     833            0 :     return false;
     834              : }
     835              : 
     836            1 : void animmodel::part::preloadBIH() const
     837              : {
     838            2 :     for(const skin &i : skins)
     839              :     {
     840            1 :         i.preloadBIH();
     841              :     }
     842            1 : }
     843              : 
     844            0 : void animmodel::part::preloadshaders()
     845              : {
     846            0 :     for(skin &i : skins)
     847              :     {
     848            0 :         i.preloadshader();
     849              :     }
     850            0 : }
     851              : 
     852            0 : void animmodel::part::preloadmeshes()
     853              : {
     854            0 :     if(meshes)
     855              :     {
     856            0 :         meshes->preload();
     857              :     }
     858            0 : }
     859              : 
     860            0 : void animmodel::part::getdefaultanim(animinfo &info) const
     861              : {
     862            0 :     info.frame = 0;
     863            0 :     info.range = 1;
     864            0 : }
     865              : 
     866            0 : bool animmodel::part::calcanim(int animpart, int anim, int basetime, int basetime2, dynent *d, int interp, animinfo &info, int &animinterptime) const
     867              : {
     868              :     //varseed uses an UGLY reinterpret cast from a pointer address to a size_t int
     869              :     //presumably the address should be a fairly random value
     870            0 :     uint varseed = static_cast<uint>(reinterpret_cast<size_t>(d));
     871            0 :     info.anim = anim;
     872            0 :     info.basetime = basetime;
     873            0 :     info.varseed = varseed;
     874            0 :     info.speed = anim & Anim_SetSpeed ? basetime2 : 100.0f;
     875            0 :     if((anim & Anim_Index) == Anim_All)
     876              :     {
     877            0 :         info.frame = 0;
     878            0 :         info.range = meshes->totalframes();
     879              :     }
     880              :     else
     881              :     {
     882            0 :         const animspec *spec = nullptr;
     883            0 :         if(anims[animpart])
     884              :         {
     885            0 :             const std::vector<animspec> &primary = anims[animpart][anim & Anim_Index];
     886            0 :             if(primary.size())
     887              :             {
     888            0 :                 spec = &primary[(varseed + static_cast<uint>(basetime))%primary.size()];
     889              :             }
     890            0 :             if((anim >> Anim_Secondary) & (Anim_Index | Anim_Dir))
     891              :             {
     892            0 :                 const std::vector<animspec> &secondary = anims[animpart][(anim >> Anim_Secondary) & Anim_Index];
     893            0 :                 if(secondary.size())
     894              :                 {
     895            0 :                     const animspec &spec2 = secondary[(varseed + static_cast<uint>(basetime2))%secondary.size()];
     896            0 :                     if(!spec || spec2.priority > spec->priority)
     897              :                     {
     898            0 :                         spec = &spec2;
     899            0 :                         info.anim >>= Anim_Secondary;
     900            0 :                         info.basetime = basetime2;
     901              :                     }
     902              :                 }
     903              :             }
     904              :         }
     905            0 :         if(spec)
     906              :         {
     907            0 :             info.frame = spec->frame;
     908            0 :             info.range = spec->range;
     909            0 :             if(spec->speed>0)
     910              :             {
     911            0 :                 info.speed = 1000.0f/spec->speed;
     912              :             }
     913              :         }
     914              :         else
     915              :         {
     916            0 :             getdefaultanim(info);
     917              :         }
     918              :     }
     919              : 
     920            0 :     info.anim &= (1 << Anim_Secondary) - 1;
     921            0 :     info.anim |= anim & Anim_Flags;
     922            0 :     if(info.anim & Anim_Loop)
     923              :     {
     924            0 :         info.anim &= ~Anim_SetTime;
     925            0 :         if(!info.basetime)
     926              :         {
     927            0 :             info.basetime = -(static_cast<int>(reinterpret_cast<size_t>(d)) & 0xFFF);
     928              :         }
     929            0 :         if(info.anim & Anim_Clamp)
     930              :         {
     931            0 :             if(info.anim & Anim_Reverse)
     932              :             {
     933            0 :                 info.frame += info.range-1;
     934              :             }
     935            0 :             info.range = 1;
     936              :         }
     937              :     }
     938              : 
     939            0 :     if(!meshes->hasframes(info.frame, info.range))
     940              :     {
     941            0 :         if(!meshes->hasframe(info.frame))
     942              :         {
     943            0 :             return false;
     944              :         }
     945            0 :         info.range = meshes->clipframes(info.frame, info.range);
     946              :     }
     947              : 
     948            0 :     if(d && interp>=0)
     949              :     {
     950            0 :         animinterpinfo &animationinterpolation = d->animinterp[interp];
     951            0 :         if((info.anim&(Anim_Loop | Anim_Clamp)) == Anim_Clamp)
     952              :         {
     953            0 :             animinterptime = std::min(animinterptime, static_cast<int>(info.range*info.speed*0.5e-3f));
     954              :         }
     955            0 :         void *ak = meshes->animkey();
     956            0 :         if(d->ragdoll && d->ragdoll->millis != lastmillis)
     957              :         {
     958            0 :             animationinterpolation.prev.range = animationinterpolation.cur.range = 0;
     959            0 :             animationinterpolation.lastswitch = -1;
     960              :         }
     961            0 :         else if(animationinterpolation.lastmodel!=ak || animationinterpolation.lastswitch<0 || lastmillis-d->lastrendered>animinterptime)
     962              :         {
     963            0 :             animationinterpolation.prev = animationinterpolation.cur = info;
     964            0 :             animationinterpolation.lastswitch = lastmillis-animinterptime*2;
     965              :         }
     966            0 :         else if(animationinterpolation.cur!=info)
     967              :         {
     968            0 :             if(lastmillis-animationinterpolation.lastswitch>animinterptime/2)
     969              :             {
     970            0 :                 animationinterpolation.prev = animationinterpolation.cur;
     971              :             }
     972            0 :             animationinterpolation.cur = info;
     973            0 :             animationinterpolation.lastswitch = lastmillis;
     974              :         }
     975            0 :         else if(info.anim & Anim_SetTime)
     976              :         {
     977            0 :             animationinterpolation.cur.basetime = info.basetime;
     978              :         }
     979            0 :         animationinterpolation.lastmodel = ak;
     980              :     }
     981            0 :     return true;
     982              : }
     983              : 
     984            0 : void animmodel::part::intersect(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, const vec &o, const vec &ray)
     985              : {
     986              :     AnimState as[maxanimparts];
     987            0 :     intersect(anim, basetime, basetime2, pitch, axis, forward, d, o, ray, as);
     988            0 : }
     989              : 
     990            0 : void animmodel::part::intersect(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, const vec &o, const vec &ray, AnimState *as)
     991              : {
     992            0 :     if((anim & Anim_Reuse) != Anim_Reuse)
     993              :     {
     994            0 :         for(int i = 0; i < numanimparts; ++i)
     995              :         {
     996            0 :             animinfo info;
     997            0 :             int interp = d && index+numanimparts<=maxanimparts ? index+i : -1,
     998            0 :                 animinterptime = animationinterpolationtime;
     999            0 :             if(!calcanim(i, anim, basetime, basetime2, d, interp, info, animinterptime))
    1000              :             {
    1001            0 :                 return;
    1002              :             }
    1003            0 :             AnimState &p = as[i];
    1004            0 :             p.owner = this;
    1005            0 :             p.cur.setframes(info);
    1006            0 :             p.interp = 1;
    1007            0 :             if(interp>=0 && d->animinterp[interp].prev.range>0)
    1008              :             {
    1009            0 :                 int diff = lastmillis-d->animinterp[interp].lastswitch;
    1010            0 :                 if(diff<animinterptime)
    1011              :                 {
    1012            0 :                     p.prev.setframes(d->animinterp[interp].prev);
    1013            0 :                     p.interp = diff/static_cast<float>(animinterptime);
    1014              :                 }
    1015              :             }
    1016              :         }
    1017              :     }
    1018              : 
    1019            0 :     float resize = model->scale * sizescale;
    1020            0 :     size_t oldpos = matrixstack.size();
    1021            0 :     vec oaxis, oforward, oo, oray;
    1022            0 :     matrixstack.top().transposedtransformnormal(axis, oaxis);
    1023            0 :     float pitchamount = pitchscale*pitch + pitchoffset;
    1024            0 :     if(pitchmin || pitchmax)
    1025              :     {
    1026            0 :         pitchamount = std::clamp(pitchamount, pitchmin, pitchmax);
    1027              :     }
    1028            0 :     if(as->cur.anim & Anim_NoPitch || (as->interp < 1 && as->prev.anim & Anim_NoPitch))
    1029              :     {
    1030            0 :         pitchamount *= (as->cur.anim & Anim_NoPitch ? 0 : as->interp) + (as->interp < 1 && as->prev.anim & Anim_NoPitch ? 0 : 1 - as->interp);
    1031              :     }
    1032            0 :     if(pitchamount)
    1033              :     {
    1034            0 :         matrixstack.push(matrixstack.top());
    1035            0 :         matrixstack.top().rotate(pitchamount/RAD, oaxis);
    1036              :     }
    1037            0 :     if(this == model->parts[0] && !model->translate.iszero())
    1038              :     {
    1039            0 :         if(oldpos == matrixstack.size())
    1040              :         {
    1041            0 :             matrixstack.push(matrixstack.top());
    1042              :         }
    1043            0 :         matrixstack.top().translate(model->translate, resize);
    1044              :     }
    1045            0 :     matrixstack.top().transposedtransformnormal(forward, oforward);
    1046            0 :     matrixstack.top().transposedtransform(o, oo);
    1047            0 :     oo.div(resize);
    1048            0 :     matrixstack.top().transposedtransformnormal(ray, oray);
    1049              : 
    1050            0 :     if((anim & Anim_Reuse) != Anim_Reuse)
    1051              :     {
    1052            0 :         for(linkedpart &link : links)
    1053              :         {
    1054            0 :             if(!link.p)
    1055              :             {
    1056            0 :                 continue;
    1057              :             }
    1058            0 :             link.matrix.translate(link.translate, resize);
    1059            0 :             matrix4 mul;
    1060            0 :             mul.mul(matrixstack.top(), link.matrix);
    1061            0 :             matrixstack.push(mul);
    1062              : 
    1063            0 :             int nanim = anim,
    1064            0 :                 nbasetime  = basetime,
    1065            0 :                 nbasetime2 = basetime2;
    1066            0 :             if(link.anim>=0)
    1067              :             {
    1068            0 :                 nanim = link.anim | (anim & Anim_Flags);
    1069            0 :                 nbasetime = link.basetime;
    1070            0 :                 nbasetime2 = 0;
    1071              :             }
    1072            0 :             link.p->intersect(nanim, nbasetime, nbasetime2, pitch, axis, forward, d, o, ray);
    1073              : 
    1074            0 :             matrixstack.pop();
    1075              :         }
    1076              :     }
    1077              : 
    1078            0 :     while(matrixstack.size() > oldpos)
    1079              :     {
    1080            0 :         matrixstack.pop();
    1081              :     }
    1082              : }
    1083              : 
    1084            0 : void animmodel::part::render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d)
    1085              : {
    1086              :     AnimState as[maxanimparts];
    1087            0 :     render(anim, basetime, basetime2, pitch, axis, forward, d, as);
    1088            0 : }
    1089              : 
    1090            0 : void animmodel::part::render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, AnimState *as)
    1091              : {
    1092            0 :     if((anim & Anim_Reuse) != Anim_Reuse)
    1093              :     {
    1094            0 :         for(int i = 0; i < numanimparts; ++i)
    1095              :         {
    1096            0 :             animinfo info;
    1097            0 :             int interp = d && index+numanimparts<=maxanimparts ? index+i : -1, animinterptime = animationinterpolationtime;
    1098            0 :             if(!calcanim(i, anim, basetime, basetime2, d, interp, info, animinterptime))
    1099              :             {
    1100            0 :                 return;
    1101              :             }
    1102            0 :             AnimState &p = as[i];
    1103            0 :             p.owner = this;
    1104            0 :             p.cur.setframes(info);
    1105            0 :             p.interp = 1;
    1106            0 :             if(interp>=0 && d->animinterp[interp].prev.range>0)
    1107              :             {
    1108            0 :                 int diff = lastmillis-d->animinterp[interp].lastswitch;
    1109            0 :                 if(diff<animinterptime)
    1110              :                 {
    1111            0 :                     p.prev.setframes(d->animinterp[interp].prev);
    1112            0 :                     p.interp = diff/static_cast<float>(animinterptime);
    1113              :                 }
    1114              :             }
    1115              :         }
    1116              :     }
    1117              : 
    1118            0 :     float resize = model->scale * sizescale;
    1119            0 :     size_t oldpos = matrixstack.size();
    1120            0 :     vec oaxis, oforward;
    1121            0 :     matrixstack.top().transposedtransformnormal(axis, oaxis);
    1122            0 :     float pitchamount = pitchscale*pitch + pitchoffset;
    1123            0 :     if(pitchmin || pitchmax)
    1124              :     {
    1125            0 :         pitchamount = std::clamp(pitchamount, pitchmin, pitchmax);
    1126              :     }
    1127            0 :     if(as->cur.anim & Anim_NoPitch || (as->interp < 1 && as->prev.anim & Anim_NoPitch))
    1128              :     {
    1129            0 :         pitchamount *= (as->cur.anim & Anim_NoPitch ? 0 : as->interp) + (as->interp < 1 && as->prev.anim & Anim_NoPitch ? 0 : 1 - as->interp);
    1130              :     }
    1131            0 :     if(pitchamount)
    1132              :     {
    1133            0 :         matrixstack.push(matrixstack.top());
    1134            0 :         matrixstack.top().rotate(pitchamount/RAD, oaxis);
    1135              :     }
    1136            0 :     if(this == model->parts[0] && !model->translate.iszero())
    1137              :     {
    1138            0 :         if(oldpos == matrixstack.size())
    1139              :         {
    1140            0 :             matrixstack.push(matrixstack.top());
    1141              :         }
    1142            0 :         matrixstack.top().translate(model->translate, resize);
    1143              :     }
    1144            0 :     matrixstack.top().transposedtransformnormal(forward, oforward);
    1145              : 
    1146            0 :     if(!(anim & Anim_NoRender))
    1147              :     {
    1148            0 :         matrix4 modelmatrix;
    1149            0 :         modelmatrix.mul(shadowmapping ? shadowmatrix : camprojmatrix, matrixstack.top());
    1150            0 :         if(resize!=1)
    1151              :         {
    1152            0 :             modelmatrix.scale(resize);
    1153              :         }
    1154            0 :         GLOBALPARAM(modelmatrix, modelmatrix);
    1155            0 :         if(!(anim & Anim_NoSkin))
    1156              :         {
    1157            0 :             GLOBALPARAM(modelworld, matrix3(matrixstack.top()));
    1158              : 
    1159            0 :             vec modelcamera;
    1160            0 :             matrixstack.top().transposedtransform(camera1->o, modelcamera);
    1161            0 :             modelcamera.div(resize);
    1162            0 :             GLOBALPARAM(modelcamera, modelcamera);
    1163              :         }
    1164              :     }
    1165              : 
    1166            0 :     meshes->render(as, pitch, oaxis, oforward, d, this);
    1167              : 
    1168            0 :     if((anim & Anim_Reuse) != Anim_Reuse)
    1169              :     {
    1170            0 :         for(linkedpart &link : links)
    1171              :         {
    1172            0 :             link.matrix.translate(link.translate, resize);
    1173            0 :             matrix4 mul;
    1174            0 :             mul.mul(matrixstack.top(), link.matrix);
    1175            0 :             matrixstack.push(mul);
    1176            0 :             if(link.pos)
    1177              :             {
    1178            0 :                 *link.pos = matrixstack.top().gettranslation();
    1179              :             }
    1180            0 :             if(!link.p)
    1181              :             {
    1182            0 :                 matrixstack.pop();
    1183            0 :                 continue;
    1184              :             }
    1185            0 :             int nanim = anim,
    1186            0 :                 nbasetime = basetime,
    1187            0 :                 nbasetime2 = basetime2;
    1188            0 :             if(link.anim>=0)
    1189              :             {
    1190            0 :                 nanim = link.anim | (anim & Anim_Flags);
    1191            0 :                 nbasetime = link.basetime;
    1192            0 :                 nbasetime2 = 0;
    1193              :             }
    1194            0 :             link.p->render(nanim, nbasetime, nbasetime2, pitch, axis, forward, d);
    1195            0 :             matrixstack.pop();
    1196              :         }
    1197              :     }
    1198              : 
    1199            0 :     while(matrixstack.size() > oldpos)
    1200              :     {
    1201            0 :         matrixstack.pop();
    1202              :     }
    1203              : }
    1204              : 
    1205            2 : void animmodel::part::setanim(int animpart, int num, int frame, int range, float speed, int priority)
    1206              : {
    1207            2 :     if(animpart<0 || animpart>=maxanimparts || num<0 || num >= static_cast<int>(animnames.size()))
    1208              :     {
    1209            0 :         return;
    1210              :     }
    1211            2 :     if(frame<0 || range<=0 || !meshes || !meshes->hasframes(frame, range))
    1212              :     {
    1213            0 :         conoutf("invalid frame %d, range %d in model %s", frame, range, model->modelname().c_str());
    1214            0 :         return;
    1215              :     }
    1216            2 :     if(!anims[animpart])
    1217              :     {
    1218            4 :         anims[animpart] = new std::vector<animspec>[animnames.size()];
    1219              :     }
    1220            2 :     anims[animpart][num].push_back({frame, range, speed, priority});
    1221              : }
    1222              : 
    1223            2 : bool animmodel::part::animated() const
    1224              : {
    1225            2 :     for(int i = 0; i < maxanimparts; ++i)
    1226              :     {
    1227            2 :         if(anims[i])
    1228              :         {
    1229            2 :             return true;
    1230              :         }
    1231              :     }
    1232            0 :     return false;
    1233              : }
    1234              : 
    1235           11 : void animmodel::part::loaded()
    1236              : {
    1237           22 :     for(skin &i : skins)
    1238              :     {
    1239           11 :         i.setkey();
    1240              :     }
    1241           11 : }
    1242              : 
    1243            0 : int animmodel::linktype(const animmodel *, const part *) const
    1244              : {
    1245            0 :     return Link_Tag;
    1246              : }
    1247              : 
    1248            0 : void animmodel::intersect(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, modelattach *a, const vec &o, const vec &ray) const
    1249              : {
    1250            0 :     int numtags = 0;
    1251            0 :     if(a)
    1252              :     {
    1253            0 :         int index = parts.back()->index + parts.back()->numanimparts;
    1254            0 :         for(int i = 0; a[i].tag; i++)
    1255              :         {
    1256            0 :             numtags++;
    1257            0 :             animmodel *m = static_cast<animmodel *>(a[i].m);
    1258            0 :             if(!m)
    1259              :             {
    1260            0 :                 continue;
    1261              :             }
    1262            0 :             part *p = m->parts[0];
    1263            0 :             switch(linktype(m, p))
    1264              :             {
    1265            0 :                 case Link_Tag:
    1266              :                 {
    1267            0 :                     p->index = link(p, a[i].tag, vec(0, 0, 0), a[i].anim, a[i].basetime, a[i].pos) ? index : -1;
    1268            0 :                     break;
    1269              :                 }
    1270            0 :                 default:
    1271              :                 {
    1272            0 :                     continue;
    1273              :                 }
    1274              :             }
    1275            0 :             index += p->numanimparts;
    1276              :         }
    1277              :     }
    1278              : 
    1279              :     AnimState as[maxanimparts];
    1280            0 :     parts[0]->intersect(anim, basetime, basetime2, pitch, axis, forward, d, o, ray, as);
    1281              : 
    1282            0 :     for(part *p : parts)
    1283              :     {
    1284            0 :         switch(linktype(this, p))
    1285              :         {
    1286            0 :             case Link_Reuse:
    1287              :             {
    1288            0 :                 p->intersect(anim | Anim_Reuse, basetime, basetime2, pitch, axis, forward, d, o, ray, as);
    1289            0 :                 break;
    1290              :             }
    1291              :         }
    1292              :     }
    1293              : 
    1294            0 :     if(a)
    1295              :     {
    1296            0 :         for(int i = numtags-1; i >= 0; i--)
    1297              :         {
    1298            0 :             animmodel *m = static_cast<animmodel *>(a[i].m);
    1299            0 :             if(!m)
    1300              :             {
    1301            0 :                 continue;
    1302              :             }
    1303              : 
    1304            0 :             part *p = m->parts[0];
    1305            0 :             switch(linktype(m, p))
    1306              :             {
    1307            0 :                 case Link_Tag:
    1308              :                 {
    1309            0 :                     if(p->index >= 0)
    1310              :                     {
    1311            0 :                         unlink(p);
    1312              :                     }
    1313            0 :                     p->index = 0;
    1314            0 :                     break;
    1315              :                 }
    1316            0 :                 case Link_Reuse:
    1317              :                 {
    1318            0 :                     p->intersect(anim | Anim_Reuse, basetime, basetime2, pitch, axis, forward, d, o, ray, as);
    1319            0 :                     break;
    1320              :                 }
    1321              :             }
    1322              :         }
    1323              :     }
    1324            0 : }
    1325              : 
    1326            0 : int animmodel::intersect(int anim, int basetime, int basetime2, const vec &pos, float yaw, float pitch, float roll, dynent *d, modelattach *a, float size, const vec &o, const vec &ray) const
    1327              : {
    1328            0 :     vec axis(1, 0, 0),
    1329            0 :         forward(0, 1, 0);
    1330              : 
    1331            0 :     matrixstack.push(matrix4());
    1332            0 :     matrixstack.top().identity();
    1333            0 :     if(!d || !d->ragdoll || d->ragdoll->millis == lastmillis)
    1334              :     {
    1335            0 :         const float secs = lastmillis/1000.0f;
    1336            0 :         yaw += spin.x*secs;
    1337            0 :         pitch += spin.y*secs;
    1338            0 :         roll += spin.z*secs;
    1339              : 
    1340            0 :         matrixstack.top().settranslation(pos);
    1341            0 :         matrixstack.top().rotate_around_z(yaw/RAD);
    1342            0 :         const bool usepitch = pitched();
    1343            0 :         if(roll && !usepitch)
    1344              :         {
    1345            0 :             matrixstack.top().rotate_around_y(-roll/RAD);
    1346              :         }
    1347            0 :         matrixstack.top().transformnormal(vec(axis), axis);
    1348            0 :         matrixstack.top().transformnormal(vec(forward), forward);
    1349            0 :         if(roll && usepitch)
    1350              :         {
    1351            0 :             matrixstack.top().rotate_around_y(-roll/RAD);
    1352              :         }
    1353            0 :         if(orientation.x)
    1354              :         {
    1355            0 :             matrixstack.top().rotate_around_z(orientation.x/RAD);
    1356              :         }
    1357            0 :         if(orientation.y)
    1358              :         {
    1359            0 :             matrixstack.top().rotate_around_x(orientation.y/RAD);
    1360              :         }
    1361            0 :         if(orientation.z)
    1362              :         {
    1363            0 :             matrixstack.top().rotate_around_y(-orientation.z/RAD);
    1364              :         }
    1365            0 :     }
    1366              :     else
    1367              :     {
    1368            0 :         matrixstack.top().settranslation(d->ragdoll->center);
    1369            0 :         pitch = 0;
    1370              :     }
    1371            0 :     sizescale = size;
    1372              : 
    1373            0 :     intersect(anim, basetime, basetime2, pitch, axis, forward, d, a, o, ray);
    1374            0 :     return -1;
    1375              : }
    1376              : 
    1377            0 : void animmodel::render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, modelattach *a) const
    1378              : {
    1379            0 :     int numtags = 0;
    1380            0 :     if(a)
    1381              :     {
    1382            0 :         int index = parts.back()->index + parts.back()->numanimparts;
    1383            0 :         for(int i = 0; a[i].tag; i++)
    1384              :         {
    1385            0 :             numtags++;
    1386              : 
    1387            0 :             animmodel *m = static_cast<animmodel *>(a[i].m);
    1388            0 :             if(!m)
    1389              :             {
    1390            0 :                 if(a[i].pos)
    1391              :                 {
    1392            0 :                     link(nullptr, a[i].tag, vec(0, 0, 0), 0, 0, a[i].pos);
    1393              :                 }
    1394            0 :                 continue;
    1395              :             }
    1396            0 :             part *p = m->parts[0];
    1397            0 :             switch(linktype(m, p))
    1398              :             {
    1399            0 :                 case Link_Tag:
    1400              :                 {
    1401            0 :                     p->index = link(p, a[i].tag, vec(0, 0, 0), a[i].anim, a[i].basetime, a[i].pos) ? index : -1;
    1402            0 :                     break;
    1403              :                 }
    1404            0 :                 default:
    1405              :                 {
    1406            0 :                     continue;
    1407              :                 }
    1408              :             }
    1409            0 :             index += p->numanimparts;
    1410              :         }
    1411              :     }
    1412              : 
    1413              :     AnimState as[maxanimparts];
    1414            0 :     parts[0]->render(anim, basetime, basetime2, pitch, axis, forward, d, as);
    1415              : 
    1416            0 :     for(size_t i = 1; i < parts.size(); i++)
    1417              :     {
    1418            0 :         part *p = parts[i];
    1419            0 :         switch(linktype(this, p))
    1420              :         {
    1421            0 :             case Link_Reuse:
    1422              :             {
    1423            0 :                 p->render(anim | Anim_Reuse, basetime, basetime2, pitch, axis, forward, d, as);
    1424            0 :                 break;
    1425              :             }
    1426              :         }
    1427              :     }
    1428              : 
    1429            0 :     if(a)
    1430              :     {
    1431            0 :         for(int i = numtags-1; i >= 0; i--)
    1432              :         {
    1433            0 :             const animmodel *m = static_cast<animmodel *>(a[i].m);
    1434            0 :             if(!m)
    1435              :             {
    1436            0 :                 if(a[i].pos)
    1437              :                 {
    1438            0 :                     unlink(nullptr);
    1439              :                 }
    1440            0 :                 continue;
    1441              :             }
    1442            0 :             part *p = m->parts[0];
    1443            0 :             switch(linktype(m, p))
    1444              :             {
    1445            0 :                 case Link_Tag:
    1446              :                 {
    1447            0 :                     if(p->index >= 0)
    1448              :                     {
    1449            0 :                         unlink(p);
    1450              :                     }
    1451            0 :                     p->index = 0;
    1452            0 :                     break;
    1453              :                 }
    1454            0 :                 case Link_Reuse:
    1455              :                 {
    1456            0 :                     p->render(anim | Anim_Reuse, basetime, basetime2, pitch, axis, forward, d, as);
    1457            0 :                     break;
    1458              :                 }
    1459              :             }
    1460              :         }
    1461              :     }
    1462            0 : }
    1463              : 
    1464            0 : void animmodel::render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, float roll, dynent *d, modelattach *a, float size, const vec4<float> &color) const
    1465              : {
    1466            0 :     vec axis(1, 0, 0),
    1467            0 :         forward(0, 1, 0);
    1468              : 
    1469            0 :     matrixstack = {};
    1470            0 :     matrixstack.push(matrix4());
    1471            0 :     matrixstack.top().identity();
    1472            0 :     if(!d || !d->ragdoll || d->ragdoll->millis == lastmillis)
    1473              :     {
    1474            0 :         float secs = lastmillis/1000.0f;
    1475            0 :         yaw += spin.x*secs;
    1476            0 :         pitch += spin.y*secs;
    1477            0 :         roll += spin.z*secs;
    1478              : 
    1479            0 :         matrixstack.top().settranslation(o);
    1480            0 :         matrixstack.top().rotate_around_z(yaw/RAD);
    1481            0 :         bool usepitch = pitched();
    1482            0 :         if(roll && !usepitch)
    1483              :         {
    1484            0 :             matrixstack.top().rotate_around_y(-roll/RAD);
    1485              :         }
    1486            0 :         matrixstack.top().transformnormal(vec(axis), axis);
    1487            0 :         matrixstack.top().transformnormal(vec(forward), forward);
    1488            0 :         if(roll && usepitch)
    1489              :         {
    1490            0 :             matrixstack.top().rotate_around_y(-roll/RAD);
    1491              :         }
    1492            0 :         if(orientation.x)
    1493              :         {
    1494            0 :             matrixstack.top().rotate_around_z(orientation.x/RAD);
    1495              :         }
    1496            0 :         if(orientation.y)
    1497              :         {
    1498            0 :             matrixstack.top().rotate_around_x(orientation.y/RAD);
    1499              :         }
    1500            0 :         if(orientation.z)
    1501              :         {
    1502            0 :             matrixstack.top().rotate_around_y(-orientation.z/RAD);
    1503              :         }
    1504            0 :     }
    1505              :     else
    1506              :     {
    1507            0 :         matrixstack.top().settranslation(d->ragdoll->center);
    1508            0 :         pitch = 0;
    1509              :     }
    1510              : 
    1511            0 :     sizescale = size;
    1512              : 
    1513            0 :     if(anim & Anim_NoRender)
    1514              :     {
    1515            0 :         render(anim, basetime, basetime2, pitch, axis, forward, d, a);
    1516            0 :         if(d)
    1517              :         {
    1518            0 :             d->lastrendered = lastmillis;
    1519              :         }
    1520            0 :         return;
    1521              :     }
    1522              : 
    1523            0 :     if(!(anim & Anim_NoSkin))
    1524              :     {
    1525            0 :         if(colorscale != color)
    1526              :         {
    1527            0 :             colorscale = color;
    1528            0 :             skin::invalidateshaderparams();
    1529              :         }
    1530              :     }
    1531              : 
    1532            0 :     if(depthoffset && !enabledepthoffset)
    1533              :     {
    1534            0 :         enablepolygonoffset(GL_POLYGON_OFFSET_FILL);
    1535            0 :         enabledepthoffset = true;
    1536              :     }
    1537              : 
    1538            0 :     render(anim, basetime, basetime2, pitch, axis, forward, d, a);
    1539              : 
    1540            0 :     if(d)
    1541              :     {
    1542            0 :         d->lastrendered = lastmillis;
    1543              :     }
    1544              : }
    1545              : 
    1546            0 : void animmodel::cleanup()
    1547              : {
    1548            0 :     for(part *p : parts)
    1549              :     {
    1550            0 :         p->cleanup();
    1551              :     }
    1552            0 : }
    1553              : 
    1554            0 : animmodel::part &animmodel::addpart()
    1555              : {
    1556            0 :     part *p = new part(this, parts.size());
    1557            0 :     parts.push_back(p);
    1558            0 :     return *p;
    1559              : }
    1560              : 
    1561            2 : matrix4x3 animmodel::initmatrix() const
    1562              : {
    1563            2 :     matrix4x3 m;
    1564            2 :     m.identity();
    1565            2 :     if(orientation.x)
    1566              :     {
    1567            0 :         m.rotate_around_z(orientation.x/RAD);
    1568              :     }
    1569            2 :     if(orientation.y)
    1570              :     {
    1571            0 :         m.rotate_around_x(orientation.y/RAD);
    1572              :     }
    1573            2 :     if(orientation.z)
    1574              :     {
    1575            0 :         m.rotate_around_y(-orientation.z/RAD);
    1576              :     }
    1577            2 :     m.translate(translate, scale);
    1578            2 :     return m;
    1579              : }
    1580              : 
    1581            2 : void animmodel::genBIH(std::vector<BIH::mesh> &bih)
    1582              : {
    1583            2 :     if(parts.empty())
    1584              :     {
    1585            0 :         return;
    1586              :     }
    1587            2 :     matrix4x3 m = initmatrix();
    1588            4 :     for(const skin &s : parts[0]->skins)
    1589              :     {
    1590            2 :         s.tex->loadalphamask();
    1591              :     }
    1592            2 :     for(size_t i = 1; i < parts.size(); i++)
    1593              :     {
    1594            0 :         const part *p = parts[i];
    1595            0 :         switch(linktype(this, p))
    1596              :         {
    1597            0 :             case Link_Reuse:
    1598              :             {
    1599            0 :                 for(skin &s : parts[i]->skins)
    1600              :                 {
    1601            0 :                     s.tex->loadalphamask();
    1602              :                 }
    1603            0 :                 p->genBIH(bih, m, scale);
    1604            0 :                 break;
    1605              :             }
    1606              :         }
    1607              :     }
    1608              : }
    1609              : 
    1610            0 : bool animmodel::link(part *p, std::string_view tag, const vec &translate, int anim, int basetime, vec *pos) const
    1611              : {
    1612            0 :     if(parts.empty())
    1613              :     {
    1614            0 :         return false;
    1615              :     }
    1616            0 :     return parts[0]->link(p, tag, translate, anim, basetime, pos);
    1617              : }
    1618              : 
    1619            7 : void animmodel::loaded()
    1620              : {
    1621           18 :     for(part *p : parts)
    1622              :     {
    1623           11 :         p->loaded();
    1624              :     }
    1625            7 : }
    1626              : 
    1627            0 : bool animmodel::unlink(const part *p) const
    1628              : {
    1629            0 :     if(parts.empty())
    1630              :     {
    1631            0 :         return false;
    1632              :     }
    1633            0 :     return parts[0]->unlink(p);
    1634              : }
    1635              : 
    1636            0 : void animmodel::genshadowmesh(std::vector<triangle> &tris, const matrix4x3 &orient) const
    1637              : {
    1638            0 :     if(parts.empty())
    1639              :     {
    1640            0 :         return;
    1641              :     }
    1642            0 :     matrix4x3 m = initmatrix();
    1643            0 :     m.mul(orient, matrix4x3(m));
    1644            0 :     parts[0]->genshadowmesh(tris, m, scale);
    1645            0 :     for(size_t i = 1; i < parts.size(); i++)
    1646              :     {
    1647            0 :         const part *p = parts[i];
    1648            0 :         switch(linktype(this, p))
    1649              :         {
    1650            0 :             case Link_Reuse:
    1651              :             {
    1652            0 :                 p->genshadowmesh(tris, m, scale);
    1653            0 :                 break;
    1654              :             }
    1655              :         }
    1656              :     }
    1657              : }
    1658              : 
    1659            1 : void animmodel::preloadBIH()
    1660              : {
    1661            1 :     if(!bih)
    1662              :     {
    1663            1 :         setBIH();
    1664              :     }
    1665            1 :     if(bih)
    1666              :     {
    1667            2 :         for(const part *i : parts)
    1668              :         {
    1669            1 :             i->preloadBIH();
    1670              :         }
    1671              :     }
    1672            1 : }
    1673              : 
    1674              : //always will return true (note that this overloads model::setBIH())
    1675            2 : void animmodel::setBIH()
    1676              : {
    1677            2 :     if(bih)
    1678              :     {
    1679            0 :         return;
    1680              :     }
    1681            2 :     std::vector<BIH::mesh> meshes;
    1682            2 :     genBIH(meshes);
    1683            2 :     bih = std::make_unique<BIH>(meshes);
    1684            2 : }
    1685              : 
    1686            1 : bool animmodel::animated() const
    1687              : {
    1688            1 :     if(spin.x || spin.y || spin.z)
    1689              :     {
    1690            0 :         return true;
    1691              :     }
    1692            1 :     for(const part *i : parts)
    1693              :     {
    1694            1 :         if(i->animated())
    1695              :         {
    1696            1 :             return true;
    1697              :         }
    1698              :     }
    1699            0 :     return false;
    1700              : }
    1701              : 
    1702            1 : bool animmodel::pitched() const
    1703              : {
    1704            1 :     return parts[0]->pitchscale != 0;
    1705              : }
    1706              : 
    1707            0 : bool animmodel::alphatested() const
    1708              : {
    1709            0 :     for(const part *i : parts)
    1710              :     {
    1711            0 :         if(i->alphatested())
    1712              :         {
    1713            0 :             return true;
    1714              :         }
    1715              :     }
    1716            0 :     return false;
    1717              : }
    1718              : 
    1719            6 : bool animmodel::load()
    1720              : {
    1721            6 :     startload();
    1722            6 :     bool success = loadconfig(modelname()) && parts.size(); // configured model, will call the model commands below
    1723            6 :     if(!success)
    1724              :     {
    1725            6 :         success = loaddefaultparts(); // model without configuration, try default tris and skin
    1726              :     }
    1727            6 :     endload();
    1728            6 :     if(flipy())
    1729              :     {
    1730            0 :         translate.y = -translate.y;
    1731              :     }
    1732              : 
    1733            6 :     if(!success)
    1734              :     {
    1735            1 :         return false;
    1736              :     }
    1737           14 :     for(const part *i : parts)
    1738              :     {
    1739            9 :         if(!i->meshes)
    1740              :         {
    1741            0 :             return false;
    1742              :         }
    1743              :     }
    1744            5 :     loaded();
    1745            5 :     return true;
    1746              : }
    1747              : 
    1748            0 : void animmodel::preloadshaders()
    1749              : {
    1750            0 :     for(part *i : parts)
    1751              :     {
    1752            0 :         i->preloadshaders();
    1753              :     }
    1754            0 : }
    1755              : 
    1756            0 : void animmodel::preloadmeshes()
    1757              : {
    1758            0 :     for(part *i : parts)
    1759              :     {
    1760            0 :         i->preloadmeshes();
    1761              :     }
    1762            0 : }
    1763              : 
    1764            0 : void animmodel::setshader(Shader *shader)
    1765              : {
    1766            0 :     if(parts.empty())
    1767              :     {
    1768            0 :         loaddefaultparts();
    1769              :     }
    1770            0 :     for(part *i : parts)
    1771              :     {
    1772            0 :         for(skin &j : i->skins)
    1773              :         {
    1774            0 :             j.shader = shader;
    1775              :         }
    1776              :     }
    1777            0 : }
    1778              : 
    1779            0 : void animmodel::setspec(float spec)
    1780              : {
    1781            0 :     if(parts.empty())
    1782              :     {
    1783            0 :         loaddefaultparts();
    1784              :     }
    1785            0 :     for(part *i : parts)
    1786              :     {
    1787            0 :         for(skin &j : i->skins)
    1788              :         {
    1789            0 :             j.spec = spec;
    1790              :         }
    1791              :     }
    1792            0 : }
    1793              : 
    1794            0 : void animmodel::setgloss(int gloss)
    1795              : {
    1796            0 :     if(parts.empty())
    1797              :     {
    1798            0 :         loaddefaultparts();
    1799              :     }
    1800            0 :     for(part *i : parts)
    1801              :     {
    1802            0 :         for(skin &j : i->skins)
    1803              :         {
    1804            0 :             j.gloss = gloss;
    1805              :         }
    1806              :     }
    1807            0 : }
    1808              : 
    1809            0 : void animmodel::setglow(float glow, float delta, float pulse)
    1810              : {
    1811            0 :     if(parts.empty())
    1812              :     {
    1813            0 :         loaddefaultparts();
    1814              :     }
    1815            0 :     for(part *i : parts)
    1816              :     {
    1817            0 :         for(skin &s : i->skins)
    1818              :         {
    1819            0 :             s.glow = glow;
    1820            0 :             s.glowdelta = delta;
    1821            0 :             s.glowpulse = pulse;
    1822              :         }
    1823              :     }
    1824            0 : }
    1825              : 
    1826            0 : void animmodel::setalphatest(float alphatest)
    1827              : {
    1828            0 :     if(parts.empty())
    1829              :     {
    1830            0 :         loaddefaultparts();
    1831              :     }
    1832            0 :     for(part *i : parts)
    1833              :     {
    1834            0 :         for(skin &j : i->skins)
    1835              :         {
    1836            0 :             j.alphatest = alphatest;
    1837              :         }
    1838              :     }
    1839            0 : }
    1840              : 
    1841            0 : void animmodel::setfullbright(float fullbright)
    1842              : {
    1843            0 :     if(parts.empty())
    1844              :     {
    1845            0 :         loaddefaultparts();
    1846              :     }
    1847            0 :     for(part *i : parts)
    1848              :     {
    1849            0 :         for(skin &j : i->skins)
    1850              :         {
    1851            0 :             j.fullbright = fullbright;
    1852              :         }
    1853              :     }
    1854            0 : }
    1855              : 
    1856            0 : void animmodel::setcullface(int cullface)
    1857              : {
    1858            0 :     if(parts.empty())
    1859              :     {
    1860            0 :         loaddefaultparts();
    1861              :     }
    1862            0 :     for(part *i : parts)
    1863              :     {
    1864            0 :         for(skin &j : i->skins)
    1865              :         {
    1866            0 :             j.cullface = cullface;
    1867              :         }
    1868              :     }
    1869            0 : }
    1870              : 
    1871            0 : void animmodel::setcolor(const vec &color)
    1872              : {
    1873            0 :     if(parts.empty())
    1874              :     {
    1875            0 :         loaddefaultparts();
    1876              :     }
    1877            0 :     for(part *i : parts)
    1878              :     {
    1879            0 :         for(skin &j : i->skins)
    1880              :         {
    1881            0 :             j.color = color;
    1882              :         }
    1883              :     }
    1884            0 : }
    1885              : 
    1886            0 : void animmodel::settransformation(const std::optional<vec> pos,
    1887              :                                   const std::optional<vec> rotate,
    1888              :                                   const std::optional<vec> orient,
    1889              :                                   const std::optional<float> size)
    1890              : {
    1891            0 :     if(pos)
    1892              :     {
    1893            0 :         translate = pos.value();
    1894              :     }
    1895            0 :     if(rotate)
    1896              :     {
    1897            0 :         spin = rotate.value();
    1898              :     }
    1899            0 :     if(orient)
    1900              :     {
    1901            0 :         orientation = orient.value();
    1902              :     }
    1903            0 :     if(size)
    1904              :     {
    1905            0 :         scale = size.value();
    1906              :     }
    1907            0 : }
    1908              : 
    1909            0 : vec4<float> animmodel::locationsize() const
    1910              : {
    1911            0 :     return vec4<float>(translate.x, translate.y, translate.z, scale);
    1912              : }
    1913              : 
    1914            0 : void animmodel::calcbb(vec &center, vec &radius) const
    1915              : {
    1916            0 :     if(parts.empty())
    1917              :     {
    1918            0 :         return;
    1919              :     }
    1920            0 :     vec bbmin(1e16f, 1e16f, 1e16f),
    1921            0 :         bbmax(-1e16f, -1e16f, -1e16f);
    1922            0 :     matrix4x3 m = initmatrix();
    1923            0 :     parts[0]->calcbb(bbmin, bbmax, m, scale);
    1924            0 :     for(const part *p : parts)
    1925              :     {
    1926            0 :         switch(linktype(this, p))
    1927              :         {
    1928            0 :             case Link_Reuse:
    1929              :             {
    1930            0 :                 p->calcbb(bbmin, bbmax, m, scale);
    1931            0 :                 break;
    1932              :             }
    1933              :         }
    1934              :     }
    1935            0 :     radius = bbmax;
    1936            0 :     radius.sub(bbmin);
    1937            0 :     radius.mul(0.5f);
    1938            0 :     center = bbmin;
    1939            0 :     center.add(radius);
    1940              : }
    1941              : 
    1942            0 : void animmodel::startrender() const
    1943              : {
    1944            0 :     enabletc = enabletangents = enablebones = enabledepthoffset = false;
    1945            0 :     enablecullface = true;
    1946            0 :     lastvbuf = lasttcbuf = lastxbuf = lastbbuf = lastebuf =0;
    1947            0 :     lastmasks = lastnormalmap = lastdecal = lasttex = nullptr;
    1948            0 :     skin::invalidateshaderparams();
    1949            0 : }
    1950              : 
    1951            0 : void animmodel::endrender() const
    1952              : {
    1953            0 :     if(lastvbuf || lastebuf)
    1954              :     {
    1955            0 :         disablevbo();
    1956              :     }
    1957            0 :     if(!enablecullface)
    1958              :     {
    1959            0 :         glEnable(GL_CULL_FACE);
    1960              :     }
    1961            0 :     if(enabledepthoffset)
    1962              :     {
    1963            0 :         disablepolygonoffset(GL_POLYGON_OFFSET_FILL);
    1964              :     }
    1965            0 : }
    1966              : 
    1967            0 : void animmodel::boundbox(vec &center, vec &radius)
    1968              : {
    1969            0 :     if(bbradius.x < 0)
    1970              :     {
    1971            0 :         calcbb(bbcenter, bbradius);
    1972            0 :         bbradius.add(bbextend);
    1973              :     }
    1974            0 :     center = bbcenter;
    1975            0 :     radius = bbradius;
    1976            0 : }
    1977              : 
    1978            0 : float animmodel::collisionbox(vec &center, vec &radius)
    1979              : {
    1980            0 :     if(collideradius.x < 0)
    1981              :     {
    1982            0 :         boundbox(collidecenter, collideradius);
    1983            0 :         if(collidexyradius)
    1984              :         {
    1985            0 :             collidecenter.x = collidecenter.y = 0;
    1986            0 :             collideradius.x = collideradius.y = collidexyradius;
    1987              :         }
    1988            0 :         if(collideheight)
    1989              :         {
    1990            0 :             collidecenter.z = collideradius.z = collideheight/2;
    1991              :         }
    1992            0 :         rejectradius = collideradius.magnitude();
    1993              :     }
    1994            0 :     center = collidecenter;
    1995            0 :     radius = collideradius;
    1996            0 :     return rejectradius;
    1997              : }
    1998              : 
    1999          132 : const std::string &animmodel::modelname() const
    2000              : {
    2001          132 :     return name;
    2002              : }
    2003              : 
    2004            0 : void animmodel::disablebones()
    2005              : {
    2006            0 :     gle::disableboneweight();
    2007            0 :     gle::disableboneindex();
    2008            0 :     enablebones = false;
    2009            0 : }
    2010              : 
    2011            0 : void animmodel::disabletangents()
    2012              : {
    2013            0 :     gle::disabletangent();
    2014            0 :     enabletangents = false;
    2015            0 : }
    2016              : 
    2017            0 : void animmodel::disabletc()
    2018              : {
    2019            0 :     gle::disabletexcoord0();
    2020            0 :     enabletc = false;
    2021            0 : }
    2022              : 
    2023            0 : void animmodel::disablevbo()
    2024              : {
    2025            0 :     if(lastebuf)
    2026              :     {
    2027            0 :         gle::clearebo();
    2028              :     }
    2029            0 :     if(lastvbuf)
    2030              :     {
    2031            0 :         gle::clearvbo();
    2032            0 :         gle::disablevertex();
    2033              :     }
    2034            0 :     if(enabletc)
    2035              :     {
    2036            0 :         disabletc();
    2037              :     }
    2038            0 :     if(enabletangents)
    2039              :     {
    2040            0 :         disabletangents();
    2041              :     }
    2042            0 :     if(enablebones)
    2043              :     {
    2044            0 :         disablebones();
    2045              :     }
    2046            0 :     lastvbuf = lasttcbuf = lastxbuf = lastbbuf = lastebuf = 0;
    2047            0 : }
        

Generated by: LCOV version 2.0-1