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-04-16 12:09:02 Functions: 87.5 % 16 14

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

Generated by: LCOV version 2.0-1