LCOV - code coverage report
Current view: top level - engine/model - md5.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 86.0 % 300 258
Test Date: 2025-12-12 08:02:14 Functions: 87.5 % 16 14

            Line data    Source code
       1              : 
       2              : /**
       3              :  * @brief md5 (id tech 4) model support
       4              :  *
       5              :  * Libprimis supports the md5 (id Tech 4) skeletal model format for animated
       6              :  * models. Ragdolls and animated models (such as players) use this model format.
       7              :  * The implmentation for the md5 class is in this file, while the object is defined
       8              :  * inside md5.h.
       9              :  */
      10              : 
      11              : //includes copied from obj.cpp
      12              : #include "../libprimis-headers/cube.h"
      13              : #include "../../shared/geomexts.h"
      14              : #include "../../shared/glemu.h"
      15              : #include "../../shared/glexts.h"
      16              : #include "../../shared/stream.h"
      17              : 
      18              : #include <optional>
      19              : #include <memory>
      20              : #include <format>
      21              : 
      22              : #include "render/rendergl.h"
      23              : #include "render/rendermodel.h"
      24              : #include "render/renderwindow.h"
      25              : #include "render/shader.h"
      26              : #include "render/shaderparam.h"
      27              : #include "render/texture.h"
      28              : 
      29              : #include "interface/console.h"
      30              : #include "interface/control.h"
      31              : #include "interface/cs.h"
      32              : 
      33              : #include "world/entities.h"
      34              : #include "world/octaworld.h"
      35              : #include "world/bih.h"
      36              : 
      37              : #include "model.h"
      38              : #include "ragdoll.h"
      39              : #include "animmodel.h"
      40              : #include "skelmodel.h"
      41              : 
      42              : #include "md5.h"
      43              : 
      44              : static constexpr int md5version = 10;
      45              : 
      46              : skelcommands<md5> md5::md5commands;
      47              : 
      48           18 : md5::md5(std::string name) : skelloader(name) {}
      49              : 
      50           86 : const char *md5::formatname()
      51              : {
      52           86 :     return "md5";
      53              : }
      54              : 
      55            7 : bool md5::flipy() const
      56              : {
      57            7 :     return false;
      58              : }
      59              : 
      60            1 : int md5::type() const
      61              : {
      62            1 :     return MDL_MD5;
      63              : }
      64              : 
      65            3 : md5::skelmeshgroup *md5::newmeshes()
      66              : {
      67            3 :     return new md5meshgroup;
      68              : }
      69              : 
      70            8 : bool md5::loaddefaultparts()
      71              : {
      72            8 :     skelpart &mdl = addpart();
      73            8 :     const char *fname = modelname().c_str() + std::strlen(modelname().c_str());
      74              :     do
      75              :     {
      76           90 :         --fname;
      77           90 :     } while(fname >= modelname().c_str() && *fname!='/' && *fname!='\\');
      78            8 :     fname++;
      79            8 :     std::string meshname = modelpath;
      80            8 :     meshname.append(modelname()).append("/").append(fname).append(".md5mesh");
      81           16 :     mdl.meshes = sharemeshes(path(meshname).c_str());
      82            8 :     if(!mdl.meshes)
      83              :     {
      84            2 :         return false;
      85              :     }
      86            6 :     mdl.initanimparts();
      87            6 :     mdl.initskins();
      88            6 :     std::string animname = modelpath;
      89            6 :     animname.append(modelname()).append("/").append(fname).append(".md5anim");
      90            6 :     static_cast<md5meshgroup *>(mdl.meshes)->loadanim(path(animname));
      91            6 :     return true;
      92            8 : }
      93              : 
      94              : 
      95            3 : md5::md5meshgroup::md5meshgroup()
      96              : {
      97            3 : }
      98              : 
      99              : //main anim loading functionality
     100            9 : const md5::skelanimspec *md5::md5meshgroup::loadanim(const std::string &filename)
     101              : {
     102              :     {
     103            9 :         const skelanimspec *sa = skel->findskelanim(filename);
     104            9 :         if(sa)
     105              :         {
     106            8 :             return sa;
     107              :         }
     108              :     }
     109            1 :     stream *f = openfile(filename.c_str(), "r");
     110            1 :     if(!f)
     111              :     {
     112            0 :         return nullptr;
     113              :     }
     114              :     //hierarchy, basejoints vectors are to have correlating indices with
     115              :     // skel->bones, this->adjustments, this->frame, skel->framebones
     116              :     struct md5hierarchy final
     117              :     {
     118              :         string name;
     119              :         int parent, flags, start;
     120              :     };
     121            1 :     std::vector<md5hierarchy> hierarchy; //metadata used only within this function
     122            1 :     std::vector<md5joint> basejoints;
     123            1 :     int animdatalen = 0,
     124            1 :         animframes = 0;
     125            1 :     float *animdata = nullptr;
     126            1 :     dualquat *animbones = nullptr;
     127              :     char buf[512]; //presumably lines over 512 char long will break this loader
     128              :     //for each line in the opened file
     129            1 :     skelanimspec *sas = nullptr;
     130           17 :     while(f->getline(buf, sizeof(buf)))
     131              :     {
     132              :         int tmp;
     133           16 :         if(std::sscanf(buf, " MD5Version %d", &tmp) == 1)
     134              :         {
     135            1 :             if(tmp != md5version)
     136              :             {
     137            0 :                 delete f; //bail out if md5version is not what we want
     138            0 :                 return nullptr;
     139              :             }
     140              :         }
     141           15 :         else if(std::sscanf(buf, " numJoints %d", &tmp) == 1)
     142              :         {
     143            1 :             if(tmp != static_cast<int>(skel->numbones))
     144              :             {
     145            0 :                 delete f; //bail out if numbones is not consistent
     146            0 :                 return nullptr;
     147              :             }
     148              :         }
     149           14 :         else if(std::sscanf(buf, " numFrames %d", &animframes) == 1)
     150              :         {
     151            1 :             if(animframes < 1) //if there are no animated frames, don't do animated frame stuff
     152              :             {
     153            0 :                 delete f;
     154            0 :                 return nullptr;
     155              :             }
     156              :         }
     157              :         //apparently, do nothing with respect to framerate
     158           13 :         else if(std::sscanf(buf, " frameRate %d", &tmp) == 1)
     159              :         {
     160              :             //(empty body)
     161              :         }
     162              :         //create animdata if there is some relevant info in file
     163           12 :         else if(std::sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1)
     164              :         {
     165            1 :             if(animdatalen > 0)
     166              :             {
     167            1 :                 animdata = new float[animdatalen];
     168              :             }
     169              :         }
     170           11 :         else if(std::strstr(buf, "bounds {"))
     171              :         {
     172            2 :             while(f->getline(buf, sizeof(buf)) && buf[0]!='}') //loop until end of {} block
     173              :             {
     174              :                 //(empty body)
     175              :             }
     176              :         }
     177           10 :         else if(std::strstr(buf, "hierarchy {"))
     178              :         {
     179            5 :             while(f->getline(buf, sizeof(buf)) && buf[0]!='}') //loop until end of {} block
     180              :             {
     181              :                 md5hierarchy h;
     182            4 :                 if(std::sscanf(buf, " %100s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4)
     183              :                 {
     184            4 :                     hierarchy.push_back(std::move(h));
     185              :                 }
     186              :             }
     187              :         }
     188            9 :         else if(std::strstr(buf, "baseframe {"))
     189              :         {
     190            5 :             while(f->getline(buf, sizeof(buf)) && buf[0]!='}') //loop until end of {} block
     191              :             {
     192            4 :                 md5joint j;
     193              :                 //pick up pos/orient 3-vectors within
     194            4 :                 if(std::sscanf(buf, " ( %f %f %f ) ( %f %f %f )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6)
     195              :                 {
     196            4 :                     j.pos.y = -j.pos.y;
     197            4 :                     j.orient.x = -j.orient.x;
     198            4 :                     j.orient.z = -j.orient.z;
     199            4 :                     j.orient.restorew();
     200            4 :                     basejoints.push_back(j); //no value to std::move POD of fundamental types
     201              :                 }
     202              :             }
     203            1 :             if(basejoints.size() != skel->numbones)
     204              :             {
     205            0 :                 delete f;
     206            0 :                 if(animdata)
     207              :                 {
     208            0 :                     delete[] animdata;
     209              :                 }
     210            0 :                 return nullptr;
     211              :             }
     212            5 :             animbones = new dualquat[(skel->numframes+animframes)*skel->numbones];
     213            1 :             if(skel->framebones)
     214              :             {
     215            0 :                 std::memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat));
     216            0 :                 delete[] skel->framebones;
     217              :             }
     218            1 :             skel->framebones = animbones;
     219            1 :             animbones += skel->numframes*skel->numbones;
     220              : 
     221            1 :             sas = &skel->addskelanim(filename, skel->numframes, animframes);
     222              : 
     223            1 :             skel->numframes += animframes;
     224              :         }
     225            8 :         else if(std::sscanf(buf, " frame %d", &tmp)==1)
     226              :         {
     227            5 :             for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';)
     228              :             {
     229           28 :                 for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next)
     230              :                 {
     231           27 :                     animdata[numdata] = std::strtod(src, &next);
     232           27 :                     if(next <= src)
     233              :                     {
     234            3 :                         break;
     235              :                     }
     236              :                 }
     237              :             }
     238            1 :             dualquat *frame = &animbones[tmp*skel->numbones];
     239            1 :             if(basejoints.size() != hierarchy.size())
     240              :             {
     241            0 :                 conoutf("Invalid model data: hierarchy (%lu) and baseframe (%lu) size mismatch", hierarchy.size(), basejoints.size());
     242            0 :                 return nullptr;
     243              :             }
     244            5 :             for(size_t i = 0; i < basejoints.size(); i++)
     245              :             {
     246            4 :                 const md5hierarchy &h = hierarchy[i];
     247            4 :                 md5joint j = basejoints[i]; //intentionally getting by value to modify temp copy
     248            4 :                 if(h.start < animdatalen && h.flags)
     249              :                 {
     250            4 :                     const float *jdata = &animdata[h.start];
     251              :                     //bitwise AND against bits 0...5
     252            4 :                     if(h.flags & 1)
     253              :                     {
     254            4 :                         j.pos.x = *jdata++;
     255              :                     }
     256            4 :                     if(h.flags & 2)
     257              :                     {
     258            4 :                         j.pos.y = -*jdata++;
     259              :                     }
     260            4 :                     if(h.flags & 4)
     261              :                     {
     262            4 :                         j.pos.z = *jdata++;
     263              :                     }
     264            4 :                     if(h.flags & 8)
     265              :                     {
     266            4 :                         j.orient.x = -*jdata++;
     267              :                     }
     268            4 :                     if(h.flags & 16)
     269              :                     {
     270            4 :                         j.orient.y = *jdata++;
     271              :                     }
     272            4 :                     if(h.flags & 32)
     273              :                     {
     274            4 :                         j.orient.z = -*jdata++;
     275              :                     }
     276            4 :                     j.orient.restorew();
     277              :                 }
     278            4 :                 dualquat dq(j.orient, j.pos);
     279            4 :                 if(adjustments.size() > i)
     280              :                 {
     281            0 :                     adjustments[i].adjust(dq);
     282              :                 }
     283              :                 //assume nullopt cannot happen
     284            4 :                 dq.mul(skel->getbonebase(i).value().invert());
     285            4 :                 dualquat &dst = frame[i];
     286            4 :                 if(h.parent < 0)
     287              :                 {
     288            1 :                     dst = dq;
     289              :                 }
     290              :                 else
     291              :                 {
     292            3 :                     dst.mul(skel->getbonebase(h.parent).value(), dq);
     293              :                 }
     294            4 :                 dst.fixantipodal(skel->framebones[i]);
     295              :             }
     296              :         }
     297              :     }
     298              : 
     299            1 :     if(animdata)
     300              :     {
     301            1 :         delete[] animdata;
     302              :     }
     303            1 :     delete f;
     304              : 
     305            1 :     return sas;
     306            1 : }
     307              : 
     308            3 : bool md5::md5meshgroup::loadmesh(std::string_view filename, float smooth, part &p)
     309              : {
     310            3 :     stream *f = openfile(filename.data(), "r");
     311            3 :     if(!f) //immediately bail if no file present
     312              :     {
     313            2 :         return false;
     314              :     }
     315              :     char buf[512]; //presumably this will fail with lines over 512 char long
     316            1 :     std::vector<md5joint> basejoints;
     317           10 :     while(f->getline(buf, sizeof(buf)))
     318              :     {
     319              :         int tmp;
     320            9 :         if(std::sscanf(buf, " MD5Version %d", &tmp)==1)
     321              :         {
     322            1 :             if(tmp!=10)
     323              :             {
     324            0 :                 delete f; //bail out if md5version is not what we want
     325            0 :                 return false;
     326              :             }
     327              :         }
     328            8 :         else if(std::sscanf(buf, " numJoints %d", &tmp)==1)
     329              :         {
     330            1 :             if(tmp<1)
     331              :             {
     332            0 :                 delete f; //bail out if no joints found
     333            0 :                 return false;
     334              :             }
     335            1 :             if(skel->numbones>0) //if we have bones, keep going
     336              :             {
     337            0 :                 continue;
     338              :             }
     339            1 :             skel->createbones(tmp);
     340              :         }
     341            7 :         else if(std::sscanf(buf, " numMeshes %d", &tmp)==1)
     342              :         {
     343            1 :             if(tmp<1)
     344              :             {
     345            0 :                 delete f; //if there's no meshes, nothing to be done
     346            0 :                 return false;
     347              :             }
     348              :         }
     349            6 :         else if(std::strstr(buf, "joints {"))
     350              :         {
     351              :             string name;
     352              :             int parent;
     353            1 :             md5joint j;
     354            6 :             while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
     355              :             {
     356            4 :                 const char *curbuf = buf;
     357            4 :                       char *curname = name;
     358            4 :                 bool allowspace = false;
     359            8 :                 while(*curbuf && std::isspace(*curbuf))
     360              :                 {
     361            4 :                     curbuf++;
     362              :                 }
     363            4 :                 if(*curbuf == '"')
     364              :                 {
     365            4 :                     curbuf++;
     366            4 :                     allowspace = true;
     367              :                 }
     368           51 :                 while(*curbuf && curname < &name[sizeof(name)-1])
     369              :                 {
     370           51 :                     char c = *curbuf++;
     371           51 :                     if(c == '"')
     372              :                     {
     373            4 :                         break;
     374              :                     }
     375           47 :                     if(std::isspace(c) && !allowspace)
     376              :                     {
     377            0 :                         break;
     378              :                     }
     379           47 :                     *curname++ = c;
     380              :                 }
     381            4 :                 *curname = '\0';
     382              :                 //pickup parent, pos/orient 3-vectors
     383            4 :                 if(std::sscanf(curbuf, " %d ( %f %f %f ) ( %f %f %f )",
     384              :                     &parent, &j.pos.x, &j.pos.y, &j.pos.z,
     385            4 :                     &j.orient.x, &j.orient.y, &j.orient.z)==7)
     386              :                 {
     387            4 :                     j.pos.y = -j.pos.y;
     388            4 :                     j.orient.x = -j.orient.x;
     389            4 :                     j.orient.z = -j.orient.z;
     390            4 :                     if(basejoints.size() < skel->numbones)
     391              :                     {
     392            4 :                         skel->setbonename(basejoints.size(), name);
     393            4 :                         skel->setboneparent(basejoints.size(), parent);
     394              :                     }
     395            4 :                     j.orient.restorew();
     396            4 :                     basejoints.push_back(j); //no value to std::move POD of fundamental types
     397              :                 }
     398              :             }
     399            1 :             if(basejoints.size() != skel->numbones)
     400              :             {
     401            0 :                 delete f;
     402            0 :                 return false;
     403              :             }
     404              :         }
     405              :         //load up meshes
     406            5 :         else if(std::strstr(buf, "mesh {"))
     407              :         {
     408            1 :             md5mesh *m = new md5mesh("", this); //we will set its name later
     409            1 :             meshes.push_back(m);
     410              : 
     411            1 :             std::string modeldir(filename);
     412            1 :             modeldir.resize(modeldir.rfind("/")); //truncate to file's directory
     413            1 :             m->load(f, buf, sizeof(buf), p, modeldir);
     414            1 :             if(!m->tricount() || !m->vertcount()) //if no content in the mesh
     415              :             {
     416            0 :                 conoutf("empty mesh in %s", filename.data());
     417            0 :                 auto itr = std::find(meshes.begin(), meshes.end(), m);
     418            0 :                 if(itr != meshes.end())
     419              :                 {
     420            0 :                     meshes.erase(itr);
     421              :                 }
     422            0 :                 delete m;
     423              :             }
     424            1 :         }
     425              :     }
     426              : 
     427            1 :     skel->linkchildren();
     428              :     //assign basejoints accumulated above
     429              :     {
     430            1 :         std::vector<dualquat> bases;
     431            5 :         for(const md5joint &m : basejoints)
     432              :         {
     433            4 :             bases.emplace_back(m.orient, m.pos);
     434              :         }
     435            1 :         skel->setbonebases(bases);
     436            1 :     }
     437            2 :     for(size_t i = 0; i < meshes.size(); i++)
     438              :     {
     439            1 :         md5mesh &m = *static_cast<md5mesh *>(meshes[i]);
     440            1 :         m.buildverts(basejoints);
     441            1 :         if(smooth <= 1)
     442              :         {
     443            0 :             m.smoothnorms(smooth);
     444              :         }
     445              :         else
     446              :         {
     447            1 :             m.buildnorms();
     448              :         }
     449            1 :         m.calctangents();
     450            1 :         m.cleanup();
     451              :     }
     452              : 
     453            1 :     sortblendcombos();
     454              : 
     455            1 :     delete f;
     456            1 :     return true;
     457            1 : }
     458              : 
     459            3 : bool md5::md5meshgroup::load(std::string_view meshfile, float smooth, part &p)
     460              : {
     461            3 :     name = meshfile;
     462              : 
     463            3 :     if(!loadmesh(meshfile, smooth, p))
     464              :     {
     465            2 :         return false;
     466              :     }
     467            1 :     return true;
     468              : }
     469              : 
     470            1 : md5::md5mesh::md5mesh(std::string_view name, meshgroup *m) :
     471              :     skelmesh(name, nullptr, 0, nullptr, 0, m),
     472            1 :     weightinfo(nullptr),
     473            1 :     numweights(0),
     474            1 :     vertinfo(nullptr)
     475              : {
     476            1 : }
     477              : 
     478            0 : md5::md5mesh::~md5mesh()
     479              : {
     480            0 :     cleanup();
     481            0 : }
     482              : 
     483            1 : void md5::md5mesh::cleanup()
     484              : {
     485            1 :     delete[] weightinfo;
     486            1 :     delete[] vertinfo;
     487            1 :     vertinfo = nullptr;
     488            1 :     weightinfo = nullptr;
     489            1 : }
     490              : 
     491            1 : void md5::md5mesh::buildverts(const std::vector<md5joint> &joints)
     492              : {
     493          439 :     for(int i = 0; i < numverts; ++i)
     494              :     {
     495          438 :         md5vert &v = vertinfo[i];
     496          438 :         vec pos(0, 0, 0);
     497          935 :         for(size_t k = 0; k < v.count; ++k)
     498              :         {
     499          497 :             const md5weight &w = weightinfo[v.start+k];
     500          497 :             const md5joint &j = joints[w.joint];
     501          497 :             vec wpos = j.orient.rotate(w.pos);
     502          497 :             wpos.add(j.pos);
     503          497 :             wpos.mul(w.bias);
     504          497 :             pos.add(wpos);
     505              :         }
     506          438 :         vert &vv = verts[i];
     507          438 :         vv.pos = pos;
     508          438 :         vv.tc = v.tc;
     509              : 
     510          438 :         blendcombo c;
     511          438 :         int sorted = 0;
     512          935 :         for(size_t j = 0; j < v.count; ++j)
     513              :         {
     514          497 :             const md5weight &w = weightinfo[v.start+j];
     515          497 :             sorted = c.addweight(sorted, w.bias, w.joint);
     516              :         }
     517          438 :         c.finalize(sorted);
     518          438 :         vv.blend = addblendcombo(c);
     519              :     }
     520            1 : }
     521              : 
     522              : //md5 model loader
     523            1 : void  md5::md5mesh::load(stream *f, char *buf, size_t bufsize, part &p, const std::string &modeldir)
     524              : {
     525            1 :     md5weight w;
     526            1 :     md5vert v;
     527              :     tri t;
     528              :     int index;
     529              : 
     530         1405 :     while(f->getline(buf, bufsize) && buf[0]!='}')
     531              :     {
     532         1404 :         if(std::strstr(buf, "// meshes:"))
     533              :         {
     534            0 :             char *start = std::strchr(buf, ':')+1;
     535            0 :             if(*start==' ')
     536              :             {
     537            0 :                 start++;
     538              :             }
     539            0 :             char *end = start + std::strlen(start)-1;
     540            0 :             while(end >= start && std::isspace(*end))
     541              :             {
     542            0 :                 end--;
     543              :             }
     544            0 :             name = std::string(start, end+1-start);
     545              :         }
     546         1404 :         else if(std::strstr(buf, "shader"))
     547              :         {
     548            1 :             char *start = std::strchr(buf, '"'),
     549            1 :                  *end = start ? std::strchr(start+1, '"') : nullptr;
     550            1 :             if(start && end)
     551              :             {
     552            1 :                 char *texname = newstring(start+1, end-(start+1));
     553            1 :                 p.initskins(notexture, notexture, group->meshes.size());
     554            1 :                 skin &s = p.skins.back();
     555            1 :                 s.tex = textureload(makerelpath(modeldir.c_str(), texname), 0, true, false);
     556            1 :                 delete[] texname;
     557              :             }
     558              :         }
     559              :         //create the vert arrays
     560         1403 :         else if(std::sscanf(buf, " numverts %d", &numverts)==1)
     561              :         {
     562            1 :             numverts = std::max(numverts, 0);
     563            1 :             if(numverts)
     564              :             {
     565          439 :                 vertinfo = new md5vert[numverts];
     566          439 :                 verts = new vert[numverts];
     567              :             }
     568              :         }
     569              :         //create tri array
     570         1402 :         else if(std::sscanf(buf, " numtris %d", &numtris)==1)
     571              :         {
     572            1 :             numtris = std::max(numtris, 0);
     573            1 :             if(numtris)
     574              :             {
     575            1 :                 tris = new tri[numtris];
     576              :             }
     577              :         }
     578              :         //create md5weight array
     579         1401 :         else if(std::sscanf(buf, " numweights %d", &numweights)==1)
     580              :         {
     581            1 :             numweights = std::max(numweights, 0);
     582            1 :             if(numweights)
     583              :             {
     584          498 :                 weightinfo = new md5weight[numweights];
     585              :             }
     586              :         }
     587              :         //assign md5verts to vertinfo array
     588         1400 :         else if(std::sscanf(buf, " vert %d ( %f %f ) %u %u", &index, &v.tc.x, &v.tc.y, &v.start, &v.count)==5)
     589              :         {
     590          438 :             if(index>=0 && index<numverts)
     591              :             {
     592          438 :                 vertinfo[index] = v;
     593              :             }
     594              :         }
     595              :         // assign tris to tri array
     596          962 :         else if(std::sscanf(buf, " tri %d %u %u %u", &index, &t.vert[0], &t.vert[1], &t.vert[2])==4)
     597              :         {
     598          462 :             if(index>=0 && index<numtris)
     599              :             {
     600          462 :                 tris[index] = t;
     601              :             }
     602              :         }
     603              :         //assign md5weights to weights array
     604          500 :         else if(std::sscanf(buf, " weight %d %d %f ( %f %f %f ) ", &index, &w.joint, &w.bias, &w.pos.x, &w.pos.y, &w.pos.z)==6)
     605              :         {
     606          497 :             w.pos.y = -w.pos.y;
     607          497 :             if(index>=0 && index<numweights)
     608              :             {
     609          497 :                 weightinfo[index] = w;
     610              :             }
     611              :         }
     612              :     }
     613            1 : }
        

Generated by: LCOV version 2.0-1