LCOV - code coverage report
Current view: top level - engine/model - skelmodel.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 46.4 % 1010 469
Test Date: 2026-05-09 04:28:55 Functions: 59.6 % 114 68

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

Generated by: LCOV version 2.0-1