LCOV - code coverage report
Current view: top level - engine/model - animmodel.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 13.9 % 1033 144
Test Date: 2025-02-21 06:59:27 Functions: 30.6 % 111 34

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

Generated by: LCOV version 2.0-1