LCOV - code coverage report
Current view: top level - engine/render - rendermodel.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 7.5 % 639 48
Test Date: 2025-02-21 06:59:27 Functions: 19.1 % 47 9

            Line data    Source code
       1              : /* rendermodel.cpp: world static and dynamic models
       2              :  *
       3              :  * Libprimis can handle static ("mapmodel") type models which are placed in levels
       4              :  * as well as dynamic, animated models such as players or other actors. For animated
       5              :  * models, the md5 model format is supported; simpler static models can use the
       6              :  * common Wavefront (obj) model format.
       7              :  *
       8              :  */
       9              : #include "../libprimis-headers/cube.h"
      10              : #include "../../shared/geomexts.h"
      11              : #include "../../shared/glemu.h"
      12              : #include "../../shared/glexts.h"
      13              : #include "../../shared/stream.h"
      14              : 
      15              : #include <optional>
      16              : #include <memory>
      17              : 
      18              : #include "aa.h"
      19              : #include "csm.h"
      20              : #include "radiancehints.h"
      21              : #include "rendergl.h"
      22              : #include "renderlights.h"
      23              : #include "rendermodel.h"
      24              : #include "renderva.h"
      25              : #include "renderwindow.h"
      26              : #include "shader.h"
      27              : #include "shaderparam.h"
      28              : #include "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/octaedit.h"
      36              : #include "world/octaworld.h"
      37              : #include "world/bih.h"
      38              : #include "world/world.h"
      39              : 
      40              : VAR(oqdynent, 0, 1, 1); //occlusion query dynamic ents
      41              : 
      42              : std::vector<std::string> animnames; //set by game at runtime
      43              : 
      44              : //need the above vars inited before these headers will load properly
      45              : 
      46              : #include "model/model.h"
      47              : #include "model/ragdoll.h"
      48              : #include "model/animmodel.h"
      49              : #include "model/vertmodel.h"
      50              : #include "model/skelmodel.h"
      51              : 
      52            0 : model *loadmapmodel(int n)
      53              : {
      54            0 :     if(static_cast<int>(mapmodel::mapmodels.size()) > n)
      55              :     {
      56            0 :         model *m = mapmodel::mapmodels[n].m;
      57            0 :         return m ? m : loadmodel("", n);
      58              :     }
      59            0 :     return nullptr;
      60              : }
      61              : 
      62              : //need the above macros & fxns inited before these headers will load properly
      63              : #include "model/md5.h"
      64              : #include "model/obj.h"
      65              : #include "model/gltf.h"
      66              : 
      67              : // mapmodels
      68              : 
      69              : namespace mapmodel
      70              : {
      71              :     std::vector<mapmodelinfo> mapmodels;
      72              :     static const std::string mmprefix = "mapmodel/";
      73              :     static const size_t mmprefixlen = mmprefix.size();
      74              : 
      75            1 :     void open(const char *name)
      76              :     {
      77            1 :         mapmodelinfo mmi;
      78            1 :         if(name[0])
      79              :         {
      80            0 :             mmi.name = std::string().append(mmprefix).append(name);
      81              :         }
      82              :         else
      83              :         {
      84            1 :             mmi.name.clear();
      85              :         }
      86            1 :         mmi.m = mmi.collide = nullptr;
      87            1 :         mapmodels.push_back(mmi);
      88            1 :     }
      89              : 
      90            1 :     void reset(const int *n)
      91              :     {
      92            1 :         if(!(identflags&Idf_Overridden) && !allowediting)
      93              :         {
      94            1 :             return;
      95              :         }
      96            0 :         mapmodels.resize(std::clamp(*n, 0, static_cast<int>(mapmodels.size())));
      97              :     }
      98              : 
      99            0 :     const char *name(int i)
     100              :     {
     101            0 :         return (static_cast<int>(mapmodels.size()) > i) ? mapmodels[i].name.c_str() : nullptr;
     102              :     }
     103              : 
     104            1 :     void namecmd(const int *index, const int *prefix)
     105              :     {
     106            1 :         if(static_cast<int>(mapmodels.size()) > *index)
     107              :         {
     108            0 :             result(mapmodels[*index].name.empty() ? mapmodels[*index].name.c_str() + (*prefix ? 0 : mmprefixlen) : "");
     109              :         }
     110            1 :     }
     111              : 
     112            1 :     void loaded(const int *index)
     113              :     {
     114            1 :         intret(static_cast<int>(mapmodels.size()) > *index && mapmodels[*index].m ? 1 : 0);
     115            1 :     }
     116              : 
     117            1 :     void num()
     118              :     {
     119            1 :         intret(mapmodels.size());
     120            1 :     }
     121              : }
     122              : 
     123              : // model registry
     124              : 
     125              : std::unordered_map<std::string, model *> models;
     126              : std::vector<std::string> preloadmodels;
     127              : 
     128              : //used in iengine
     129            0 : void preloadmodel(std::string name)
     130              : {
     131            0 :     if(name.empty() || models.find(name) != models.end() || std::find(preloadmodels.begin(), preloadmodels.end(), name) != preloadmodels.end() )
     132              :     {
     133            0 :         return;
     134              :     }
     135            0 :     preloadmodels.push_back(name);
     136              : }
     137              : 
     138            0 : void flushpreloadedmodels(bool msg)
     139              : {
     140            0 :     for(uint i = 0; i < preloadmodels.size(); i++)
     141              :     {
     142            0 :         loadprogress = static_cast<float>(i+1)/preloadmodels.size();
     143            0 :         model *m = loadmodel(preloadmodels[i].c_str(), -1, msg);
     144            0 :         if(!m)
     145              :         {
     146            0 :             if(msg)
     147              :             {
     148            0 :                 conoutf(Console_Warn, "could not load model: %s", preloadmodels[i].c_str());
     149              :             }
     150              :         }
     151              :         else
     152              :         {
     153            0 :             m->preloadmeshes();
     154            0 :             m->preloadshaders();
     155              :         }
     156              :     }
     157            0 :     preloadmodels.clear();
     158              : 
     159            0 :     loadprogress = 0;
     160            0 : }
     161              : 
     162            0 : void preloadusedmapmodels(bool msg, bool bih)
     163              : {
     164            0 :     std::vector<extentity *> &ents = entities::getents();
     165            0 :     std::vector<int> used;
     166            0 :     for(extentity *&e : ents)
     167              :     {
     168            0 :         if(e->type==EngineEnt_Mapmodel && e->attr1 >= 0 && std::find(used.begin(), used.end(), e->attr1) != used.end() )
     169              :         {
     170            0 :             used.push_back(e->attr1);
     171              :         }
     172              :     }
     173              : 
     174            0 :     std::vector<std::string> col;
     175            0 :     for(uint i = 0; i < used.size(); i++)
     176              :     {
     177            0 :         loadprogress = static_cast<float>(i+1)/used.size();
     178            0 :         int mmindex = used[i];
     179            0 :         if(!(static_cast<int>(mapmodel::mapmodels.size()) > (mmindex)))
     180              :         {
     181            0 :             if(msg)
     182              :             {
     183            0 :                 conoutf(Console_Warn, "could not find map model: %d", mmindex);
     184              :             }
     185            0 :             continue;
     186              :         }
     187            0 :         const mapmodelinfo &mmi = mapmodel::mapmodels[mmindex];
     188            0 :         if(mmi.name.empty())
     189              :         {
     190            0 :             continue;
     191              :         }
     192            0 :         model *m = loadmodel("", mmindex, msg);
     193            0 :         if(!m)
     194              :         {
     195            0 :             if(msg)
     196              :             {
     197            0 :                 conoutf(Console_Warn, "could not load map model: %s", mmi.name.c_str());
     198              :             }
     199              :         }
     200              :         else
     201              :         {
     202            0 :             if(bih)
     203              :             {
     204            0 :                 m->preloadBIH();
     205              :             }
     206            0 :             else if(m->collide == Collide_TRI && m->collidemodel.empty() && m->bih)
     207              :             {
     208            0 :                 m->setBIH();
     209              :             }
     210            0 :             m->preloadmeshes();
     211            0 :             m->preloadshaders();
     212            0 :             if(!m->collidemodel.empty() && std::find(col.begin(), col.end(), m->collidemodel) == col.end())
     213              :             {
     214            0 :                 col.push_back(m->collidemodel);
     215              :             }
     216              :         }
     217              :     }
     218              : 
     219            0 :     for(uint i = 0; i < col.size(); i++)
     220              :     {
     221            0 :         loadprogress = static_cast<float>(i+1)/col.size();
     222            0 :         model *m = loadmodel(col[i].c_str(), -1, msg);
     223            0 :         if(!m)
     224              :         {
     225            0 :             if(msg)
     226              :             {
     227            0 :                 conoutf(Console_Warn, "could not load collide model: %s", col[i].c_str());
     228              :             }
     229              :         }
     230            0 :         else if(!m->bih)
     231              :         {
     232            0 :             m->setBIH();
     233              :         }
     234              :     }
     235              : 
     236            0 :     loadprogress = 0;
     237            0 : }
     238              : 
     239            0 : model *loadmodel(std::string_view name, int i, bool msg)
     240              : {
     241            0 :     model *(__cdecl *md5loader)(const std::string &filename) = +[] (const std::string &filename) -> model* { return new md5(filename); };
     242            0 :     model *(__cdecl *objloader)(const std::string &filename) = +[] (const std::string &filename) -> model* { return new obj(filename); };
     243            0 :     model *(__cdecl *gltfloader)(const std::string &filename) = +[] (const std::string &filename) -> model* { return new gltf(filename); };
     244              : 
     245            0 :     std::vector<model *(__cdecl *)(const std::string &)> loaders;
     246            0 :     loaders.push_back(md5loader);
     247            0 :     loaders.push_back(objloader);
     248            0 :     loaders.push_back(gltfloader);
     249            0 :     std::unordered_set<std::string> failedmodels;
     250              : 
     251            0 :     if(!name.size())
     252              :     {
     253            0 :         if(!(static_cast<int>(mapmodel::mapmodels.size()) > i))
     254              :         {
     255            0 :             return nullptr;
     256              :         }
     257            0 :         const mapmodelinfo &mmi = mapmodel::mapmodels[i];
     258            0 :         if(mmi.m)
     259              :         {
     260            0 :             return mmi.m;
     261              :         }
     262            0 :         name = mmi.name.c_str();
     263              :     }
     264            0 :     auto itr = models.find(std::string(name));
     265              :     model *m;
     266            0 :     if(itr != models.end())
     267              :     {
     268            0 :         m = (*itr).second;
     269              :     }
     270              :     else
     271              :     {
     272            0 :         if(!name[0] || failedmodels.find(std::string(name)) != failedmodels.end())
     273              :         {
     274            0 :             return nullptr;
     275              :         }
     276            0 :         if(msg)
     277              :         {
     278            0 :             std::string filename;
     279            0 :             filename.append(modelpath).append(name);
     280            0 :             renderprogress(loadprogress, filename.c_str());
     281            0 :         }
     282            0 :         for(model *(__cdecl *i)(const std::string &) : loaders)
     283              :         {
     284            0 :             m = i(std::string(name)); //call model ctor
     285            0 :             if(!m)
     286              :             {
     287            0 :                 continue;
     288              :             }
     289            0 :             if(m->load()) //now load the model
     290              :             {
     291            0 :                 break;
     292              :             }
     293              :             //delete model if not successful
     294            0 :             delete m;
     295            0 :             m = nullptr;
     296              :         }
     297            0 :         if(!m)
     298              :         {
     299            0 :             failedmodels.insert(std::string(name));
     300            0 :             return nullptr;
     301              :         }
     302            0 :         if(models.find(m->modelname()) == models.end())
     303              :         {
     304            0 :             models[m->modelname()] = m;
     305              :         }
     306              :     }
     307            0 :     if((mapmodel::mapmodels.size() > static_cast<uint>(i)) && !mapmodel::mapmodels[i].m)
     308              :     {
     309            0 :         mapmodel::mapmodels[i].m = m;
     310              :     }
     311            0 :     return m;
     312            0 : }
     313              : 
     314              : //used in iengine.h
     315            0 : void clear_models()
     316              : {
     317            0 :     for(auto [k, i] : models)
     318              :     {
     319            0 :         delete i;
     320            0 :     }
     321            0 : }
     322              : 
     323            0 : void cleanupmodels()
     324              : {
     325            0 :     for(auto [k, i] : models)
     326              :     {
     327            0 :         i->cleanup();
     328            0 :     }
     329            0 : }
     330              : 
     331            1 : static void clearmodel(const char *name)
     332              : {
     333            1 :     model *m = nullptr;
     334            1 :     const auto it = models.find(name);
     335            1 :     if(it != models.end())
     336              :     {
     337            0 :         m = (*it).second;
     338              :     }
     339            1 :     if(!m)
     340              :     {
     341            1 :         conoutf("model %s is not loaded", name);
     342            1 :         return;
     343              :     }
     344            0 :     for(mapmodelinfo &mmi : mapmodel::mapmodels)
     345              :     {
     346            0 :         if(mmi.m == m)
     347              :         {
     348            0 :             mmi.m = nullptr;
     349              :         }
     350            0 :         if(mmi.collide == m)
     351              :         {
     352            0 :             mmi.collide = nullptr;
     353              :         }
     354              :     }
     355            0 :     models.erase(name);
     356            0 :     m->cleanup();
     357            0 :     delete m;
     358            0 :     conoutf("cleared model %s", name);
     359              : }
     360              : 
     361            0 : static bool modeloccluded(const vec &center, float radius)
     362              : {
     363            0 :     ivec bbmin(vec(center).sub(radius)),
     364            0 :          bbmax(vec(center).add(radius+1));
     365            0 :     return rootworld.bboccluded(bbmin, bbmax);
     366              : }
     367              : 
     368              : struct modelbatch
     369              : {
     370              :     const model *m;
     371              :     int flags, batched;
     372              : };
     373              : 
     374              : struct batchedmodel
     375              : {
     376              :     //orient = yaw, pitch, roll
     377              :     vec pos, orient, center;
     378              :     float radius, sizescale;
     379              :     vec4<float> colorscale;
     380              :     int anim, basetime, basetime2, flags, attached;
     381              :     union
     382              :     {
     383              :         int visible;
     384              :         int culled;
     385              :     };
     386              :     dynent *d;
     387              :     int next;
     388              : 
     389              :     void renderbatchedmodel(const model *m) const;
     390              :     //sets bbmin and bbmax to the min/max of itself and the batchedmodel's bb
     391              :     void applybb(vec &bbmin, vec &bbmax) const;
     392              :     bool shadowmask(bool dynshadow);
     393              : 
     394              :     int rendertransparentmodel(const modelbatch &b, bool &rendered);
     395              : };
     396              : 
     397              : static std::vector<batchedmodel> batchedmodels;
     398              : static std::vector<modelbatch> batches;
     399              : static std::vector<modelattach> modelattached;
     400              : 
     401            0 : void resetmodelbatches()
     402              : {
     403            0 :     batchedmodels.clear();
     404            0 :     batches.clear();
     405            0 :     modelattached.clear();
     406            0 : }
     407              : 
     408            0 : void addbatchedmodel(model *m, batchedmodel &bm, int idx)
     409              : {
     410            0 :     modelbatch *b = nullptr;
     411            0 :     if(batches.size() > static_cast<uint>(m->batch))
     412              :     {
     413            0 :         b = &batches[m->batch];
     414            0 :         if(b->m == m && (b->flags & Model_Mapmodel) == (bm.flags & Model_Mapmodel))
     415              :         {
     416            0 :             goto foundbatch; //skip some shit
     417              :         }
     418              :     }
     419            0 :     m->batch = batches.size();
     420            0 :     batches.emplace_back();
     421            0 :     b = &batches.back();
     422            0 :     b->m = m;
     423            0 :     b->flags = 0;
     424            0 :     b->batched = -1;
     425              : 
     426            0 : foundbatch:
     427            0 :     b->flags |= bm.flags;
     428            0 :     bm.next = b->batched;
     429            0 :     b->batched = idx;
     430            0 : }
     431              : 
     432            0 : void batchedmodel::renderbatchedmodel(const model *m) const
     433              : {
     434            0 :     modelattach *a = nullptr;
     435            0 :     if(attached>=0)
     436              :     {
     437            0 :         a = &modelattached[attached];
     438              :     }
     439            0 :     int tempanim = anim;
     440            0 :     if(shadowmapping > ShadowMap_Reflect)
     441              :     {
     442            0 :         tempanim |= Anim_NoSkin;
     443              :     }
     444              :     else
     445              :     {
     446            0 :         if(flags&Model_FullBright)
     447              :         {
     448            0 :             tempanim |= Anim_FullBright;
     449              :         }
     450              :     }
     451              : 
     452            0 :     m->render(tempanim, basetime, basetime2, pos, orient.x, orient.y, orient.z, d, a, sizescale, colorscale);
     453            0 : }
     454              : 
     455              : //ratio between model size and distance at which to cull: at 200, model must be 200 times smaller than distance to model
     456              : VAR(maxmodelradiusdistance, 10, 200, 1000);
     457              : 
     458            0 : static void rendercullmodelquery(dynent *d, const vec &center, float radius)
     459              : {
     460            0 :     if(std::fabs(camera1->o.x-center.x) < radius+1 &&
     461            0 :        std::fabs(camera1->o.y-center.y) < radius+1 &&
     462            0 :        std::fabs(camera1->o.z-center.z) < radius+1)
     463              :     {
     464            0 :         d->query = nullptr;
     465            0 :         return;
     466              :     }
     467            0 :     d->query = occlusionengine.newquery(d);
     468            0 :     if(!d->query)
     469              :     {
     470            0 :         return;
     471              :     }
     472            0 :     d->query->startquery();
     473            0 :     int br = static_cast<int>(radius*2)+1;
     474            0 :     drawbb(ivec(static_cast<float>(center.x-radius), static_cast<float>(center.y-radius), static_cast<float>(center.z-radius)), ivec(br, br, br));
     475            0 :     occlusionengine.endquery();
     476              : }
     477              : 
     478              : /**
     479              :  * @brief Returns whether the model should be culled.
     480              :  *
     481              :  * Attempts to cull by distance from camera1, then by view frustum from `vfc` view,
     482              :  * then by occlusion query.
     483              :  *
     484              :  * If no reason can be found to occlude the model, returns 0. Otherwise, returns
     485              :  * the Model enum flag for the cull reason.
     486              :  */
     487            0 : static int cullmodel(const vec &center, float radius, int flags, const dynent *d = nullptr)
     488              : {
     489            0 :     if(flags&Model_CullDist && (center.dist(camera1->o) / radius) > maxmodelradiusdistance)
     490              :     {
     491            0 :         return Model_CullDist;
     492              :     }
     493            0 :     if(flags&Model_CullVFC && view.isfoggedsphere(radius, center))
     494              :     {
     495            0 :         return Model_CullVFC;
     496              :     }
     497            0 :     if(flags&Model_CullOccluded && modeloccluded(center, radius))
     498              :     {
     499            0 :         return Model_CullOccluded;
     500              :     }
     501            0 :     else if(flags&Model_CullQuery && d->query && d->query->owner==d && occlusionengine.checkquery(d->query))
     502              :     {
     503            0 :         return Model_CullQuery;
     504              :     }
     505            0 :     return 0;
     506              : }
     507              : 
     508            0 : static int shadowmaskmodel(const vec &center, float radius)
     509              : {
     510            0 :     switch(shadowmapping)
     511              :     {
     512            0 :         case ShadowMap_Reflect:
     513            0 :             return calcspherersmsplits(center, radius);
     514            0 :         case ShadowMap_CubeMap:
     515              :         {
     516            0 :             vec scenter = vec(center).sub(shadoworigin);
     517            0 :             float sradius = radius + shadowradius;
     518            0 :             if(scenter.squaredlen() >= sradius*sradius)
     519              :             {
     520            0 :                 return 0;
     521              :             }
     522            0 :             return calcspheresidemask(scenter, radius, shadowbias);
     523              :         }
     524            0 :         case ShadowMap_Cascade:
     525              :         {
     526            0 :             return csm.calcspherecsmsplits(center, radius);
     527              :         }
     528            0 :         case ShadowMap_Spot:
     529              :         {
     530            0 :             vec scenter = vec(center).sub(shadoworigin);
     531            0 :             float sradius = radius + shadowradius;
     532            0 :             return scenter.squaredlen() < sradius*sradius && sphereinsidespot(shadowdir, shadowspot, scenter, radius) ? 1 : 0;
     533              :         }
     534              :     }
     535            0 :     return 0;
     536              : }
     537              : 
     538            0 : bool batchedmodel::shadowmask(bool dynshadow)
     539              : {
     540            0 :     if(flags&(Model_Mapmodel | Model_NoShadow)) //mapmodels are not dynamic models by definition
     541              :     {
     542            0 :         return false;
     543              :     }
     544            0 :     visible = dynshadow && (colorscale.a() >= 1 || flags&(Model_OnlyShadow | Model_ForceShadow)) ? shadowmaskmodel(center, radius) : 0;
     545            0 :     return true;
     546              : }
     547              : 
     548            0 : void shadowmaskbatchedmodels(bool dynshadow)
     549              : {
     550            0 :     for(batchedmodel &b : batchedmodels)
     551              :     {
     552            0 :         if(!b.shadowmask(dynshadow))
     553              :         {
     554            0 :             break;
     555              :         }
     556              :     }
     557            0 : }
     558              : 
     559            0 : int batcheddynamicmodels()
     560              : {
     561            0 :     int visible = 0;
     562            0 :     for(const batchedmodel &b : batchedmodels)
     563              :     {
     564            0 :         if(b.flags&Model_Mapmodel) //mapmodels are not dynamic models by definition
     565              :         {
     566            0 :             break;
     567              :         }
     568            0 :         visible |= b.visible;
     569              :     }
     570            0 :     for(const modelbatch &b : batches)
     571              :     {
     572            0 :         if(!(b.flags&Model_Mapmodel) || !b.m->animated())
     573              :         {
     574            0 :             continue;
     575              :         }
     576            0 :         for(int j = b.batched; j >= 0;)
     577              :         {
     578            0 :             const batchedmodel &bm = batchedmodels[j];
     579            0 :             j = bm.next;
     580            0 :             visible |= bm.visible;
     581              :         }
     582              :     }
     583            0 :     return visible;
     584              : }
     585              : 
     586            0 : void batchedmodel::applybb(vec &bbmin, vec &bbmax) const
     587              : {
     588            0 :     bbmin.min(vec(center).sub(radius));
     589            0 :     bbmax.max(vec(center).add(radius));
     590            0 : }
     591              : 
     592            0 : int batcheddynamicmodelbounds(int mask, vec &bbmin, vec &bbmax)
     593              : {
     594            0 :     int vis = 0;
     595            0 :     for(const batchedmodel &b : batchedmodels)
     596              :     {
     597            0 :         if(b.flags&Model_Mapmodel) //mapmodels are not dynamic models by definition
     598              :         {
     599            0 :             break;
     600              :         }
     601            0 :         if(b.visible&mask)
     602              :         {
     603            0 :             b.applybb(bbmin, bbmax);
     604            0 :             ++vis;
     605              :         }
     606              :     }
     607            0 :     for(const modelbatch &b : batches)
     608              :     {
     609            0 :         if(!(b.flags&Model_Mapmodel) || !b.m->animated())
     610              :         {
     611            0 :             continue;
     612              :         }
     613            0 :         for(int j = b.batched; j >= 0;)
     614              :         {
     615            0 :             const batchedmodel &bm = batchedmodels[j];
     616            0 :             j = bm.next;
     617            0 :             if(bm.visible&mask)
     618              :             {
     619            0 :                 bm.applybb(bbmin, bbmax);
     620            0 :                 ++vis;
     621              :             }
     622              :         }
     623              :     }
     624            0 :     return vis;
     625              : }
     626              : 
     627            0 : void rendershadowmodelbatches(bool dynmodel)
     628              : {
     629            0 :     for(const modelbatch &b : batches)
     630              :     {
     631            0 :         if(!b.m->shadow || (!dynmodel && (!(b.flags&Model_Mapmodel) || b.m->animated())))
     632              :         {
     633            0 :             continue;
     634              :         }
     635            0 :         bool rendered = false;
     636            0 :         for(int j = b.batched; j >= 0;)
     637              :         {
     638            0 :             const batchedmodel &bm = batchedmodels[j];
     639            0 :             j = bm.next;
     640            0 :             if(!(bm.visible&(1<<shadowside)))
     641              :             {
     642            0 :                 continue;
     643              :             }
     644            0 :             if(!rendered)
     645              :             {
     646            0 :                 b.m->startrender();
     647            0 :                 rendered = true;
     648              :             }
     649            0 :             bm.renderbatchedmodel(b.m);
     650              :         }
     651            0 :         if(rendered)
     652              :         {
     653            0 :             b.m->endrender();
     654              :         }
     655              :     }
     656            0 : }
     657              : 
     658            0 : void rendermapmodelbatches()
     659              : {
     660            0 :     aamask::enable();
     661            0 :     for(const modelbatch &b : batches)
     662              :     {
     663            0 :         if(!(b.flags&Model_Mapmodel))
     664              :         {
     665            0 :             continue;
     666              :         }
     667            0 :         b.m->startrender();
     668            0 :         aamask::set(b.m->animated());
     669            0 :         for(int j = b.batched; j >= 0;)
     670              :         {
     671            0 :             const batchedmodel &bm = batchedmodels[j];
     672            0 :             bm.renderbatchedmodel(b.m);
     673            0 :             j = bm.next;
     674              :         }
     675            0 :         b.m->endrender();
     676              :     }
     677            0 :     aamask::disable();
     678            0 : }
     679              : 
     680            0 : void GBuffer::rendermodelbatches()
     681              : {
     682            0 :     tmodelinfo.mdlsx1 = tmodelinfo.mdlsy1 = 1;
     683            0 :     tmodelinfo.mdlsx2 = tmodelinfo.mdlsy2 = -1;
     684            0 :     tmodelinfo.mdltiles.fill(0);
     685              : 
     686            0 :     aamask::enable();
     687            0 :     for(const modelbatch &b : batches)
     688              :     {
     689            0 :         if(b.flags&Model_Mapmodel)
     690              :         {
     691            0 :             continue;
     692              :         }
     693            0 :         bool rendered = false;
     694            0 :         for(int j = b.batched; j >= 0;)
     695              :         {
     696            0 :             batchedmodel &bm = batchedmodels[j];
     697            0 :             j = bm.next;
     698            0 :             bm.culled = cullmodel(bm.center, bm.radius, bm.flags, bm.d);
     699            0 :             if(bm.culled || bm.flags&Model_OnlyShadow)
     700              :             {
     701            0 :                 continue;
     702              :             }
     703            0 :             if(bm.colorscale.a() < 1 || bm.flags&Model_ForceTransparent)
     704              :             {
     705              :                 float sx1, sy1, sx2, sy2;
     706            0 :                 ivec bbmin(vec(bm.center).sub(bm.radius)), bbmax(vec(bm.center).add(bm.radius+1));
     707            0 :                 if(calcbbscissor(bbmin, bbmax, sx1, sy1, sx2, sy2))
     708              :                 {
     709            0 :                     tmodelinfo.mdlsx1 = std::min(tmodelinfo.mdlsx1, sx1);
     710            0 :                     tmodelinfo.mdlsy1 = std::min(tmodelinfo.mdlsy1, sy1);
     711            0 :                     tmodelinfo.mdlsx2 = std::max(tmodelinfo.mdlsx2, sx2);
     712            0 :                     tmodelinfo.mdlsy2 = std::max(tmodelinfo.mdlsy2, sy2);
     713            0 :                     masktiles(tmodelinfo.mdltiles.data(), sx1, sy1, sx2, sy2);
     714              :                 }
     715            0 :                 continue;
     716            0 :             }
     717            0 :             if(!rendered)
     718              :             {
     719            0 :                 b.m->startrender();
     720            0 :                 rendered = true;
     721            0 :                 aamask::set(true);
     722              :             }
     723            0 :             if(bm.flags&Model_CullQuery)
     724              :             {
     725            0 :                 bm.d->query = occlusionengine.newquery(bm.d);
     726            0 :                 if(bm.d->query)
     727              :                 {
     728            0 :                     bm.d->query->startquery();
     729            0 :                     bm.renderbatchedmodel(b.m);
     730            0 :                     occlusionengine.endquery();
     731            0 :                     continue;
     732              :                 }
     733              :             }
     734            0 :             bm.renderbatchedmodel(b.m);
     735              :         }
     736            0 :         if(rendered)
     737              :         {
     738            0 :             b.m->endrender();
     739              :         }
     740            0 :         if(b.flags&Model_CullQuery)
     741              :         {
     742            0 :             bool queried = false;
     743            0 :             for(int j = b.batched; j >= 0;)
     744              :             {
     745            0 :                 batchedmodel &bm = batchedmodels[j];
     746            0 :                 j = bm.next;
     747            0 :                 if(bm.culled&(Model_CullOccluded|Model_CullQuery) && bm.flags&Model_CullQuery)
     748              :                 {
     749            0 :                     if(!queried)
     750              :                     {
     751            0 :                         if(rendered)
     752              :                         {
     753            0 :                             aamask::set(false);
     754              :                         }
     755            0 :                         startbb();
     756            0 :                         queried = true;
     757              :                     }
     758            0 :                     rendercullmodelquery(bm.d, bm.center, bm.radius);
     759              :                 }
     760              :             }
     761            0 :             if(queried)
     762              :             {
     763            0 :                 endbb();
     764              :             }
     765              :         }
     766              :     }
     767            0 :     aamask::disable();
     768            0 : }
     769              : 
     770            0 : int batchedmodel::rendertransparentmodel(const modelbatch &b, bool &rendered)
     771              : {
     772            0 :     int j = next;
     773            0 :     culled = cullmodel(center, radius, flags, d);
     774            0 :     if(culled || !(colorscale.a() < 1 || flags&Model_ForceTransparent) || flags&Model_OnlyShadow)
     775              :     {
     776            0 :         return j;
     777              :     }
     778            0 :     if(!rendered)
     779              :     {
     780            0 :         b.m->startrender();
     781            0 :         rendered = true;
     782            0 :         aamask::set(true);
     783              :     }
     784            0 :     if(flags&Model_CullQuery)
     785              :     {
     786            0 :         d->query = occlusionengine.newquery(d);
     787            0 :         if(d->query)
     788              :         {
     789            0 :             d->query->startquery();
     790            0 :             renderbatchedmodel(b.m);
     791            0 :             occlusionengine.endquery();
     792            0 :             return j;
     793              :         }
     794              :     }
     795            0 :     renderbatchedmodel(b.m);
     796            0 :     return j;
     797              : }
     798              : 
     799            0 : void rendertransparentmodelbatches(int stencil)
     800              : {
     801            0 :     aamask::enable(stencil);
     802            0 :     for(modelbatch &b : batches)
     803              :     {
     804            0 :         if(b.flags&Model_Mapmodel)
     805              :         {
     806            0 :             continue;
     807              :         }
     808            0 :         bool rendered = false;
     809            0 :         for(int j = b.batched; j >= 0;)
     810              :         {
     811            0 :             batchedmodel &bm = batchedmodels[j];
     812            0 :             bm.rendertransparentmodel(b, rendered);
     813              :         }
     814            0 :         if(rendered)
     815              :         {
     816            0 :             b.m->endrender();
     817              :         }
     818              :     }
     819            0 :     aamask::disable();
     820            0 : }
     821              : 
     822            0 : void Occluder::setupmodelquery(occludequery *q)
     823              : {
     824            0 :     modelquery = q;
     825            0 :     modelquerybatches = batches.size();
     826            0 :     modelquerymodels = batchedmodels.size();
     827            0 :     modelqueryattached = modelattached.size();
     828            0 : }
     829              : 
     830            0 : void Occluder::endmodelquery()
     831              : {
     832            0 :     if(static_cast<int>(batchedmodels.size()) == modelquerymodels)
     833              :     {
     834            0 :         modelquery->fragments = 0;
     835            0 :         modelquery = nullptr;
     836            0 :         return;
     837              :     }
     838            0 :     aamask::enable();
     839            0 :     modelquery->startquery();
     840            0 :     for(modelbatch &b : batches)
     841              :     {
     842            0 :         int j = b.batched;
     843            0 :         if(j < modelquerymodels)
     844              :         {
     845            0 :             continue;
     846              :         }
     847            0 :         b.m->startrender();
     848            0 :         aamask::set(!(b.flags&Model_Mapmodel) || b.m->animated());
     849              :         do
     850              :         {
     851            0 :             const batchedmodel &bm = batchedmodels[j];
     852            0 :             bm.renderbatchedmodel(b.m);
     853            0 :             j = bm.next;
     854            0 :         } while(j >= modelquerymodels);
     855            0 :         b.batched = j;
     856            0 :         b.m->endrender();
     857              :     }
     858            0 :     occlusionengine.endquery();
     859            0 :     modelquery = nullptr;
     860            0 :     batches.resize(modelquerybatches);
     861            0 :     batchedmodels.resize(modelquerymodels);
     862            0 :     modelattached.resize(modelqueryattached);
     863            0 :     aamask::disable();
     864              : }
     865              : 
     866            0 : void clearbatchedmapmodels()
     867              : {
     868            0 :     for(uint i = 0; i < batches.size(); i++)
     869              :     {
     870            0 :         const modelbatch &b = batches[i];
     871            0 :         if(b.flags&Model_Mapmodel)
     872              :         {
     873            0 :             batchedmodels.resize(b.batched);
     874            0 :             batches.resize(i);
     875            0 :             break;
     876              :         }
     877              :     }
     878            0 : }
     879              : 
     880            0 : void rendermapmodel(int idx, int anim, const vec &o, float yaw, float pitch, float roll, int flags, int basetime, float size)
     881              : {
     882            0 :     if(!(static_cast<int>(mapmodel::mapmodels.size()) > idx))
     883              :     {
     884            0 :         return;
     885              :     }
     886            0 :     const mapmodelinfo &mmi = mapmodel::mapmodels[idx];
     887            0 :     model *m = mmi.m ? mmi.m : loadmodel(mmi.name);
     888            0 :     if(!m)
     889              :     {
     890            0 :         return;
     891              :     }
     892            0 :     vec center, bbradius;
     893            0 :     m->boundbox(center, bbradius);
     894            0 :     float radius = bbradius.magnitude();
     895            0 :     center.mul(size);
     896            0 :     if(roll)
     897              :     {
     898            0 :         center.rotate_around_y(roll/RAD);
     899              :     }
     900            0 :     if(pitch && m->pitched())
     901              :     {
     902            0 :         center.rotate_around_x(pitch/RAD);
     903              :     }
     904            0 :     center.rotate_around_z(yaw/RAD);
     905            0 :     center.add(o);
     906            0 :     radius *= size;
     907              : 
     908            0 :     int visible = 0;
     909            0 :     if(shadowmapping)
     910              :     {
     911            0 :         if(!m->shadow)
     912              :         {
     913            0 :             return;
     914              :         }
     915            0 :         visible = shadowmaskmodel(center, radius);
     916            0 :         if(!visible)
     917              :         {
     918            0 :             return;
     919              :         }
     920              :     }
     921            0 :     else if(flags&(Model_CullVFC|Model_CullDist|Model_CullOccluded) && cullmodel(center, radius, flags))
     922              :     {
     923            0 :         return;
     924              :     }
     925            0 :     batchedmodels.emplace_back();
     926            0 :     batchedmodel &b = batchedmodels.back();
     927            0 :     b.pos = o;
     928            0 :     b.center = center;
     929            0 :     b.radius = radius;
     930            0 :     b.anim = anim;
     931            0 :     b.orient = {yaw, pitch, roll};
     932            0 :     b.basetime = basetime;
     933            0 :     b.basetime2 = 0;
     934            0 :     b.sizescale = size;
     935            0 :     b.colorscale = vec4<float>(1, 1, 1, 1);
     936            0 :     b.flags = flags | Model_Mapmodel;
     937            0 :     b.visible = visible;
     938            0 :     b.d = nullptr;
     939            0 :     b.attached = -1;
     940            0 :     addbatchedmodel(m, b, batchedmodels.size()-1);
     941              : }
     942              : 
     943            0 : void rendermodel(std::string_view mdl, int anim, const vec &o, float yaw, float pitch, float roll, int flags, dynent *d, modelattach *a, int basetime, int basetime2, float size, const vec4<float> &color)
     944              : {
     945            0 :     model *m = loadmodel(mdl);
     946            0 :     if(!m)
     947              :     {
     948            0 :         return;
     949              :     }
     950              : 
     951            0 :     vec center, bbradius;
     952            0 :     m->boundbox(center, bbradius);
     953            0 :     float radius = bbradius.magnitude();
     954            0 :     if(d)
     955              :     {
     956            0 :         if(d->ragdoll)
     957              :         {
     958            0 :             if(anim & Anim_Ragdoll && d->ragdoll->millis >= basetime)
     959              :             {
     960            0 :                 radius = std::max(radius, d->ragdoll->radius);
     961            0 :                 center = d->ragdoll->center;
     962            0 :                 goto hasboundbox; //skip roll and pitch stuff
     963              :             }
     964            0 :             if(d->ragdoll)
     965              :             {
     966            0 :                 delete d->ragdoll;
     967            0 :                 d->ragdoll = nullptr;
     968              :             }
     969              :         }
     970            0 :         if(anim & Anim_Ragdoll)
     971              :         {
     972            0 :             flags &= ~(Model_CullVFC | Model_CullOccluded | Model_CullQuery);
     973              :         }
     974              :     }
     975            0 :     center.mul(size);
     976            0 :     if(roll)
     977              :     {
     978            0 :         center.rotate_around_y(roll/RAD);
     979              :     }
     980            0 :     if(pitch && m->pitched())
     981              :     {
     982            0 :         center.rotate_around_x(pitch/RAD);
     983              :     }
     984            0 :     center.rotate_around_z(yaw/RAD);
     985            0 :     center.add(o);
     986            0 : hasboundbox:
     987            0 :     radius *= size;
     988              : 
     989            0 :     if(flags&Model_NoRender)
     990              :     {
     991            0 :         anim |= Anim_NoRender;
     992              :     }
     993              : 
     994            0 :     if(a)
     995              :     {
     996            0 :         for(int i = 0; a[i].tag; i++)
     997              :         {
     998            0 :             if(a[i].name)
     999              :             {
    1000            0 :                 a[i].m = loadmodel(a[i].name);
    1001              :             }
    1002              :         }
    1003              :     }
    1004              : 
    1005            0 :     if(flags&Model_CullQuery)
    1006              :     {
    1007            0 :         if(!oqfrags || !oqdynent || !d)
    1008              :         {
    1009            0 :             flags &= ~Model_CullQuery;
    1010              :         }
    1011              :     }
    1012              : 
    1013            0 :     if(flags&Model_NoBatch)
    1014              :     {
    1015            0 :         const int culled = cullmodel(center, radius, flags, d);
    1016            0 :         if(culled)
    1017              :         {
    1018            0 :             if(culled&(Model_CullOccluded|Model_CullQuery) && flags&Model_CullQuery)
    1019              :             {
    1020            0 :                 startbb();
    1021            0 :                 rendercullmodelquery(d, center, radius);
    1022            0 :                 endbb();
    1023              :             }
    1024            0 :             return;
    1025              :         }
    1026            0 :         aamask::enable();
    1027            0 :         if(flags&Model_CullQuery)
    1028              :         {
    1029            0 :             d->query = occlusionengine.newquery(d);
    1030            0 :             if(d->query)
    1031              :             {
    1032            0 :                 d->query->startquery();
    1033              :             }
    1034              :         }
    1035            0 :         m->startrender();
    1036            0 :         aamask::set(true);
    1037            0 :         if(flags&Model_FullBright)
    1038              :         {
    1039            0 :             anim |= Anim_FullBright;
    1040              :         }
    1041            0 :         m->render(anim, basetime, basetime2, o, yaw, pitch, roll, d, a, size, color);
    1042            0 :         m->endrender();
    1043            0 :         if(flags&Model_CullQuery && d->query)
    1044              :         {
    1045            0 :             occlusionengine.endquery();
    1046              :         }
    1047            0 :         aamask::disable();
    1048            0 :         return;
    1049              :     }
    1050              : 
    1051            0 :     batchedmodels.emplace_back();
    1052            0 :     batchedmodel &b = batchedmodels.back();
    1053            0 :     b.pos = o;
    1054            0 :     b.center = center;
    1055            0 :     b.radius = radius;
    1056            0 :     b.anim = anim;
    1057            0 :     b.orient = {yaw, pitch, roll};
    1058            0 :     b.basetime = basetime;
    1059            0 :     b.basetime2 = basetime2;
    1060            0 :     b.sizescale = size;
    1061            0 :     b.colorscale = color;
    1062            0 :     b.flags = flags;
    1063            0 :     b.visible = 0;
    1064            0 :     b.d = d;
    1065            0 :     b.attached = a ? modelattached.size() : -1;
    1066            0 :     if(a)
    1067              :     {
    1068            0 :         for(int i = 0;; i++)
    1069              :         {
    1070            0 :             modelattached.push_back(a[i]);
    1071            0 :             if(!a[i].tag)
    1072              :             {
    1073            0 :                 break;
    1074              :             }
    1075              :         }
    1076              :     }
    1077            0 :     addbatchedmodel(m, b, batchedmodels.size()-1);
    1078              : }
    1079              : 
    1080            0 : int intersectmodel(std::string_view mdl, int anim, const vec &pos, float yaw, float pitch, float roll, const vec &o, const vec &ray, dynent *d, modelattach *a, int basetime, int basetime2, float size)
    1081              : {
    1082            0 :     const model *m = loadmodel(mdl);
    1083            0 :     if(!m)
    1084              :     {
    1085            0 :         return -1;
    1086              :     }
    1087            0 :     if(d && d->ragdoll && (!(anim & Anim_Ragdoll) || d->ragdoll->millis < basetime))
    1088              :     {
    1089            0 :         if(d->ragdoll)
    1090              :         {
    1091            0 :             delete d->ragdoll;
    1092            0 :             d->ragdoll = nullptr;
    1093              :         }
    1094              :     }
    1095            0 :     if(a)
    1096              :     {
    1097            0 :         for(int i = 0; a[i].tag; i++)
    1098              :         {
    1099            0 :             if(a[i].name)
    1100              :             {
    1101            0 :                 a[i].m = loadmodel(a[i].name);
    1102              :             }
    1103              :         }
    1104              :     }
    1105            0 :     return m->intersect(anim, basetime, basetime2, pos, yaw, pitch, roll, d, a, size, o, ray);
    1106              : }
    1107              : 
    1108            0 : void abovemodel(vec &o, const char *mdl)
    1109              : {
    1110            0 :     const model *m = loadmodel(mdl);
    1111            0 :     if(!m)
    1112              :     {
    1113            0 :         return;
    1114              :     }
    1115            0 :     vec center, radius;
    1116            0 :     m->calcbb(center, radius);
    1117            0 :     o.z += center.z + radius.z;
    1118              : }
    1119              : 
    1120            3 : std::vector<size_t> findanims(std::string_view pattern)
    1121              : {
    1122            3 :     std::vector<size_t> anims;
    1123            5 :     for(size_t i = 0; i < animnames.size(); ++i)
    1124              :     {
    1125            2 :         if(!animnames.at(i).compare(pattern))
    1126              :         {
    1127            2 :             anims.push_back(i);
    1128              :         }
    1129              :     }
    1130            3 :     return anims;
    1131            0 : }
    1132              : 
    1133            1 : void findanimscmd(const char *name)
    1134              : {
    1135            1 :     std::vector<size_t> anims = findanims(name);
    1136            1 :     std::vector<char> buf;
    1137              :     string num;
    1138            1 :     for(size_t i = 0; i < anims.size(); i++)
    1139              :     {
    1140            0 :         formatstring(num, "%lu", anims[i]);
    1141            0 :         if(i > 0)
    1142              :         {
    1143            0 :             buf.push_back(' ');
    1144              :         }
    1145            0 :         for(size_t i = 0; i < std::strlen(num); ++i)
    1146              :         {
    1147            0 :             buf.push_back(num[i]);
    1148              :         }
    1149              :     }
    1150            1 :     buf.push_back('\0');
    1151            1 :     result(buf.data());
    1152            1 : }
    1153              : 
    1154            0 : void loadskin(const std::string &dir, const std::string &altdir, Texture *&skin, Texture *&masks) // model skin sharing
    1155              : {
    1156              :     //goes and attempts a textureload for png, jpg four times using the cascading if statements, first for default then for alt directory
    1157            0 :     static auto tryload = [] (Texture *tex, std::string name, const std::string &mdir, const std::string &maltdir) -> bool
    1158              :     {
    1159            0 :         if((tex = textureload(makerelpath(mdir.c_str(), name.append(".jpg").c_str(), nullptr, nullptr), 0, true, false))==notexture)
    1160              :         {
    1161            0 :             if((tex = textureload(makerelpath(mdir.c_str(), name.append(".png").c_str(), nullptr, nullptr), 0, true, false))==notexture)
    1162              :             {
    1163            0 :                 if((tex = textureload(makerelpath(maltdir.c_str(), name.append(".jpg").c_str(), nullptr, nullptr), 0, true, false))==notexture)
    1164              :                 {
    1165            0 :                     if((tex = textureload(makerelpath(maltdir.c_str(), name.append(".png").c_str(), nullptr, nullptr), 0, true, false))==notexture)
    1166              :                     {
    1167            0 :                         return true;
    1168              :                     }
    1169              :                 }
    1170              :             }
    1171              :         }
    1172            0 :         return false;
    1173              :     };
    1174              : 
    1175            0 :     std::string mdir,
    1176            0 :                 maltdir;
    1177            0 :     mdir.append(modelpath).append(dir);
    1178            0 :     mdir.append(modelpath).append(altdir);
    1179            0 :     masks = notexture;
    1180            0 :     if(tryload(skin, "skin", mdir, maltdir))
    1181              :     {
    1182            0 :         return;
    1183              :     }
    1184            0 :     if(tryload(masks, "masks", mdir, maltdir))
    1185              :     {
    1186            0 :         return;
    1187              :     }
    1188            0 : }
    1189              : 
    1190            0 : void setbbfrommodel(dynent *d, std::string_view mdl)
    1191              : {
    1192            0 :     model *m = loadmodel(mdl);
    1193            0 :     if(!m)
    1194              :     {
    1195            0 :         return;
    1196              :     }
    1197            0 :     vec center, radius;
    1198            0 :     m->collisionbox(center, radius);
    1199            0 :     if(m->collide != Collide_Ellipse)
    1200              :     {
    1201            0 :         d->collidetype = Collide_OrientedBoundingBox;
    1202              :     }
    1203            0 :     d->xradius   = radius.x + std::fabs(center.x);
    1204            0 :     d->yradius   = radius.y + std::fabs(center.y);
    1205            0 :     d->radius    = d->collidetype==Collide_OrientedBoundingBox ? sqrtf(d->xradius*d->xradius + d->yradius*d->yradius) : std::max(d->xradius, d->yradius);
    1206            0 :     d->eyeheight = (center.z-radius.z) + radius.z*2*m->eyeheight;
    1207            0 :     d->aboveeye  = radius.z*2*(1.0f-m->eyeheight);
    1208            0 :     if (d->aboveeye + d->eyeheight <= 0.5f)
    1209              :     {
    1210            0 :         float zrad = (0.5f - (d->aboveeye + d->eyeheight)) / 2;
    1211            0 :         d->aboveeye += zrad;
    1212            0 :         d->eyeheight += zrad;
    1213              :     }
    1214              : }
    1215              : 
    1216            1 : void initrendermodelcmds()
    1217              : {
    1218            1 :     addcommand("mapmodelreset", reinterpret_cast<identfun>(mapmodel::reset), "i", Id_Command);
    1219            1 :     addcommand("mapmodel", reinterpret_cast<identfun>(mapmodel::open), "s", Id_Command);
    1220            1 :     addcommand("mapmodelname", reinterpret_cast<identfun>(mapmodel::namecmd), "ii", Id_Command);
    1221            1 :     addcommand("mapmodelloaded", reinterpret_cast<identfun>(mapmodel::loaded), "i", Id_Command);
    1222            1 :     addcommand("nummapmodels", reinterpret_cast<identfun>(mapmodel::num), "", Id_Command);
    1223            1 :     addcommand("clearmodel", reinterpret_cast<identfun>(clearmodel), "s", Id_Command);
    1224            1 :     addcommand("findanims", reinterpret_cast<identfun>(findanimscmd), "s", Id_Command);
    1225            1 : }
        

Generated by: LCOV version 2.0-1