LCOV - code coverage report
Current view: top level - engine/render - rendermodel.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 48 637 7.5 %
Date: 2024-11-22 05:07:59 Functions: 9 47 19.1 %

          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(const model *m, 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 model *m, 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(b.m, 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(b.m, 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(b.m, 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(m, 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(m, 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(m, 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, float &dist, int mode, 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, dist);
    1106             : }
    1107             : 
    1108           0 : void abovemodel(vec &o, const char *mdl)
    1109             : {
    1110           0 :     model *m = loadmodel(mdl);
    1111           0 :     if(!m)
    1112             :     {
    1113           0 :         return;
    1114             :     }
    1115           0 :     o.z += m->above();
    1116             : }
    1117             : 
    1118           3 : std::vector<size_t> findanims(std::string_view pattern)
    1119             : {
    1120           3 :     std::vector<size_t> anims;
    1121           5 :     for(size_t i = 0; i < animnames.size(); ++i)
    1122             :     {
    1123           2 :         if(!animnames.at(i).compare(pattern))
    1124             :         {
    1125           2 :             anims.push_back(i);
    1126             :         }
    1127             :     }
    1128           3 :     return anims;
    1129           0 : }
    1130             : 
    1131           1 : void findanimscmd(const char *name)
    1132             : {
    1133           1 :     std::vector<size_t> anims = findanims(name);
    1134           1 :     std::vector<char> buf;
    1135             :     string num;
    1136           1 :     for(size_t i = 0; i < anims.size(); i++)
    1137             :     {
    1138           0 :         formatstring(num, "%lu", anims[i]);
    1139           0 :         if(i > 0)
    1140             :         {
    1141           0 :             buf.push_back(' ');
    1142             :         }
    1143           0 :         for(uint i = 0; i < std::strlen(num); ++i)
    1144             :         {
    1145           0 :             buf.push_back(num[i]);
    1146             :         }
    1147             :     }
    1148           1 :     buf.push_back('\0');
    1149           1 :     result(buf.data());
    1150           1 : }
    1151             : 
    1152           0 : void loadskin(const std::string &dir, const std::string &altdir, Texture *&skin, Texture *&masks) // model skin sharing
    1153             : {
    1154             :     //goes and attempts a textureload for png, jpg four times using the cascading if statements, first for default then for alt directory
    1155           0 :     static auto tryload = [] (Texture *tex, std::string name, const std::string &mdir, const std::string &maltdir) -> bool
    1156             :     {
    1157           0 :         if((tex = textureload(makerelpath(mdir.c_str(), name.append(".jpg").c_str(), nullptr, nullptr), 0, true, false))==notexture)
    1158             :         {
    1159           0 :             if((tex = textureload(makerelpath(mdir.c_str(), name.append(".png").c_str(), nullptr, nullptr), 0, true, false))==notexture)
    1160             :             {
    1161           0 :                 if((tex = textureload(makerelpath(maltdir.c_str(), name.append(".jpg").c_str(), nullptr, nullptr), 0, true, false))==notexture)
    1162             :                 {
    1163           0 :                     if((tex = textureload(makerelpath(maltdir.c_str(), name.append(".png").c_str(), nullptr, nullptr), 0, true, false))==notexture)
    1164             :                     {
    1165           0 :                         return true;
    1166             :                     }
    1167             :                 }
    1168             :             }
    1169             :         }
    1170           0 :         return false;
    1171             :     };
    1172             : 
    1173           0 :     std::string mdir,
    1174           0 :                 maltdir;
    1175           0 :     mdir.append(modelpath).append(dir);
    1176           0 :     mdir.append(modelpath).append(altdir);
    1177           0 :     masks = notexture;
    1178           0 :     if(tryload(skin, "skin", mdir, maltdir))
    1179             :     {
    1180           0 :         return;
    1181             :     }
    1182           0 :     if(tryload(masks, "masks", mdir, maltdir))
    1183             :     {
    1184           0 :         return;
    1185             :     }
    1186           0 : }
    1187             : 
    1188           0 : void setbbfrommodel(dynent *d, std::string_view mdl)
    1189             : {
    1190           0 :     model *m = loadmodel(mdl);
    1191           0 :     if(!m)
    1192             :     {
    1193           0 :         return;
    1194             :     }
    1195           0 :     vec center, radius;
    1196           0 :     m->collisionbox(center, radius);
    1197           0 :     if(m->collide != Collide_Ellipse)
    1198             :     {
    1199           0 :         d->collidetype = Collide_OrientedBoundingBox;
    1200             :     }
    1201           0 :     d->xradius   = radius.x + std::fabs(center.x);
    1202           0 :     d->yradius   = radius.y + std::fabs(center.y);
    1203           0 :     d->radius    = d->collidetype==Collide_OrientedBoundingBox ? sqrtf(d->xradius*d->xradius + d->yradius*d->yradius) : std::max(d->xradius, d->yradius);
    1204           0 :     d->eyeheight = (center.z-radius.z) + radius.z*2*m->eyeheight;
    1205           0 :     d->aboveeye  = radius.z*2*(1.0f-m->eyeheight);
    1206           0 :     if (d->aboveeye + d->eyeheight <= 0.5f)
    1207             :     {
    1208           0 :         float zrad = (0.5f - (d->aboveeye + d->eyeheight)) / 2;
    1209           0 :         d->aboveeye += zrad;
    1210           0 :         d->eyeheight += zrad;
    1211             :     }
    1212             : }
    1213             : 
    1214           1 : void initrendermodelcmds()
    1215             : {
    1216           1 :     addcommand("mapmodelreset", reinterpret_cast<identfun>(mapmodel::reset), "i", Id_Command);
    1217           1 :     addcommand("mapmodel", reinterpret_cast<identfun>(mapmodel::open), "s", Id_Command);
    1218           1 :     addcommand("mapmodelname", reinterpret_cast<identfun>(mapmodel::namecmd), "ii", Id_Command);
    1219           1 :     addcommand("mapmodelloaded", reinterpret_cast<identfun>(mapmodel::loaded), "i", Id_Command);
    1220           1 :     addcommand("nummapmodels", reinterpret_cast<identfun>(mapmodel::num), "", Id_Command);
    1221           1 :     addcommand("clearmodel", reinterpret_cast<identfun>(clearmodel), "s", Id_Command);
    1222           1 :     addcommand("findanims", reinterpret_cast<identfun>(findanimscmd), "s", Id_Command);
    1223           1 : }

Generated by: LCOV version 1.14