LCOV - code coverage report
Current view: top level - engine/model - md5.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 258 300 86.0 %
Date: 2024-11-22 05:07:59 Functions: 14 16 87.5 %

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

Generated by: LCOV version 1.14