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-02-18 06:21:28 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              : 
      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           16 :     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(std::move(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); //no value to std::move POD of fundamental types
     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            6 :             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); //no value to std::move POD of fundamental types
     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 2.0-1