LCOV - code coverage report
Current view: top level - engine/model - md5.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 86.1 % 303 261
Test Date: 2026-08-20 06:51:03 Functions: 87.5 % 16 14

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

Generated by: LCOV version 2.0-1