LCOV - code coverage report
Current view: top level - engine/model - skelmodel.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 46.5 % 1009 469
Test Date: 2025-02-21 06:59:27 Functions: 59.6 % 114 68

            Line data    Source code
       1              : /* skelmodel.cpp: implementation for the skeletal animation object
       2              :  *
       3              :  * the skelmodel object handles the behavior of animated skeletal models -- such
       4              :  * as the ones used by the player and bots
       5              :  *
       6              :  * for the procedural modification of skeletal models using ragdoll physics, see
       7              :  * ragdoll.h
       8              :  *
       9              :  * this file contains the implementation for the skelmodel object, see skelmodel.h
      10              :  * for the class definition
      11              :  */
      12              : #include "../libprimis-headers/cube.h"
      13              : #include "../../shared/geomexts.h"
      14              : #include "../../shared/glemu.h"
      15              : #include "../../shared/glexts.h"
      16              : 
      17              : #include <optional>
      18              : #include <memory>
      19              : 
      20              : #include "interface/console.h"
      21              : #include "interface/control.h"
      22              : #include "interface/cs.h"
      23              : 
      24              : #include "render/rendergl.h"
      25              : #include "render/renderlights.h"
      26              : #include "render/rendermodel.h"
      27              : #include "render/shader.h"
      28              : #include "render/shaderparam.h"
      29              : #include "render/texture.h"
      30              : 
      31              : #include "world/entities.h"
      32              : #include "world/octaworld.h"
      33              : #include "world/bih.h"
      34              : 
      35              : #include "model.h"
      36              : #include "ragdoll.h"
      37              : #include "animmodel.h"
      38              : #include "skelmodel.h"
      39              : 
      40              : VAR(maxskelanimdata, 1, 192, 0); //sets maximum number of gpu bones
      41              : 
      42              : //animcacheentry child classes
      43              : 
      44            0 : bool skelmodel::vbocacheentry::check() const
      45              : {
      46            0 :     return !vbuf;
      47              : }
      48              : 
      49           64 : skelmodel::vbocacheentry::vbocacheentry() : vbuf(0), owner(-1)
      50              : {
      51           64 : }
      52              : 
      53            0 : void skelmodel::skelcacheentry::nextversion()
      54              : {
      55            0 :     version = Shader::uniformlocversion();
      56            0 : }
      57              : 
      58           64 : skelmodel::skelcacheentry::skelcacheentry() : bdata(nullptr), version(-1)
      59              : {
      60           64 : }
      61              : 
      62           64 : skelmodel::blendcacheentry::blendcacheentry() : owner(-1)
      63              : {
      64           64 : }
      65              : 
      66              : //skelmodel::animcacheentry object
      67          128 : skelmodel::animcacheentry::animcacheentry() : ragdoll(nullptr)
      68              : {
      69          512 :     for(int k = 0; k < maxanimparts; ++k)
      70              :     {
      71          384 :         as[k].cur.fr1 = as[k].prev.fr1 = -1;
      72              :     }
      73          128 : }
      74              : 
      75            0 : bool skelmodel::animcacheentry::operator==(const animcacheentry &c) const
      76              : {
      77            0 :     for(int i = 0; i < maxanimparts; ++i)
      78              :     {
      79            0 :         if(as[i]!=c.as[i])
      80              :         {
      81            0 :             return false;
      82              :         }
      83              :     }
      84            0 :     return pitch==c.pitch && partmask==c.partmask && ragdoll==c.ragdoll && (!ragdoll || std::min(millis, c.millis) >= ragdoll->lastmove);
      85              : }
      86              : 
      87            0 : bool skelmodel::animcacheentry::operator!=(const animcacheentry &c) const
      88              : {
      89            0 :     return !(*this == c);
      90              : }
      91              : 
      92              : //pitchcorrect
      93              : 
      94            0 : skelmodel::skeleton::pitchcorrect::pitchcorrect(int bone, size_t target, float pitchscale, float pitchmin, float pitchmax) :
      95            0 :     bone(bone), parent (-1), target(target), pitchmin(pitchmin), pitchmax(pitchmax),
      96            0 :     pitchscale(pitchscale), pitchangle(0), pitchtotal(0)
      97              : {
      98            0 : }
      99              : 
     100            0 : skelmodel::skeleton::pitchcorrect::pitchcorrect() : parent(-1), pitchangle(0), pitchtotal(0)
     101              : {
     102            0 : }
     103              : 
     104              : //skeleton
     105              : 
     106            9 : const skelmodel::skelanimspec *skelmodel::skeleton::findskelanim(std::string_view name) const
     107              : {
     108            9 :     for(const skelanimspec &i : skelanims)
     109              :     {
     110            8 :         if(!i.name.empty())
     111              :         {
     112            8 :             if(name == i.name)
     113              :             {
     114            8 :                 return &i;
     115              :             }
     116              :         }
     117              :     }
     118            1 :     return nullptr;
     119              : }
     120              : 
     121            1 : skelmodel::skelanimspec &skelmodel::skeleton::addskelanim(std::string_view name, int numframes, int animframes)
     122              : {
     123            2 :     skelanims.push_back({name.data(), numframes, animframes});
     124            1 :     return skelanims.back();
     125            2 : }
     126              : 
     127            3 : skelmodel::skeleton::skeleton(skelmeshgroup * const group) :
     128            3 :     numbones(0),
     129            3 :     numgpubones(0),
     130            3 :     numframes(0),
     131            3 :     framebones(nullptr),
     132            3 :     ragdoll(nullptr),
     133            3 :     owner(group),
     134            3 :     numinterpbones(0),
     135            3 :     bones(nullptr)
     136              : {
     137            3 : }
     138              : 
     139            2 : skelmodel::skeleton::~skeleton()
     140              : {
     141            2 :     delete[] bones;
     142            2 :     delete[] framebones;
     143            2 :     if(ragdoll)
     144              :     {
     145            0 :         delete ragdoll;
     146            0 :         ragdoll = nullptr;
     147              :     }
     148            2 :     for(skelcacheentry &i : skelcache)
     149              :     {
     150            0 :         delete[] i.bdata;
     151              :     }
     152            2 : }
     153              : 
     154            4 : std::optional<size_t> skelmodel::skeleton::findbone(std::string_view name) const
     155              : {
     156            6 :     for(size_t i = 0; i < numbones; ++i)
     157              :     {
     158            6 :         if(bones[i].name.size() && bones[i].name == name)
     159              :         {
     160            4 :             return i;
     161              :         }
     162              :     }
     163            0 :     return std::nullopt;
     164              : }
     165              : 
     166            4 : std::optional<size_t> skelmodel::skeleton::findtag(std::string_view name) const
     167              : {
     168            6 :     for(size_t i = 0; i < tags.size(); i++)
     169              :     {
     170            4 :         if(!std::strcmp(tags[i].name.c_str(), name.data()))
     171              :         {
     172            2 :             return i;
     173              :         }
     174              :     }
     175            2 :     return std::nullopt;
     176              : }
     177              : 
     178            2 : bool skelmodel::skeleton::addtag(std::string_view name, int bone, const matrix4x3 &matrix)
     179              : {
     180            2 :     std::optional<size_t> idx = findtag(name);
     181            2 :     if(idx)
     182              :     {
     183            0 :         if(!testtags)
     184              :         {
     185            0 :             return false;
     186              :         }
     187            0 :         tag &t = tags[*idx];
     188            0 :         t.bone = bone;
     189            0 :         t.matrix = matrix;
     190              :     }
     191              :     else
     192              :     {
     193            2 :         tags.emplace_back(name, bone, matrix);
     194              :     }
     195            2 :     return true;
     196              : }
     197              : 
     198           11 : void skelmodel::skeleton::calcantipodes()
     199              : {
     200           11 :     antipodes.clear();
     201           11 :     std::vector<uint> schedule;
     202           55 :     for(size_t i = 0; i < numbones; ++i)
     203              :     {
     204           44 :         if(bones[i].group >= static_cast<int>(numbones))
     205              :         {
     206           44 :             bones[i].scheduled = schedule.size();
     207           44 :             schedule.push_back(i);
     208              :         }
     209              :         else
     210              :         {
     211            0 :             bones[i].scheduled = -1;
     212              :         }
     213              :     }
     214           55 :     for(uint i = 0; i < schedule.size(); i++)
     215              :     {
     216           44 :         const uint bone = schedule[i];
     217           44 :         const boneinfo &info = bones[bone];
     218          220 :         for(size_t j = 0; j < numbones; ++j)
     219              :         {
     220          176 :             if(std::abs(bones[j].group) == bone && bones[j].scheduled < 0)
     221              :             {
     222            0 :                 antipodes.emplace_back(antipode(info.interpindex, bones[j].interpindex));
     223            0 :                 bones[j].scheduled = schedule.size();
     224            0 :                 schedule.push_back(j);
     225              :             }
     226              :         }
     227           44 :         if(i + 1 == schedule.size())
     228              :         {
     229           11 :             std::optional<int> conflict = std::nullopt;
     230           55 :             for(size_t j = 0; j < numbones; ++j)
     231              :             {
     232           44 :                 if(bones[j].group < static_cast<int>(numbones) && bones[j].scheduled < 0)
     233              :                 {
     234            0 :                     conflict = std::min(conflict.value(), std::abs(bones[j].group));
     235              :                 }
     236              :             }
     237           11 :             if(conflict)
     238              :             {
     239            0 :                 bones[conflict.value()].scheduled = schedule.size();
     240            0 :                 schedule.push_back(conflict.value());
     241              :             }
     242              :         }
     243              :     }
     244           11 : }
     245              : 
     246           11 : void skelmodel::skeleton::remapbones()
     247              : {
     248           55 :     for(size_t i = 0; i < numbones; ++i)//loop i
     249              :     {
     250           44 :         boneinfo &info = bones[i];
     251           44 :         info.interpindex = -1;
     252           44 :         info.ragdollindex = -1;
     253              :     }
     254           11 :     numgpubones = 0;
     255           33 :     for(blendcombo &c : owner->blendcombos)
     256              :     {
     257           44 :         for(size_t k = 0; k < c.size(); ++k) //loop k
     258              :         {
     259           22 :             if(!c.bonedata[k].weight)
     260              :             {
     261            0 :                 c.setinterpbones(k > 0 ? c.bonedata[k-1].interpbone : 0, k);
     262            0 :                 continue;
     263              :             }
     264           22 :             boneinfo &info = bones[c.getbone(k)];
     265           22 :             if(info.interpindex < 0)
     266              :             {
     267           22 :                 info.interpindex = numgpubones++;
     268              :             }
     269           22 :             c.setinterpbones(info.interpindex, k);
     270           22 :             if(info.group < 0)
     271              :             {
     272            0 :                 continue;
     273              :             }
     274              :             //for each weight represented, compare with other weights in this blendcombo
     275           44 :             for(size_t l = 0; l < c.size(); ++l) //note this is a loop l (level 4)
     276              :             {
     277           22 :                 if(l == k)
     278              :                 {
     279           22 :                     continue;
     280              :                 }
     281              :                 //get the index of the weight at l
     282            0 :                 int parent = c.getbone(l);
     283              :                 //if k's parent is this bone, or k's parent exists and k's parent is l's parent
     284            0 :                 if(info.parent == parent || (info.parent >= 0 && info.parent == bones[parent].parent))
     285              :                 {
     286              :                     //set k's group to be its parent negated
     287            0 :                     info.group = -info.parent;
     288            0 :                     break;
     289              :                 }
     290              :                 //if k's group is <= l's index
     291            0 :                 if(info.group <= parent)
     292              :                 {
     293            0 :                     continue;
     294              :                 }
     295              :                 //if k's group is > l's index, then k is a child of l (children are assigned higher numbers than parents)
     296            0 :                 int child = c.getbone(k);
     297              :                 //while l's index is greater than k's (implying l is a child of k), change l until l is a parent of k
     298            0 :                 while(parent > child)
     299              :                 {
     300            0 :                     parent = bones[parent].parent;
     301              :                 }
     302              :                 //if l, or one of its parents, is not k, set k's group to the index of the bone at l
     303            0 :                 if(parent != child)
     304              :                 {
     305            0 :                     info.group = c.getbone(l);
     306              :                 }
     307              :             }
     308              :         }
     309              :     }
     310           11 :     numinterpbones = numgpubones;
     311           31 :     for(const tag &i : tags)
     312              :     {
     313           20 :         boneinfo &info = bones[i.bone];
     314           20 :         if(info.interpindex < 0)
     315              :         {
     316           20 :             info.interpindex = numinterpbones++;
     317              :         }
     318              :     }
     319           11 :     if(ragdoll)
     320              :     {
     321            0 :         for(uint i = 0; i < ragdoll->joints.size(); i++)
     322              :         {
     323            0 :             boneinfo &info = bones[ragdoll->joints[i].bone];
     324            0 :             if(info.interpindex < 0)
     325              :             {
     326            0 :                 info.interpindex = numinterpbones++;
     327              :             }
     328            0 :             info.ragdollindex = i;
     329              :         }
     330              :     }
     331           55 :     for(size_t i = 0; i < numbones; ++i)
     332              :     {
     333           44 :         const boneinfo &info = bones[i];
     334           44 :         if(info.interpindex < 0)
     335              :         {
     336            2 :             continue;
     337              :         }
     338           43 :         for(int parent = info.parent; parent >= 0 && bones[parent].interpindex < 0; parent = bones[parent].parent)
     339              :         {
     340            1 :             bones[parent].interpindex = numinterpbones++;
     341              :         }
     342              :     }
     343           55 :     for(size_t i = 0; i < numbones; ++i)
     344              :     {
     345           44 :         boneinfo &info = bones[i];
     346           44 :         if(info.interpindex < 0)
     347              :         {
     348            1 :             continue;
     349              :         }
     350           43 :         info.interpparent = info.parent >= 0 ? bones[info.parent].interpindex : -1;
     351              :     }
     352           11 :     if(ragdoll)
     353              :     {
     354            0 :         for(size_t i = 0; i < numbones; ++i)
     355              :         {
     356            0 :             const boneinfo &info = bones[i];
     357            0 :             if(info.interpindex < 0 || info.ragdollindex >= 0)
     358              :             {
     359            0 :                 continue;
     360              :             }
     361            0 :             for(int parent = info.parent; parent >= 0; parent = bones[parent].parent)
     362              :             {
     363            0 :                 if(bones[parent].ragdollindex >= 0)
     364              :                 {
     365            0 :                     ragdoll->addreljoint(i, bones[parent].ragdollindex);
     366            0 :                     break;
     367              :                 }
     368              :             }
     369              :         }
     370              :     }
     371           11 :     calcantipodes();
     372           11 : }
     373              : 
     374            9 : void skelmodel::skeleton::addpitchdep(int bone, int frame)
     375              : {
     376           27 :     for(; bone >= 0; bone = bones[bone].parent)
     377              :     {
     378           18 :         uint pos = pitchdeps.size();
     379           18 :         for(uint j = 0; j < pitchdeps.size(); j++)
     380              :         {
     381            9 :             if(bone <= pitchdeps[j].bone)
     382              :             {
     383            9 :                 if(bone == pitchdeps[j].bone)
     384              :                 {
     385            0 :                     goto nextbone;
     386              :                 }
     387            9 :                 pos = j;
     388            9 :                 break;
     389              :             }
     390              :         }
     391              :         { //if goto nextbone not called (note: no control logic w/ braces)
     392           18 :             pitchdep d;
     393           18 :             d.bone = bone;
     394           18 :             d.parent = -1;
     395           18 :             d.pose = framebones[frame*numbones + bone];
     396           18 :             pitchdeps.insert(pitchdeps.begin() + pos, d);
     397              :         }
     398           18 :     nextbone:;
     399              :     }
     400            9 : }
     401              : 
     402           18 : std::optional<size_t> skelmodel::skeleton::findpitchdep(int bone) const
     403              : {
     404           27 :     for(uint i = 0; i < pitchdeps.size(); i++)
     405              :     {
     406           27 :         if(bone <= pitchdeps[i].bone)
     407              :         {
     408           18 :             return bone == pitchdeps[i].bone ? std::optional<uint>(i) : std::optional<uint>(std::nullopt);
     409              :         }
     410              :     }
     411            0 :     return std::nullopt;
     412              : }
     413              : 
     414           18 : std::optional<size_t> skelmodel::skeleton::findpitchcorrect(int bone) const
     415              : {
     416           18 :     for(uint i = 0; i < pitchcorrects.size(); i++)
     417              :     {
     418            0 :         if(bone <= pitchcorrects[i].bone)
     419              :         {
     420            0 :             return bone == pitchcorrects[i].bone ? std::optional<uint>(i) : std::optional<uint>(std::nullopt);
     421              :         }
     422              :     }
     423           18 :     return std::nullopt;
     424              : }
     425              : 
     426           11 : void skelmodel::skeleton::initpitchdeps()
     427              : {
     428           11 :     pitchdeps.clear();
     429           11 :     if(pitchtargets.empty())
     430              :     {
     431            2 :         return;
     432              :     }
     433           18 :     for(pitchtarget &t : pitchtargets)
     434              :     {
     435            9 :         t.deps = -1;
     436            9 :         addpitchdep(t.bone, t.frame);
     437              :     }
     438           27 :     for(pitchdep &d : pitchdeps)
     439              :     {
     440           18 :         int parent = bones[d.bone].parent;
     441           18 :         if(parent >= 0)
     442              :         {
     443            9 :             std::optional<size_t> j = findpitchdep(parent);
     444            9 :             if(j)
     445              :             {
     446            9 :                 d.parent = j.value();
     447            9 :                 d.pose.mul(pitchdeps[j.value()].pose, dualquat(d.pose));
     448              :             }
     449              :         }
     450              :     }
     451           18 :     for(pitchtarget &t : pitchtargets)
     452              :     {
     453            9 :         std::optional<size_t> j = findpitchdep(t.bone);
     454            9 :         if(j)
     455              :         {
     456            9 :             t.deps = j.value();
     457            9 :             t.pose = pitchdeps[j.value()].pose;
     458              :         }
     459            9 :         t.corrects = -1;
     460           27 :         for(int parent = t.bone; parent >= 0; parent = bones[parent].parent)
     461              :         {
     462           18 :             std::optional<size_t> newpitchcorrect = findpitchcorrect(parent);
     463           18 :             t.corrects = newpitchcorrect ? newpitchcorrect.value() : -1;
     464           18 :             if(t.corrects >= 0)
     465              :             {
     466            0 :                 break;
     467              :             }
     468              :         }
     469              :     }
     470            9 :     for(uint i = 0; i < pitchcorrects.size(); i++)
     471              :     {
     472            0 :         pitchcorrect &c = pitchcorrects[i];
     473            0 :         bones[c.bone].correctindex = i;
     474            0 :         c.parent = -1;
     475            0 :         for(int parent = c.bone;;)
     476              :         {
     477            0 :             parent = bones[parent].parent;
     478            0 :             if(parent < 0)
     479              :             {
     480            0 :                 break;
     481              :             }
     482            0 :             std::optional<size_t> newpitchcorrect = findpitchcorrect(parent);
     483            0 :             c.parent = newpitchcorrect ? newpitchcorrect.value() : -1;
     484            0 :             if(c.parent >= 0)
     485              :             {
     486            0 :                 break;
     487              :             }
     488            0 :         }
     489              :     }
     490              : }
     491              : 
     492           11 : void skelmodel::skeleton::optimize()
     493              : {
     494           11 :     cleanup();
     495           11 :     if(ragdoll)
     496              :     {
     497            0 :         ragdoll->setup();
     498              :     }
     499           11 :     remapbones();
     500           11 :     initpitchdeps();
     501           11 : }
     502              : 
     503            4 : void skelmodel::skeleton::expandbonemask(uchar *expansion, int bone, int val) const
     504              : {
     505            4 :     expansion[bone] = val;
     506            4 :     bone = bones[bone].children;
     507            7 :     while(bone>=0)
     508              :     {
     509            3 :         expandbonemask(expansion, bone, val);
     510            3 :         bone = bones[bone].next;
     511              :     }
     512            4 : }
     513              : 
     514            1 : void skelmodel::skeleton::applybonemask(const std::vector<uint> &mask, std::vector<uchar> &partmask, int partindex) const
     515              : {
     516            1 :     if(mask.empty() || mask.front()==Bonemask_End)
     517              :     {
     518            0 :         return;
     519              :     }
     520            1 :     uchar *expansion = new uchar[numbones];
     521            1 :     std::memset(expansion, mask.front()&Bonemask_Not ? 1 : 0, numbones);
     522            2 :     for(const uint &i : mask)
     523              :     {
     524            2 :         if(i == Bonemask_End)
     525              :         {
     526            1 :             break;
     527              :         }
     528            1 :         expandbonemask(expansion, i&Bonemask_Bone, i&Bonemask_Not ? 0 : 1);
     529              :     }
     530            5 :     for(size_t i = 0; i < numbones; ++i)
     531              :     {
     532            4 :         if(expansion[i])
     533              :         {
     534            4 :             partmask[i] = partindex;
     535              :         }
     536              :     }
     537            1 :     delete[] expansion;
     538              : }
     539              : 
     540            1 : void skelmodel::skeleton::linkchildren()
     541              : {
     542            5 :     for(size_t i = 0; i < numbones; ++i)
     543              :     {
     544            4 :         boneinfo &b = bones[i];
     545            4 :         b.children = -1;
     546            4 :         if(b.parent<0)
     547              :         {
     548            1 :             b.next = -1;
     549              :         }
     550              :         else
     551              :         {
     552            3 :             b.next = bones[b.parent].children;
     553            3 :             bones[b.parent].children = i;
     554              :         }
     555              :     }
     556            1 : }
     557              : 
     558            0 : int skelmodel::skeleton::availgpubones()
     559              : {
     560            0 :     return std::min(maxvsuniforms, maxskelanimdata) / 2;
     561              : }
     562              : 
     563            0 : float skelmodel::skeleton::calcdeviation(const vec &axis, const vec &forward, const dualquat &pose1, const dualquat &pose2)
     564              : {
     565            0 :     const vec forward1 = pose1.transformnormal(forward).project(axis).normalize(),
     566            0 :               forward2 = pose2.transformnormal(forward).project(axis).normalize(),
     567            0 :               daxis = vec().cross(forward1, forward2);
     568            0 :     const float dx = std::clamp(forward1.dot(forward2), -1.0f, 1.0f);
     569            0 :     float dy = std::clamp(daxis.magnitude(), -1.0f, 1.0f);
     570            0 :     if(daxis.dot(axis) < 0)
     571              :     {
     572            0 :         dy = -dy;
     573              :     }
     574            0 :     return atan2f(dy, dx)*RAD;
     575              : }
     576              : 
     577            0 : void skelmodel::skeleton::calcpitchcorrects(float pitch, const vec &axis, const vec &forward)
     578              : {
     579            0 :     for(pitchtarget &t : pitchtargets)
     580              :     {
     581            0 :         t.deviated = calcdeviation(axis, forward, t.pose, pitchdeps[t.deps].pose);
     582              :     }
     583            0 :     for(pitchcorrect &c : pitchcorrects)
     584              :     {
     585            0 :         c.pitchangle = c.pitchtotal = 0;
     586              :     }
     587            0 :     for(uint j = 0; j < pitchtargets.size(); j++)
     588              :     {
     589            0 :         const pitchtarget &t = pitchtargets[j];
     590            0 :         float tpitch = pitch - t.deviated;
     591            0 :         for(int parent = t.corrects; parent >= 0; parent = pitchcorrects[parent].parent)
     592              :         {
     593            0 :             tpitch -= pitchcorrects[parent].pitchangle;
     594              :         }
     595            0 :         if(t.pitchmin || t.pitchmax)
     596              :         {
     597            0 :             tpitch = std::clamp(tpitch, t.pitchmin, t.pitchmax);
     598              :         }
     599            0 :         for(pitchcorrect& c : pitchcorrects)
     600              :         {
     601            0 :             if(c.target != j)
     602              :             {
     603            0 :                 continue;
     604              :             }
     605            0 :             float total = c.parent >= 0 ? pitchcorrects[c.parent].pitchtotal : 0,
     606            0 :                   avail = tpitch - total,
     607            0 :                   used = tpitch*c.pitchscale;
     608            0 :             if(c.pitchmin || c.pitchmax)
     609              :             {
     610            0 :                 if(used < 0)
     611              :                 {
     612            0 :                     used = std::clamp(c.pitchmin, used, 0.0f);
     613              :                 }
     614              :                 else
     615              :                 {
     616            0 :                     used = std::clamp(c.pitchmax, 0.0f, used);
     617              :                 }
     618              :             }
     619            0 :             if(used < 0)
     620              :             {
     621            0 :                 used = std::clamp(avail, used, 0.0f);
     622              :             }
     623              :             else
     624              :             {
     625            0 :                 used = std::clamp(avail, 0.0f, used);
     626              :             }
     627            0 :             c.pitchangle = used;
     628            0 :             c.pitchtotal = used + total;
     629              :         }
     630              :     }
     631            0 : }
     632              : 
     633              : //private helper function for interpbones
     634            0 : dualquat skelmodel::skeleton::interpbone(int bone, const std::array<framedata, maxanimparts> &partframes, const AnimState *as, const uchar *partmask) const
     635              : {
     636            0 :     const AnimState &s = as[partmask[bone]];
     637            0 :     const framedata &f = partframes[partmask[bone]];
     638            0 :     dualquat d;
     639            0 :     (d = f.fr1[bone]).mul((1-s.cur.t)*s.interp);
     640            0 :     d.accumulate(f.fr2[bone], s.cur.t*s.interp);
     641            0 :     if(s.interp<1)
     642              :     {
     643            0 :         d.accumulate(f.pfr1[bone], (1-s.prev.t)*(1-s.interp));
     644            0 :         d.accumulate(f.pfr2[bone], s.prev.t*(1-s.interp));
     645              :     }
     646            0 :     return d;
     647              : }
     648              : 
     649            0 : void skelmodel::skeleton::interpbones(const AnimState *as, float pitch, const vec &axis, const vec &forward, int numanimparts, const uchar *partmask, skelcacheentry &sc)
     650              : {
     651            0 :     if(!sc.bdata)
     652              :     {
     653            0 :         sc.bdata = new dualquat[numinterpbones];
     654              :     }
     655            0 :     sc.nextversion();
     656              :     std::array<framedata, maxanimparts> partframes;
     657            0 :     for(int i = 0; i < numanimparts; ++i)
     658              :     {
     659            0 :         partframes[i].fr1 = &framebones[as[i].cur.fr1*numbones];
     660            0 :         partframes[i].fr2 = &framebones[as[i].cur.fr2*numbones];
     661            0 :         if(as[i].interp<1)
     662              :         {
     663            0 :             partframes[i].pfr1 = &framebones[as[i].prev.fr1*numbones];
     664            0 :             partframes[i].pfr2 = &framebones[as[i].prev.fr2*numbones];
     665              :         }
     666              :     }
     667            0 :     for(pitchdep &p : pitchdeps)
     668              :     {
     669            0 :         dualquat d = interpbone(p.bone, partframes, as, partmask);
     670            0 :         d.normalize();
     671            0 :         if(p.parent >= 0)
     672              :         {
     673            0 :             p.pose.mul(pitchdeps[p.parent].pose, d);
     674              :         }
     675              :         else
     676              :         {
     677            0 :             p.pose = d;
     678              :         }
     679              :     }
     680            0 :     calcpitchcorrects(pitch, axis, forward);
     681            0 :     for(size_t i = 0; i < numbones; ++i)
     682              :     {
     683            0 :         if(bones[i].interpindex>=0)
     684              :         {
     685            0 :             dualquat d = interpbone(i, partframes, as, partmask);
     686            0 :             d.normalize();
     687            0 :             const boneinfo &b = bones[i];
     688            0 :             if(b.interpparent<0)
     689              :             {
     690            0 :                 sc.bdata[b.interpindex] = d;
     691              :             }
     692              :             else
     693              :             {
     694            0 :                 sc.bdata[b.interpindex].mul(sc.bdata[b.interpparent], d);
     695              :             }
     696              :             float angle;
     697            0 :             if(b.pitchscale)
     698              :             {
     699            0 :                 angle = b.pitchscale*pitch + b.pitchoffset;
     700            0 :                 if(b.pitchmin || b.pitchmax)
     701              :                 {
     702            0 :                     angle = std::clamp(angle, b.pitchmin, b.pitchmax);
     703              :                 }
     704              :             }
     705            0 :             else if(b.correctindex >= 0)
     706              :             {
     707            0 :                 angle = pitchcorrects[b.correctindex].pitchangle;
     708              :             }
     709              :             else
     710              :             {
     711            0 :                 continue;
     712              :             }
     713            0 :             if(as->cur.anim & Anim_NoPitch || (as->interp < 1 && as->prev.anim & Anim_NoPitch))
     714              :             {
     715            0 :                 angle *= (as->cur.anim & Anim_NoPitch ? 0 : as->interp) + (as->interp < 1 && as->prev.anim & Anim_NoPitch ? 0 : 1 - as->interp);
     716              :             }
     717            0 :             sc.bdata[b.interpindex].mulorient(quat(axis, angle/RAD), b.base);
     718              :         }
     719              :     }
     720            0 :     for(const antipode &i : antipodes)
     721              :     {
     722            0 :         sc.bdata[i.child].fixantipodal(sc.bdata[i.parent]);
     723              :     }
     724            0 : }
     725              : 
     726            0 : void skelmodel::skeleton::initragdoll(ragdolldata &d, const skelcacheentry &sc, float scale) const
     727              : {
     728            0 :     const dualquat *bdata = sc.bdata;
     729            0 :     for(const ragdollskel::joint &j : ragdoll->joints)
     730              :     {
     731            0 :         const boneinfo &b = bones[j.bone];
     732            0 :         const dualquat &q = bdata[b.interpindex];
     733            0 :         for(int k = 0; k < 3; ++k)
     734              :         {
     735            0 :             if(j.vert[k] >= 0)
     736              :             {
     737            0 :                 const ragdollskel::vert &v = ragdoll->verts[j.vert[k]];
     738            0 :                 ragdolldata::vert &dv = d.verts[j.vert[k]];
     739            0 :                 dv.pos.add(q.transform(v.pos).mul(v.weight));
     740              :             }
     741              :         }
     742              :     }
     743            0 :     if(ragdoll->animjoints)
     744              :     {
     745            0 :         for(size_t i = 0; i < ragdoll->joints.size(); i++)
     746              :         {
     747            0 :             const ragdollskel::joint &j = ragdoll->joints[i];
     748            0 :             const boneinfo &b = bones[j.bone];
     749            0 :             const dualquat &q = bdata[b.interpindex];
     750            0 :             d.animjoints[i] = d.calcanimjoint(i, matrix4x3(q));
     751              :         }
     752              :     }
     753            0 :     for(size_t i = 0; i < ragdoll->verts.size(); i++)
     754              :     {
     755            0 :         ragdolldata::vert &dv = d.verts[i];
     756            0 :         matrixstack.top().transform(vec(dv.pos).mul(scale), dv.pos);
     757              :     }
     758            0 :     for(size_t i = 0; i < ragdoll->reljoints.size(); i++)
     759              :     {
     760            0 :         const ragdollskel::reljoint &r = ragdoll->reljoints[i];
     761            0 :         const ragdollskel::joint &j = ragdoll->joints[r.parent];
     762            0 :         const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
     763            0 :         d.reljoints[i].mul(dualquat(bdata[bj.interpindex]).invert(), bdata[br.interpindex]);
     764              :     }
     765            0 : }
     766              : 
     767            0 : void skelmodel::skeleton::genragdollbones(const ragdolldata &d, skelcacheentry &sc, const vec &translate, float scale) const
     768              : {
     769            0 :     if(!sc.bdata)
     770              :     {
     771            0 :         sc.bdata = new dualquat[numinterpbones];
     772              :     }
     773            0 :     sc.nextversion();
     774            0 :     const vec trans = vec(d.center).div(scale).add(translate);
     775            0 :     for(size_t i = 0; i < ragdoll->joints.size(); i++)
     776              :     {
     777            0 :         const ragdollskel::joint &j = ragdoll->joints[i];
     778            0 :         const boneinfo &b = bones[j.bone];
     779            0 :         vec pos(0, 0, 0);
     780            0 :         for(int k = 0; k < 3; ++k)
     781              :         {
     782            0 :             if(j.vert[k]>=0)
     783              :             {
     784            0 :                 pos.add(d.verts[j.vert[k]].pos);
     785              :             }
     786              :         }
     787            0 :         pos.mul(j.weight/scale).sub(trans);
     788            0 :         matrix4x3 m;
     789            0 :         m.mul(d.tris[j.tri], pos, d.animjoints ? d.animjoints[i] : j.orient);
     790            0 :         sc.bdata[b.interpindex] = dualquat(m);
     791              :     }
     792            0 :     for(size_t i = 0; i < ragdoll->reljoints.size(); i++)
     793              :     {
     794            0 :         const ragdollskel::reljoint &r = ragdoll->reljoints[i];
     795            0 :         const ragdollskel::joint &j = ragdoll->joints[r.parent];
     796            0 :         const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
     797            0 :         sc.bdata[br.interpindex].mul(sc.bdata[bj.interpindex], d.reljoints[i]);
     798              :     }
     799            0 :     for(const antipode &i : antipodes)
     800              :     {
     801            0 :         sc.bdata[i.child].fixantipodal(sc.bdata[i.parent]);
     802              :     }
     803            0 : }
     804              : 
     805            0 : void skelmodel::skeleton::concattagtransform(int i, const matrix4x3 &m, matrix4x3 &n) const
     806              : {
     807            0 :     matrix4x3 t;
     808            0 :     t.mul(bones[tags[i].bone].base, tags[i].matrix);
     809            0 :     n.mul(m, t);
     810            0 : }
     811              : 
     812            0 : void skelmodel::skeleton::calctags(part *p, const skelcacheentry *sc) const
     813              : {
     814            0 :     for(part::linkedpart &l : p->links)
     815              :     {
     816            0 :         const tag &t = tags[l.tag];
     817            0 :         dualquat q;
     818            0 :         if(sc)
     819              :         {
     820            0 :             q.mul(sc->bdata[bones[t.bone].interpindex], bones[t.bone].base);
     821              :         }
     822              :         else
     823              :         {
     824            0 :             q = bones[t.bone].base;
     825              :         }
     826            0 :         matrix4x3 m;
     827            0 :         m.mul(q, t.matrix);
     828            0 :         m.d.mul(p->model->locationsize().w * sizescale);
     829            0 :         l.matrix = m;
     830              :     }
     831            0 : }
     832              : 
     833           22 : void skelmodel::skeleton::cleanup(bool full)
     834              : {
     835           22 :     for(skelcacheentry &sc : skelcache)
     836              :     {
     837            0 :         for(int j = 0; j < maxanimparts; ++j)
     838              :         {
     839            0 :             sc.as[j].cur.fr1 = -1; //skelcache animstate @ j's cur AnimPos fr1 (frame1?)
     840              :         }
     841            0 :         delete[] sc.bdata;
     842            0 :         sc.bdata = nullptr;
     843              :     }
     844           22 :     skelcache.clear();
     845           22 :     blendoffsets.clear();
     846           22 :     if(full)
     847              :     {
     848           11 :         owner->cleanup();
     849              :     }
     850           22 : }
     851              : 
     852            0 : const skelmodel::skelcacheentry &skelmodel::skeleton::checkskelcache(const vec &pos, float scale,  const AnimState *as, float pitch, const vec &axis, const vec &forward, const ragdolldata * const rdata)
     853              : {
     854            0 :     const int numanimparts = as->owner->numanimparts;
     855            0 :     const std::vector<uchar> &partmask = (reinterpret_cast<const skelpart *>(as->owner))->partmask;
     856            0 :     skelcacheentry *sc = nullptr;
     857            0 :     bool match = false;
     858            0 :     for(skelcacheentry &c : skelcache)
     859              :     {
     860            0 :         for(int j = 0; j < numanimparts; ++j)
     861              :         {
     862            0 :             if(c.as[j]!=as[j])
     863              :             {
     864            0 :                 goto mismatch;
     865              :             }
     866              :         }
     867            0 :         if(c.pitch != pitch || *c.partmask != partmask || c.ragdoll != rdata || (rdata && c.millis < rdata->lastmove))
     868              :         {
     869            0 :             goto mismatch;
     870              :         }
     871            0 :         match = true;
     872            0 :         sc = &c;
     873            0 :         break;
     874            0 :     mismatch:
     875            0 :         if(c.millis < lastmillis)
     876              :         {
     877            0 :             sc = &c;
     878            0 :             break;
     879              :         }
     880              :     }
     881            0 :     if(!sc)
     882              :     {
     883            0 :         skelcache.emplace_back(skelcacheentry());
     884            0 :         sc = &skelcache.back();
     885              :     }
     886            0 :     if(!match)
     887              :     {
     888            0 :         for(int i = 0; i < numanimparts; ++i)
     889              :         {
     890            0 :             sc->as[i] = as[i];
     891              :         }
     892            0 :         sc->pitch = pitch;
     893            0 :         sc->partmask = &partmask;
     894            0 :         sc->ragdoll = rdata;
     895            0 :         if(rdata)
     896              :         {
     897            0 :             genragdollbones(*rdata, *sc, pos, scale);
     898              :         }
     899              :         else
     900              :         {
     901            0 :             interpbones(as, pitch, axis, forward, numanimparts, partmask.data(), *sc);
     902              :         }
     903              :     }
     904            0 :     sc->millis = lastmillis;
     905            0 :     return *sc;
     906              : }
     907              : 
     908            0 : GLint skelmodel::skeleton::getblendoffset(const UniformLoc &u)
     909              : {
     910            0 :     auto itr = blendoffsets.find(Shader::lastshader->program);
     911            0 :     if(itr == blendoffsets.end())
     912              :     {
     913            0 :         itr = blendoffsets.insert( { Shader::lastshader->program, -1 } ).first;
     914            0 :         DEF_FORMAT_STRING(offsetname, "%s[%d]", u.name, 2*numgpubones);
     915            0 :         (*itr).second = glGetUniformLocation(Shader::lastshader->program, offsetname);
     916              :     }
     917            0 :     return (*itr).second;
     918              : }
     919              : 
     920            0 : void skelmodel::skeleton::setglslbones(UniformLoc &u, const skelcacheentry &sc, const skelcacheentry &bc, int count)
     921              : {
     922            0 :     if(u.version == bc.version && u.data == bc.bdata)
     923              :     {
     924            0 :         return;
     925              :     }
     926            0 :     glUniform4fv(u.loc, 2*numgpubones, sc.bdata[0].real.data());
     927            0 :     if(count > 0)
     928              :     {
     929            0 :         GLint offset = getblendoffset(u);
     930            0 :         if(offset >= 0)
     931              :         {
     932            0 :             glUniform4fv(offset, 2*count, bc.bdata[0].real.data());
     933              :         }
     934              :     }
     935            0 :     u.version = bc.version;
     936            0 :     u.data = bc.bdata;
     937              : }
     938              : 
     939            0 : void skelmodel::skeleton::setgpubones(const skelcacheentry &sc, const blendcacheentry *bc, int count)
     940              : {
     941            0 :     if(!Shader::lastshader)
     942              :     {
     943            0 :         return;
     944              :     }
     945            0 :     if(Shader::lastshader->uniformlocs.size() < 1)
     946              :     {
     947            0 :         return;
     948              :     }
     949            0 :     UniformLoc &u = Shader::lastshader->uniformlocs[0];
     950            0 :     setglslbones(u, sc, bc ? *bc : sc, count);
     951              : }
     952              : 
     953            0 : bool skelmodel::skeleton::shouldcleanup() const
     954              : {
     955            0 :     return numframes && skelcache.empty();
     956              : }
     957              : 
     958            0 : bool skelmodel::skeleton::setbonepitch(size_t index, float scale, float offset, float min, float max)
     959              : {
     960            0 :     if(index > numbones)
     961              :     {
     962            0 :         return false;
     963              :     }
     964            0 :     boneinfo &b = bones[index];
     965            0 :     b.pitchscale = scale;
     966            0 :     b.pitchoffset = offset;
     967            0 :     b.pitchmin = min;
     968            0 :     b.pitchmax = max;
     969            0 :     return true;
     970              : }
     971              : 
     972            7 : std::optional<dualquat> skelmodel::skeleton::getbonebase(size_t index) const
     973              : {
     974            7 :     if(index > numbones)
     975              :     {
     976            0 :         return std::nullopt;
     977              :     }
     978            7 :     return bones[index].base;
     979              : }
     980              : 
     981            1 : bool skelmodel::skeleton::setbonebases(const std::vector<dualquat> &bases)
     982              : {
     983            1 :     if(bases.size() != numbones)
     984              :     {
     985            0 :         return false;
     986              :     }
     987            5 :     for(size_t i = 0; i < numbones; ++i)
     988              :     {
     989            4 :         bones[i].base = bases[i];
     990              :     }
     991            1 :     return true;
     992              : }
     993              : 
     994            4 : bool skelmodel::skeleton::setbonename(size_t index, std::string_view name)
     995              : {
     996            4 :     if(index > numbones)
     997              :     {
     998            0 :         return false;
     999              :     }
    1000            4 :     boneinfo &b = bones[index];
    1001            4 :     if(!b.name.size())
    1002              :     {
    1003            4 :         b.name = name;
    1004            4 :         return true;
    1005              :     }
    1006            0 :     return false;
    1007              : }
    1008              : 
    1009            4 : bool skelmodel::skeleton::setboneparent(size_t index, size_t parent)
    1010              : {
    1011            4 :     if(index > numbones || parent > numbones)
    1012              :     {
    1013            1 :         return false;
    1014              :     }
    1015            3 :     boneinfo &b = bones[index];
    1016            3 :     b.parent = parent;
    1017            3 :     return true;
    1018              : }
    1019              : 
    1020            1 : void skelmodel::skeleton::createbones(size_t num)
    1021              : {
    1022            1 :     numbones = num;
    1023            5 :     bones = new boneinfo[numbones];
    1024            1 : }
    1025              : 
    1026            0 : ragdollskel *skelmodel::skeleton::trycreateragdoll()
    1027              : {
    1028            0 :     if(!ragdoll)
    1029              :     {
    1030            0 :         ragdoll = new ragdollskel;
    1031              :     }
    1032            0 :     return ragdoll;
    1033              : }
    1034              : 
    1035            2 : skelmodel::skelmeshgroup::~skelmeshgroup()
    1036              : {
    1037            2 :     if(skel)
    1038              :     {
    1039            2 :         delete skel;
    1040            2 :         skel = nullptr;
    1041              :     }
    1042            2 :     if(ebuf)
    1043              :     {
    1044            0 :         glDeleteBuffers(1, &ebuf);
    1045              :     }
    1046           34 :     for(size_t i = 0; i < maxblendcache; ++i)
    1047              :     {
    1048           32 :         delete[] blendcache[i].bdata;
    1049              :     }
    1050           34 :     for(size_t i = 0; i < maxvbocache; ++i)
    1051              :     {
    1052           32 :         if(vbocache[i].vbuf)
    1053              :         {
    1054            0 :             glDeleteBuffers(1, &vbocache[i].vbuf);
    1055              :         }
    1056              :     }
    1057            2 :     delete[] vdata;
    1058            2 : }
    1059              : 
    1060            0 : void skelmodel::skelmeshgroup::genvbo(vbocacheentry &vc)
    1061              : {
    1062            0 :     if(!vc.vbuf)
    1063              :     {
    1064            0 :         glGenBuffers(1, &vc.vbuf);
    1065              :     }
    1066            0 :     if(ebuf)
    1067              :     {
    1068            0 :         return;
    1069              :     }
    1070              : 
    1071            0 :     std::vector<GLuint> idxs;
    1072              : 
    1073            0 :     vlen = 0;
    1074            0 :     vblends = 0;
    1075              : 
    1076            0 :     if(skel->numframes)
    1077              :     {
    1078            0 :         vweights = 4;
    1079            0 :         int availbones = skel->availgpubones() - skel->numgpubones;
    1080            0 :         while(vweights > 1 && availbones >= numblends[vweights-1])
    1081              :         {
    1082            0 :             availbones -= numblends[--vweights];
    1083              :         }
    1084            0 :         for(blendcombo &c : blendcombos)
    1085              :         {
    1086            0 :             c.interpindex = static_cast<int>(c.size()) > vweights ? skel->numgpubones + vblends++ : -1;
    1087              :         }
    1088              :     }
    1089              :     else
    1090              :     {
    1091            0 :         vweights = 0;
    1092            0 :         for(blendcombo &i : blendcombos)
    1093              :         {
    1094            0 :             i.interpindex = -1;
    1095              :         }
    1096              :     }
    1097              : 
    1098            0 :     gle::bindvbo(vc.vbuf);
    1099            0 :     auto rendermeshes = getrendermeshes();
    1100            0 :     if(skel->numframes)
    1101              :     {
    1102            0 :         vertsize = sizeof(vvertgw);//silent parameter to genvbo()
    1103            0 :         std::vector<vvertgw> vvertgws;
    1104            0 :         for(const auto &i : rendermeshes)
    1105              :         {
    1106            0 :             vlen += static_cast<skelmesh *>(*i)->genvbo(blendcombos, idxs, vlen, vvertgws);
    1107              :         }
    1108            0 :         glBufferData(GL_ARRAY_BUFFER, vvertgws.size()*sizeof(vvertgw), vvertgws.data(), GL_STATIC_DRAW);
    1109            0 :     }
    1110              :     else
    1111              :     {
    1112            0 :         int numverts = 0,
    1113            0 :             htlen = 128;
    1114            0 :         for(auto i : rendermeshes)
    1115              :         {
    1116            0 :             numverts += static_cast<skelmesh *>(*i)->vertcount();
    1117              :         }
    1118            0 :         while(htlen < numverts)
    1119              :         {
    1120            0 :             htlen *= 2;
    1121              :         }
    1122            0 :         if(numverts*4 > htlen*3)
    1123              :         {
    1124            0 :             htlen *= 2;
    1125              :         }
    1126            0 :         int *htdata = new int[htlen];
    1127            0 :         std::memset(htdata, -1, htlen*sizeof(int));
    1128            0 :         vertsize = sizeof(vvertg); //silent parameter to genvbo()
    1129            0 :         std::vector<vvertg> vvertgs;
    1130            0 :         for(const auto &i : rendermeshes)
    1131              :         {
    1132            0 :             vlen += static_cast<skelmesh *>(*i)->genvbo(idxs, vlen, vvertgs, htdata, htlen);
    1133              :         }
    1134            0 :         glBufferData(GL_ARRAY_BUFFER, vvertgs.size()*sizeof(vvertg), vvertgs.data(), GL_STATIC_DRAW);
    1135            0 :         delete[] htdata;
    1136            0 :     }
    1137            0 :     gle::clearvbo();
    1138              : 
    1139            0 :     glGenBuffers(1, &ebuf);
    1140            0 :     gle::bindebo(ebuf);
    1141            0 :     glBufferData(GL_ELEMENT_ARRAY_BUFFER, idxs.size()*sizeof(GLuint), idxs.data(), GL_STATIC_DRAW);
    1142            0 :     gle::clearebo();
    1143            0 : }
    1144              : 
    1145            0 : void skelmodel::skelmeshgroup::render(const AnimState *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p)
    1146              : {
    1147            0 :     if(skel->shouldcleanup())
    1148              :     {
    1149            0 :         skel->cleanup();
    1150            0 :         disablevbo();
    1151              :     }
    1152              : 
    1153            0 :     if(!skel->numframes)
    1154              :     {
    1155            0 :         if(!(as->cur.anim & Anim_NoRender))
    1156              :         {
    1157            0 :             if(!vbocache[0].vbuf)
    1158              :             {
    1159            0 :                 genvbo(vbocache[0]);
    1160              :             }
    1161            0 :             bindvbo(as, p, vbocache[0]);
    1162            0 :             LOOP_RENDER_MESHES(skelmesh, m,
    1163              :             {
    1164              :                 p->skins[i].bind(m, as, true, vweights);
    1165              :                 m.render();
    1166              :             });
    1167              :         }
    1168            0 :         skel->calctags(p);
    1169            0 :         return;
    1170              :     }
    1171              : 
    1172            0 :     const vec ploc = vec(p->model->locationsize().x, p->model->locationsize().y, p->model->locationsize().z);
    1173              : 
    1174            0 :     const skelcacheentry &sc = skel->checkskelcache(ploc, p->model->locationsize().w, as, pitch, axis, forward, !d || !d->ragdoll || d->ragdoll->skel != skel->ragdoll || d->ragdoll->millis == lastmillis ? nullptr : d->ragdoll);
    1175            0 :     if(!(as->cur.anim & Anim_NoRender))
    1176              :     {
    1177            0 :         int owner = &sc-&skel->skelcache[0];
    1178            0 :         vbocacheentry &vc = vbocache[0];
    1179            0 :         vc.millis = lastmillis;
    1180            0 :         if(!vc.vbuf)
    1181              :         {
    1182            0 :             genvbo(vc);
    1183              :         }
    1184            0 :         blendcacheentry *bc = nullptr;
    1185            0 :         if(vblends)
    1186              :         {
    1187            0 :             bc = &checkblendcache(sc, owner);
    1188            0 :             bc->millis = lastmillis;
    1189            0 :             if(bc->owner!=owner)
    1190              :             {
    1191            0 :                 bc->owner = owner;
    1192            0 :                 *reinterpret_cast<animcacheentry *>(bc) = sc;
    1193            0 :                 blendbones(sc, *bc);
    1194              :             }
    1195              :         }
    1196              : 
    1197            0 :         bindvbo(as, p, vc);
    1198              : 
    1199            0 :         LOOP_RENDER_MESHES(skelmesh, m,
    1200              :         {
    1201              :             p->skins[i].bind(m, as, true, vweights);
    1202              :             skel->setgpubones(sc, bc, vblends);
    1203              :             m.render();
    1204              :         });
    1205              :     }
    1206              : 
    1207            0 :     skel->calctags(p, &sc);
    1208              : 
    1209            0 :     if(as->cur.anim & Anim_Ragdoll && skel->ragdoll && !d->ragdoll)
    1210              :     {
    1211            0 :         d->ragdoll = new ragdolldata(skel->ragdoll, p->model->locationsize().w);
    1212            0 :         skel->initragdoll(*d->ragdoll, sc, p->model->locationsize().w);
    1213            0 :         d->ragdoll->init(d);
    1214              :     }
    1215              : }
    1216              : 
    1217            0 : void skelmodel::skelmeshgroup::bindbones(const vvertgw *vverts)
    1218              : {
    1219            0 :     meshgroup::bindbones(vverts->weights, vverts->bones, vertsize);
    1220            0 : }
    1221              : 
    1222              : //blendcombo
    1223              : 
    1224          478 : skelmodel::blendcombo::blendcombo() : uses(1)
    1225              : {
    1226          478 :     bonedata.fill({0,0,0});
    1227          478 : }
    1228              : 
    1229          499 : bool skelmodel::blendcombo::operator==(const blendcombo &c) const
    1230              : {
    1231         4510 :     for(size_t i = 0; i < bonedata.size(); ++i)
    1232              :     {
    1233         3572 :         if((bonedata[i].bone != c.bonedata[i].bone) ||
    1234         1756 :            (bonedata[i].weight != c.bonedata[i].weight))
    1235              :         {
    1236           60 :             return false;
    1237              :         }
    1238              :     }
    1239          439 :     return true;
    1240              : }
    1241              : 
    1242          533 : size_t skelmodel::blendcombo::size() const
    1243              : {
    1244          533 :     size_t i = 1;
    1245         1078 :     while(i < bonedata.size() && bonedata[i].weight)
    1246              :     {
    1247            6 :         i++;
    1248              :     }
    1249          533 :     return i;
    1250              : }
    1251              : 
    1252            6 : bool skelmodel::blendcombo::sortcmp(const blendcombo &x, const blendcombo &y)
    1253              : {
    1254           36 :     for(size_t i = 0; i < x.bonedata.size(); ++i)
    1255              :     {
    1256           17 :         if(x.bonedata[i].weight)
    1257              :         {
    1258           13 :             if(!y.bonedata[i].weight)
    1259              :             {
    1260            1 :                 return true;
    1261              :             }
    1262              :         }
    1263            4 :         else if(y.bonedata[i].weight)
    1264              :         {
    1265            1 :             return false;
    1266              :         }
    1267              :         else
    1268              :         {
    1269            3 :             break;
    1270              :         }
    1271              :     }
    1272            4 :     return false;
    1273              : }
    1274              : 
    1275          504 : int skelmodel::blendcombo::addweight(int sorted, float weight, int bone)
    1276              : {
    1277          504 :     if(weight <= 1e-3f) //do not add trivially small weights
    1278              :     {
    1279           60 :         return sorted;
    1280              :     }
    1281          450 :     for(int k = 0; k < sorted; ++k)
    1282              :     {
    1283            9 :         if(weight > bonedata[k].weight)
    1284              :         {
    1285              :             //push weights in bonedata to make room for new larger weights
    1286           10 :             for(int l = std::min(sorted-1, 2); l >= k; l--)
    1287              :             {
    1288            7 :                 bonedata[l+1].weight = bonedata[l].weight;
    1289            7 :                 bonedata[l+1].bone = bonedata[l].bone;
    1290              :             }
    1291            3 :             bonedata[k].weight = weight;
    1292            3 :             bonedata[k].bone = bone;
    1293            6 :             return sorted < static_cast<int>(bonedata.size()) ? sorted+1 : sorted;
    1294              :         }
    1295              :     }
    1296          882 :     if(sorted >= static_cast<int>(bonedata.size()))
    1297              :     {
    1298            1 :         return sorted;
    1299              :     }
    1300          440 :     bonedata[sorted].weight = weight;
    1301          440 :     bonedata[sorted].bone = bone;
    1302          440 :     return sorted+1;
    1303              : }
    1304              : 
    1305          443 : void skelmodel::blendcombo::finalize(int sorted)
    1306              : {
    1307          443 :     if(sorted <= 0)
    1308              :     {
    1309            1 :         return;
    1310              :     }
    1311          442 :     float total = 0;
    1312          894 :     for(int i = 0; i < sorted; ++i)
    1313              :     {
    1314          452 :         total += bonedata[i].weight;
    1315              :     }
    1316          442 :     total = 1.0f/total;
    1317          894 :     for(int i = 0; i < sorted; ++i)
    1318              :     {
    1319          452 :         bonedata[i].weight *= total;
    1320              :     }
    1321              : }
    1322              : 
    1323            7 : void skelmodel::blendcombo::serialize(skelmodel::vvertgw &v) const
    1324              : {
    1325            7 :     if(interpindex >= 0)
    1326              :     {
    1327            2 :         v.weights[0] = 255;
    1328            8 :         for(int k = 0; k < 3; ++k)
    1329              :         {
    1330            6 :             v.weights[k+1] = 0;
    1331              :         }
    1332            2 :         v.bones[0] = 2*interpindex;
    1333            8 :         for(int k = 0; k < 3; ++k)
    1334              :         {
    1335            6 :             v.bones[k+1] = v.bones[0];
    1336              :         }
    1337              :     }
    1338              :     else
    1339              :     {
    1340            5 :         int total = 0;
    1341           50 :         for(size_t k = 0; k < bonedata.size(); ++k)
    1342              :         {
    1343           20 :             v.weights[k] = static_cast<uchar>(0.5f + bonedata[k].weight*255);
    1344           20 :             total += (v.weights[k]);
    1345              :         }
    1346          113 :         while(total > 255)
    1347              :         {
    1348          540 :             for(size_t k = 0; k < 4; ++k)
    1349              :             {
    1350          432 :                 if(v.weights[k] > 0 && total > 255)
    1351              :                 {
    1352          412 :                     v.weights[k]--;
    1353          412 :                     total--;
    1354              :                 }
    1355              :             }
    1356              :         }
    1357          113 :         while(total < 255)
    1358              :         {
    1359          540 :             for(size_t k = 0; k < 4; ++k)
    1360              :             {
    1361          432 :                 if(v.weights[k] < 255 && total < 255)
    1362              :                 {
    1363          429 :                     v.weights[k]++;
    1364          429 :                     total++;
    1365              :                 }
    1366              :             }
    1367              :         }
    1368           50 :         for(size_t k = 0; k < bonedata.size(); ++k)
    1369              :         {
    1370           20 :             v.bones[k] = 2*bonedata[k].interpbone;
    1371              :         }
    1372              :     }
    1373            7 : }
    1374              : 
    1375            2 : dualquat skelmodel::blendcombo::blendbones(const dualquat *bdata) const
    1376              : {
    1377            2 :     dualquat d = bdata[bonedata[0].interpbone];
    1378            2 :     d.mul(bonedata[0].weight);
    1379            2 :     d.accumulate(bdata[bonedata[1].interpbone], bonedata[1].weight);
    1380            2 :     if(bonedata[2].weight)
    1381              :     {
    1382            2 :         d.accumulate(bdata[bonedata[2].interpbone], bonedata[2].weight);
    1383            2 :         if(bonedata[3].weight)
    1384              :         {
    1385            1 :             d.accumulate(bdata[bonedata[3].interpbone], bonedata[3].weight);
    1386              :         }
    1387              :     }
    1388            2 :     return d;
    1389              : }
    1390              : 
    1391           22 : void skelmodel::blendcombo::setinterpbones(int val, size_t index)
    1392              : {
    1393           22 :     bonedata[index].interpbone = val;
    1394           22 : }
    1395              : 
    1396           30 : int skelmodel::blendcombo::getbone(size_t index)
    1397              : {
    1398           30 :     return bonedata[index].bone;
    1399              : }
    1400              : 
    1401              : template<class T>
    1402            0 : T &searchcache(size_t cachesize, T *cache, const skelmodel::skelcacheentry &sc, int owner)
    1403              : {
    1404            0 :     for(size_t i = 0; i < cachesize; ++i)
    1405              :     {
    1406            0 :         T &c = cache[i];
    1407            0 :         if(c.owner==owner)
    1408              :         {
    1409            0 :              if(c==sc)
    1410              :              {
    1411            0 :                  return c;
    1412              :              }
    1413              :              else
    1414              :              {
    1415            0 :                  c.owner = -1;
    1416              :              }
    1417            0 :              break;
    1418              :         }
    1419              :     }
    1420            0 :     for(size_t i = 0; i < cachesize-1; ++i)
    1421              :     {
    1422            0 :         T &c = cache[i];
    1423            0 :         if(c.owner < 0 || c.millis < lastmillis)
    1424              :         {
    1425            0 :             return c;
    1426              :         }
    1427              :     }
    1428            0 :     return cache[cachesize-1];
    1429              : }
    1430              : 
    1431            0 : skelmodel::blendcacheentry &skelmodel::skelmeshgroup::checkblendcache(const skelcacheentry &sc, int owner)
    1432              : {
    1433            0 :     return searchcache<blendcacheentry>(maxblendcache, blendcache.data(), sc, owner);
    1434              : }
    1435              : 
    1436              : //skelmesh
    1437              : 
    1438            0 : skelmodel::skelmesh::skelmesh() : tris(nullptr), numtris(0), verts(nullptr), numverts(0), maxweights(0)
    1439              : {
    1440            0 : }
    1441              : 
    1442            4 : skelmodel::skelmesh::skelmesh(std::string_view name, vert *verts, uint numverts, tri *tris, uint numtris, meshgroup *m) : Mesh(name, m),
    1443            4 :     tris(tris),
    1444            4 :     numtris(numtris),
    1445            4 :     verts(verts),
    1446            4 :     numverts(numverts),
    1447            4 :     maxweights(0)
    1448              : {
    1449            4 : }
    1450              : 
    1451            3 : skelmodel::skelmesh::~skelmesh()
    1452              : {
    1453            3 :     delete[] verts;
    1454            3 :     delete[] tris;
    1455            3 : }
    1456              : 
    1457          438 : int skelmodel::skelmesh::addblendcombo(const blendcombo &c)
    1458              : {
    1459          438 :     maxweights = std::max(maxweights, static_cast<int>(c.size()));
    1460          438 :     return (reinterpret_cast<skelmeshgroup *>(group))->addblendcombo(c);
    1461              : }
    1462              : 
    1463            0 : void skelmodel::skelmesh::smoothnorms(float limit, bool areaweight)
    1464              : {
    1465            0 :     Mesh::smoothnorms<skelmodel>(verts, numverts, tris, numtris, limit, areaweight);
    1466            0 : }
    1467              : 
    1468            3 : void skelmodel::skelmesh::buildnorms(bool areaweight)
    1469              : {
    1470            3 :     Mesh::buildnorms<skelmodel>(verts, numverts, tris, numtris, areaweight);
    1471            3 : }
    1472              : 
    1473            1 : void skelmodel::skelmesh::calctangents(bool areaweight)
    1474              : {
    1475            1 :     Mesh::calctangents<skelmodel, skelmodel::vert>(verts, verts, numverts, tris, numtris, areaweight);
    1476            1 : }
    1477              : 
    1478            2 : void skelmodel::skelmesh::calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) const
    1479              : {
    1480           10 :     for(int j = 0; j < numverts; ++j)
    1481              :     {
    1482            8 :         vec v = m.transform(verts[j].pos);
    1483            8 :         bbmin.min(v);
    1484            8 :         bbmax.max(v);
    1485              :     }
    1486            2 : }
    1487              : 
    1488            0 : void skelmodel::skelmesh::genBIH(BIH::mesh &m) const
    1489              : {
    1490            0 :     m.setmesh(reinterpret_cast<const BIH::mesh::tri *>(tris), numtris,
    1491            0 :               reinterpret_cast<const uchar *>(&verts->pos), sizeof(vert),
    1492            0 :               reinterpret_cast<const uchar *>(&verts->tc), sizeof(vert));
    1493            0 : }
    1494              : 
    1495            0 : void skelmodel::skelmesh::genshadowmesh(std::vector<triangle> &out, const matrix4x3 &m) const
    1496              : {
    1497            0 :     for(int j = 0; j < numtris; ++j)
    1498              :     {
    1499            0 :         triangle t;
    1500            0 :         t.a = m.transform(verts[tris[j].vert[0]].pos);
    1501            0 :         t.b = m.transform(verts[tris[j].vert[1]].pos);
    1502            0 :         t.c = m.transform(verts[tris[j].vert[2]].pos);
    1503            0 :         out.push_back(t);
    1504              :     }
    1505            0 : }
    1506              : 
    1507            1 : void skelmodel::skelmesh::assignvert(vvertg &vv, const vert &v)
    1508              : {
    1509            1 :     vv.pos = vec4<half>(v.pos, 1);
    1510            1 :     vv.tc = v.tc;
    1511            1 :     vv.tangent = v.tangent;
    1512            1 : }
    1513              : 
    1514            1 : void skelmodel::skelmesh::assignvert(vvertgw &vv, const vert &v, const blendcombo &c)
    1515              : {
    1516            1 :     vv.pos = vec4<half>(v.pos, 1);
    1517            1 :     vv.tc = v.tc;
    1518            1 :     vv.tangent = v.tangent;
    1519            1 :     c.serialize(vv);
    1520            1 : }
    1521              : 
    1522            0 : int skelmodel::skelmesh::genvbo(const std::vector<blendcombo> &bcs, std::vector<GLuint> &idxs, int offset, std::vector<vvertgw> &vverts)
    1523              : {
    1524            0 :     voffset = offset;
    1525            0 :     eoffset = idxs.size();
    1526            0 :     for(int i = 0; i < numverts; ++i)
    1527              :     {
    1528            0 :         const vert &v = verts[i];
    1529            0 :         vverts.emplace_back(vvertgw());
    1530            0 :         assignvert(vverts.back(), v, bcs[v.blend]);
    1531              :     }
    1532            0 :     for(int i = 0; i < numtris; ++i)
    1533              :     {
    1534            0 :         for(int j = 0; j < 3; ++j)
    1535              :         {
    1536            0 :             idxs.push_back(voffset + tris[i].vert[j]);
    1537              :         }
    1538              :     }
    1539            0 :     elen = idxs.size()-eoffset;
    1540            0 :     minvert = voffset;
    1541            0 :     maxvert = voffset + numverts-1;
    1542            0 :     return numverts;
    1543              : }
    1544              : 
    1545            0 : int skelmodel::skelmesh::genvbo(std::vector<GLuint> &idxs, int offset, std::vector<vvertg> &vverts, int *htdata, int htlen)
    1546              : {
    1547            0 :     voffset = offset;
    1548            0 :     eoffset = idxs.size();
    1549            0 :     minvert = 0xFFFF;
    1550            0 :     for(int i = 0; i < numtris; ++i)
    1551              :     {
    1552            0 :         const tri &t = tris[i];
    1553            0 :         for(int j = 0; j < 3; ++j)
    1554              :         {
    1555            0 :             const uint index = t.vert[j];
    1556            0 :             const vert &v = verts[index];
    1557            0 :             vvertg vv;
    1558            0 :             assignvert(vv, v);
    1559              :             const auto hashfn = std::hash<vec>();
    1560            0 :             int htidx = hashfn(v.pos)&(htlen-1);
    1561            0 :             for(int k = 0; k < htlen; ++k)
    1562              :             {
    1563            0 :                 int &vidx = htdata[(htidx+k)&(htlen-1)];
    1564            0 :                 if(vidx < 0)
    1565              :                 {
    1566            0 :                     vidx = idxs.emplace_back(static_cast<GLuint>(vverts.size()));
    1567            0 :                     vverts.push_back(vv);
    1568            0 :                     break;
    1569              :                 }
    1570            0 :                 else if(!std::memcmp(&vverts[vidx], &vv, sizeof(vv)))
    1571              :                 {
    1572            0 :                     minvert = std::min(minvert, idxs.emplace_back(static_cast<GLuint>(vidx)));
    1573            0 :                     break;
    1574              :                 }
    1575              :             }
    1576              :         }
    1577              :     }
    1578            0 :     elen = idxs.size()-eoffset;
    1579            0 :     minvert = std::min(minvert, static_cast<GLuint>(voffset));
    1580            0 :     maxvert = std::max(minvert, static_cast<GLuint>(vverts.size()-1));
    1581            0 :     return vverts.size()-voffset;
    1582              : }
    1583              : 
    1584            0 : void skelmodel::skelmesh::setshader(Shader *s, bool usegpuskel, int vweights, int row) const
    1585              : {
    1586            0 :     if(row)
    1587              :     {
    1588            0 :         s->setvariant(usegpuskel ? std::min(maxweights, vweights) : 0, row);
    1589              :     }
    1590            0 :     else if(usegpuskel)
    1591              :     {
    1592            0 :         s->setvariant(std::min(maxweights, vweights)-1, 0);
    1593              :     }
    1594              :     else
    1595              :     {
    1596            0 :         s->set();
    1597              :     }
    1598            0 : }
    1599              : 
    1600            0 : void skelmodel::skelmesh::render() const
    1601              : {
    1602            0 :     if(!Shader::lastshader)
    1603              :     {
    1604            0 :         return;
    1605              :     }
    1606            0 :     glDrawRangeElements(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_INT, &(static_cast<skelmeshgroup *>(group))->edata[eoffset]);
    1607            0 :     glde++;
    1608            0 :     xtravertsva += numverts;
    1609              : }
    1610              : 
    1611            1 : void skelmodel::skelmesh::remapverts(const std::vector<int> remap)
    1612              : {
    1613          439 :     for(int i = 0; i < numverts; ++i)
    1614              :     {
    1615          438 :         vert &v = verts[i];
    1616          438 :         v.blend = remap[v.blend];
    1617              :     }
    1618            1 : }
    1619              : 
    1620            4 : int skelmodel::skelmesh::vertcount() const
    1621              : {
    1622            4 :     return numverts;
    1623              : }
    1624              : 
    1625            4 : int skelmodel::skelmesh::tricount() const
    1626              : {
    1627            4 :     return numtris;
    1628              : }
    1629              : 
    1630            8 : const skelmodel::vert &skelmodel::skelmesh::getvert(size_t index) const
    1631              : {
    1632            8 :     return verts[index];
    1633              : }
    1634              : 
    1635              : // boneinfo
    1636              : 
    1637            4 : skelmodel::skeleton::boneinfo::boneinfo() :
    1638            8 :     name(""),
    1639            4 :     parent(-1),
    1640            4 :     children(-1),
    1641            4 :     next(-1),
    1642            4 :     group(INT_MAX),
    1643            4 :     scheduled(-1),
    1644            4 :     interpindex(-1),
    1645            4 :     interpparent(-1),
    1646            4 :     ragdollindex(-1),
    1647            4 :     correctindex(-1),
    1648            4 :     pitchscale(0),
    1649            4 :     pitchoffset(0),
    1650            4 :     pitchmin(0),
    1651            4 :     pitchmax(0)
    1652              : {
    1653            4 : }
    1654              : 
    1655              : // skelmeshgroup
    1656              : 
    1657            0 : std::optional<size_t> skelmodel::skelmeshgroup::findtag(std::string_view name)
    1658              : {
    1659            0 :     return skel->findtag(name);
    1660              : }
    1661              : 
    1662            0 : void *skelmodel::skelmeshgroup::animkey()
    1663              : {
    1664            0 :     return skel;
    1665              : }
    1666              : 
    1667            2 : int skelmodel::skelmeshgroup::totalframes() const
    1668              : {
    1669            2 :     return std::max(skel->numframes, size_t(1));
    1670              : }
    1671              : 
    1672            0 : void skelmodel::skelmeshgroup::bindvbo(const AnimState *as, const part *p, const vbocacheentry &vc)
    1673              : {
    1674            0 :     if(!skel->numframes)
    1675              :     {
    1676            0 :         bindvbo<vvertg>(as, p, vc);
    1677              :     }
    1678              :     else
    1679              :     {
    1680            0 :         bindvbo<vvertgw>(as, p, vc);
    1681              :     }
    1682            0 : }
    1683              : 
    1684            0 : void skelmodel::skelmeshgroup::concattagtransform(int i, const matrix4x3 &m, matrix4x3 &n) const
    1685              : {
    1686            0 :     skel->concattagtransform(i, m, n);
    1687            0 : }
    1688              : 
    1689          438 : int skelmodel::skelmeshgroup::addblendcombo(const blendcombo &c)
    1690              : {
    1691          497 :     for(uint i = 0; i < blendcombos.size(); i++)
    1692              :     {
    1693          495 :         if(blendcombos[i]==c)
    1694              :         {
    1695          436 :             blendcombos[i].uses += c.uses;
    1696          436 :             return i;
    1697              :         }
    1698              :     }
    1699            2 :     numblends[c.size()-1]++;
    1700            2 :     blendcombos.push_back(c);
    1701            2 :     blendcombo &a = blendcombos.back();
    1702            2 :     a.interpindex = blendcombos.size()-1;
    1703            2 :     return a.interpindex;
    1704              : }
    1705              : 
    1706            1 : void skelmodel::skelmeshgroup::sortblendcombos()
    1707              : {
    1708            1 :     std::sort(blendcombos.begin(), blendcombos.end(), blendcombo::sortcmp);
    1709            1 :     std::vector<int> remap(blendcombos.size(), 0);
    1710            3 :     for(uint i = 0; i < blendcombos.size(); i++)
    1711              :     {
    1712            2 :         remap[blendcombos[i].interpindex] = i;
    1713              :     }
    1714            1 :     auto rendermeshes = getrendermeshes();
    1715            2 :     for(auto i : rendermeshes)
    1716              :     {
    1717            1 :         skelmesh *s = static_cast<skelmesh *>(*i);
    1718            1 :         s->remapverts(remap);
    1719              :     }
    1720            1 : }
    1721              : 
    1722            0 : void skelmodel::skelmeshgroup::blendbones(const skelcacheentry &sc, blendcacheentry &bc) const
    1723              : {
    1724            0 :     bc.nextversion();
    1725            0 :     if(!bc.bdata)
    1726              :     {
    1727            0 :         bc.bdata = new dualquat[vblends];
    1728              :     }
    1729            0 :     dualquat *dst = bc.bdata - skel->numgpubones;
    1730            0 :     bool normalize = vweights<=1;
    1731            0 :     for(const blendcombo &c : blendcombos)
    1732              :     {
    1733            0 :         if(c.interpindex<0)
    1734              :         {
    1735            0 :             break;
    1736              :         }
    1737            0 :         dualquat &d = dst[c.interpindex];
    1738            0 :         d = c.blendbones(sc.bdata);
    1739            0 :         if(normalize)
    1740              :         {
    1741            0 :             d.normalize();
    1742              :         }
    1743              :     }
    1744            0 : }
    1745              : 
    1746           11 : void skelmodel::skelmeshgroup::cleanup()
    1747              : {
    1748          187 :     for(size_t i = 0; i < maxblendcache; ++i)
    1749              :     {
    1750          176 :         blendcacheentry &c = blendcache[i];
    1751          176 :         delete[] c.bdata;
    1752          176 :         c.bdata = nullptr;
    1753          176 :         c.owner = -1;
    1754              :     }
    1755          187 :     for(size_t i = 0; i < maxvbocache; ++i)
    1756              :     {
    1757          176 :         vbocacheentry &c = vbocache[i];
    1758          176 :         if(c.vbuf)
    1759              :         {
    1760            0 :             glDeleteBuffers(1, &c.vbuf);
    1761            0 :             c.vbuf = 0;
    1762              :         }
    1763          176 :         c.owner = -1;
    1764              :     }
    1765           11 :     if(ebuf)
    1766              :     {
    1767            0 :         glDeleteBuffers(1, &ebuf);
    1768            0 :         ebuf = 0;
    1769              :     }
    1770           11 :     if(skel)
    1771              :     {
    1772           11 :         skel->cleanup(false);
    1773              :     }
    1774           11 : }
    1775              : 
    1776            0 : void skelmodel::skelmeshgroup::preload()
    1777              : {
    1778            0 :     if(skel->shouldcleanup())
    1779              :     {
    1780            0 :         skel->cleanup();
    1781              :     }
    1782            0 :     if(!vbocache[0].vbuf)
    1783              :     {
    1784            0 :         genvbo(vbocache[0]);
    1785              :     }
    1786            0 : }
    1787              : 
    1788              : // skelpart
    1789              : 
    1790           19 : skelmodel::skelpart::skelpart(animmodel *model, int index) : part(model, index)
    1791              : {
    1792           19 : }
    1793              : 
    1794           38 : skelmodel::skelpart::~skelpart()
    1795              : {
    1796           38 : }
    1797              : 
    1798              : /**
    1799              :  * @brief Manages caching of part masking data.
    1800              :  *
    1801              :  * Attempts to match the passed vector of uchar with one in the internal static vector.
    1802              :  * If a matching entry is found, empties the passed vector returns the entry in the cache.
    1803              :  *
    1804              :  * @param o a vector of uchar to delete or insert into the internal cache
    1805              :  * @return the passed value o, or the equivalent entry in the internal cache
    1806              :  */
    1807           11 : std::vector<uchar> &skelmodel::skelpart::sharepartmask(std::vector<uchar> &o)
    1808              : {
    1809           11 :     static std::vector<std::vector<uchar>> partmasks;
    1810           12 :     for(std::vector<uchar> &p : partmasks)
    1811              :     {
    1812           10 :         if(p == o)
    1813              :         {
    1814            9 :             o.clear();
    1815            9 :             return p;
    1816              :         }
    1817              :     }
    1818            2 :     partmasks.push_back(o);
    1819            2 :     o.clear();
    1820            2 :     return partmasks.back();
    1821              : }
    1822              : 
    1823           17 : std::vector<uchar> skelmodel::skelpart::newpartmask()
    1824              : {
    1825           17 :     return std::vector<uchar>((static_cast<skelmeshgroup *>(meshes))->skel->numbones, 0);
    1826              : 
    1827              : }
    1828              : 
    1829           17 : void skelmodel::skelpart::initanimparts()
    1830              : {
    1831           17 :     buildingpartmask = newpartmask();
    1832           17 : }
    1833              : 
    1834            1 : bool skelmodel::skelpart::addanimpart(const std::vector<uint> &bonemask)
    1835              : {
    1836            1 :     if(buildingpartmask.empty() || numanimparts>=maxanimparts)
    1837              :     {
    1838            0 :         return false;
    1839              :     }
    1840            1 :     (static_cast<skelmeshgroup *>(meshes))->skel->applybonemask(bonemask, buildingpartmask, numanimparts);
    1841            1 :     numanimparts++;
    1842            1 :     return true;
    1843              : }
    1844              : 
    1845           11 : void skelmodel::skelpart::endanimparts()
    1846              : {
    1847           11 :     if(buildingpartmask.size())
    1848              :     {
    1849           11 :         partmask = sharepartmask(buildingpartmask);
    1850           11 :         buildingpartmask.clear();
    1851              :     }
    1852              : 
    1853           11 :     (static_cast<skelmeshgroup *>(meshes))->skel->optimize();
    1854           11 : }
    1855              : 
    1856           11 : void skelmodel::skelpart::loaded()
    1857              : {
    1858           11 :     endanimparts();
    1859           11 :     part::loaded();
    1860           11 : }
    1861              : 
    1862              : //skelmodel
    1863              : 
    1864           20 : skelmodel::skelmodel(std::string name) : animmodel(std::move(name))
    1865              : {
    1866           20 : }
    1867              : 
    1868           19 : skelmodel::skelpart &skelmodel::addpart()
    1869              : {
    1870           19 :     skelpart *p = new skelpart(this, parts.size());
    1871           19 :     parts.push_back(p);
    1872           19 :     return *p;
    1873              : }
    1874              : 
    1875            3 : animmodel::meshgroup * skelmodel::loadmeshes(const std::string &name, float smooth)
    1876              : {
    1877            3 :     skelmeshgroup *group = newmeshes();
    1878            3 :     group->skel = new skeleton(group);
    1879            3 :     part &p = *parts.back();
    1880            3 :     if(!group->load(name, smooth, p))
    1881              :     {
    1882            2 :         delete group;
    1883            2 :         return nullptr;
    1884              :     }
    1885            1 :     return group;
    1886              : }
    1887              : 
    1888           19 : animmodel::meshgroup * skelmodel::sharemeshes(const std::string &name, float smooth)
    1889              : {
    1890           19 :     if(meshgroups.find(name) == meshgroups.end())
    1891              :     {
    1892            3 :         meshgroup *group = loadmeshes(name, smooth);
    1893            3 :         if(!group)
    1894              :         {
    1895            2 :             return nullptr;
    1896              :         }
    1897            1 :         meshgroups[group->groupname()] = group;
    1898              :     }
    1899           17 :     return meshgroups[name];
    1900              : }
    1901              : 
    1902              : //skelmodel overrides
    1903              : 
    1904            0 : int skelmodel::linktype(const animmodel *m, const part *p) const
    1905              : {
    1906            0 :     return type()==m->type() &&
    1907            0 :         (static_cast<skelmeshgroup *>(parts[0]->meshes))->skel == (static_cast<skelmeshgroup *>(p->meshes))->skel ?
    1908              :             Link_Reuse :
    1909            0 :             Link_Tag;
    1910              : }
    1911              : 
    1912            0 : bool skelmodel::skeletal() const
    1913              : {
    1914            0 :     return true;
    1915              : }
    1916              : 
    1917              : /*    ====    skeladjustment    ====    */
    1918              : /*======================================*/
    1919              : 
    1920            0 : void skeladjustment::adjust(dualquat &dq) const
    1921              : {
    1922            0 :     if(yaw)
    1923              :     {
    1924            0 :         dq.mulorient(quat(vec(0, 0, 1), yaw/RAD));
    1925              :     }
    1926            0 :     if(pitch)
    1927              :     {
    1928            0 :         dq.mulorient(quat(vec(0, -1, 0), pitch/RAD));
    1929              :     }
    1930            0 :     if(roll)
    1931              :     {
    1932            0 :         dq.mulorient(quat(vec(-1, 0, 0), roll/RAD));
    1933              :     }
    1934            0 :     if(!translate.iszero())
    1935              :     {
    1936            0 :         dq.translate(translate);
    1937              :     }
    1938            0 : }
        

Generated by: LCOV version 2.0-1