LCOV - code coverage report
Current view: top level - engine/model - animmodel.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 137 1044 13.1 %
Date: 2024-11-22 05:07:59 Functions: 30 113 26.5 %

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

Generated by: LCOV version 1.14