LCOV - code coverage report
Current view: top level - engine/model - animmodel.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 15.3 % 1047 160
Test Date: 2026-05-09 04:28:55 Functions: 32.7 % 113 37

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

Generated by: LCOV version 2.0-1