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: 2025-11-03 06:43:22 Functions: 32.7 % 113 37

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

Generated by: LCOV version 2.0-1