LCOV - code coverage report
Current view: top level - engine/world - physics.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 0 731 0.0 %
Date: 2024-11-22 05:07:59 Functions: 0 42 0.0 %

          Line data    Source code
       1             : /* physics.cpp: no physics books were hurt nor consulted in the construction of this code.
       2             :  * All physics computations and constants were invented on the fly and simply tweaked until
       3             :  * they "felt right", and have no basis in reality. Collision detection is simplistic but
       4             :  * very robust (uses discrete steps at fixed fps).
       5             :  */
       6             : #include "../libprimis-headers/cube.h"
       7             : #include "../../shared/geomexts.h"
       8             : 
       9             : #include <memory>
      10             : #include <optional>
      11             : 
      12             : #include "bih.h"
      13             : #include "entities.h"
      14             : #include "mpr.h"
      15             : #include "octaworld.h"
      16             : #include "physics.h"
      17             : #include "raycube.h"
      18             : #include "world.h"
      19             : 
      20             : #include "interface/console.h"
      21             : 
      22             : #include "model/model.h"
      23             : 
      24             : #include "render/rendermodel.h"
      25             : 
      26             : int numdynents; //updated by engine, visible through iengine.h
      27             : std::vector<dynent *> dynents;
      28             : 
      29             : static constexpr int maxclipoffset = 4;
      30             : static constexpr int maxclipplanes = 1024;
      31             : static std::array<clipplanes, maxclipplanes> clipcache;
      32             : static int clipcacheversion = -maxclipoffset;
      33             : 
      34           0 : clipplanes &cubeworld::getclipbounds(const cube &c, const ivec &o, int size, int offset)
      35             : {
      36             :     //index is naive hash of difference between addresses (not necessarily contiguous) modulo cache size
      37           0 :     clipplanes &p = clipcache[static_cast<int>(&c - &((*worldroot)[0])) & (maxclipplanes-1)];
      38           0 :     if(p.owner != &c || p.version != clipcacheversion+offset)
      39             :     {
      40           0 :         p.owner = &c;
      41           0 :         p.version = clipcacheversion+offset;
      42           0 :         genclipbounds(c, o, size, p);
      43             :     }
      44           0 :     return p;
      45             : }
      46             : 
      47           0 : static clipplanes &getclipbounds(const cube &c, const ivec &o, int size, const physent &d)
      48             : {
      49           0 :     int offset = !(c.visible&0x80) || d.type==physent::PhysEnt_Player ? 0 : 1;
      50           0 :     return rootworld.getclipbounds(c, o, size, offset);
      51             : }
      52             : 
      53           0 : static int forceclipplanes(const cube &c, const ivec &o, int size, clipplanes &p)
      54             : {
      55           0 :     if(p.visible&0x80)
      56             :     {
      57           0 :         bool collide = true,
      58           0 :              noclip = false;
      59           0 :         if(p.version&1)
      60             :         {
      61           0 :             collide = false;
      62           0 :             noclip  = true;
      63             :         }
      64           0 :         genclipplanes(c, o, size, p, collide, noclip);
      65             :     }
      66           0 :     return p.visible;
      67             : }
      68             : 
      69           0 : void cubeworld::resetclipplanes()
      70             : {
      71           0 :     clipcacheversion += maxclipoffset;
      72           0 :     if(!clipcacheversion)
      73             :     {
      74           0 :         for(clipplanes &i : clipcache)
      75             :         {
      76           0 :             i.clear();
      77             :         }
      78           0 :         clipcacheversion = maxclipoffset;
      79             :     }
      80           0 : }
      81             : 
      82             : /////////////////////////  entity collision  ///////////////////////////////////////////////
      83             : 
      84             : // info about collisions
      85             : int collideinside; // whether an internal collision happened
      86             : const physent *collideplayer; // whether the collection hit a player
      87             : vec collidewall; // just the normal vectors.
      88             : 
      89           0 : bool ellipseboxcollide(const physent *d, const vec &dir, const vec &origin, const vec &center, float yaw, float xr, float yr, float hi, float lo)
      90             : {
      91           0 :     float below = (origin.z+center.z-lo) - (d->o.z+d->aboveeye),
      92           0 :           above = (d->o.z-d->eyeheight) - (origin.z+center.z+hi);
      93           0 :     if(below>=0 || above>=0)
      94             :     {
      95           0 :         return false;
      96             :     }
      97           0 :     vec yo(d->o);
      98           0 :     yo.sub(origin);
      99           0 :     yo.rotate_around_z(-yaw/RAD);
     100           0 :     yo.sub(center);
     101             : 
     102           0 :     float dx = std::clamp(yo.x, -xr, xr) - yo.x,
     103           0 :           dy = std::clamp(yo.y, -yr, yr) - yo.y,
     104           0 :           dist = sqrtf(dx*dx + dy*dy) - d->radius;
     105           0 :     if(dist < 0)
     106             :     {
     107           0 :         int sx = yo.x <= -xr ? -1 : (yo.x >= xr ? 1 : 0),
     108           0 :             sy = yo.y <= -yr ? -1 : (yo.y >= yr ? 1 : 0);
     109           0 :         if(dist > (yo.z < 0 ? below : above) && (sx || sy))
     110             :         {
     111           0 :             vec ydir(dir);
     112           0 :             ydir.rotate_around_z(-yaw/RAD);
     113           0 :             if(sx*yo.x - xr > sy*yo.y - yr)
     114             :             {
     115           0 :                 if(dir.iszero() || sx*ydir.x < -1e-6f)
     116             :                 {
     117           0 :                     collidewall = vec(sx, 0, 0);
     118           0 :                     collidewall.rotate_around_z(yaw/RAD);
     119           0 :                     return true;
     120             :                 }
     121             :             }
     122           0 :             else if(dir.iszero() || sy*ydir.y < -1e-6f)
     123             :             {
     124           0 :                 collidewall = vec(0, sy, 0);
     125           0 :                 collidewall.rotate_around_z(yaw/RAD);
     126           0 :                 return true;
     127             :             }
     128             :         }
     129           0 :         if(yo.z < 0)
     130             :         {
     131           0 :             if(dir.iszero() || (dir.z > 0 && (d->type!=physent::PhysEnt_Player || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f)))
     132             :             {
     133           0 :                 collidewall = vec(0, 0, -1);
     134           0 :                 return true;
     135             :             }
     136             :         }
     137           0 :         else if(dir.iszero() || (dir.z < 0 && (d->type!=physent::PhysEnt_Player || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f)))
     138             :         {
     139           0 :             collidewall = vec(0, 0, 1);
     140           0 :             return true;
     141             :         }
     142           0 :         collideinside++;
     143             :     }
     144           0 :     return false;
     145             : }
     146             : 
     147           0 : bool ellipsecollide(const physent *d, const vec &dir, const vec &o, const vec &center, float yaw, float xr, float yr, float hi, float lo)
     148             : {
     149           0 :     float below = (o.z+center.z-lo) - (d->o.z+d->aboveeye),
     150           0 :           above = (d->o.z-d->eyeheight) - (o.z+center.z+hi);
     151           0 :     if(below>=0 || above>=0)
     152             :     {
     153           0 :         return false;
     154             :     }
     155           0 :     vec yo(center);
     156           0 :     yo.rotate_around_z(yaw/RAD);
     157           0 :     yo.add(o);
     158           0 :     float x = yo.x - d->o.x,
     159           0 :           y = yo.y - d->o.y,
     160           0 :           angle = atan2f(y, x),
     161           0 :           dangle = angle-d->yaw/RAD,
     162           0 :           eangle = angle-yaw/RAD,
     163           0 :           dx = d->xradius*std::cos(dangle),
     164           0 :           dy = d->yradius*std::sin(dangle),
     165           0 :           ex = xr*std::cos(eangle),
     166           0 :           ey = yr*std::sin(eangle),
     167           0 :           dist = sqrtf(x*x + y*y) - sqrtf(dx*dx + dy*dy) - sqrtf(ex*ex + ey*ey);
     168           0 :     if(dist < 0)
     169             :     {
     170           0 :         if(dist > (d->o.z < yo.z ? below : above) && (dir.iszero() || x*dir.x + y*dir.y > 0))
     171             :         {
     172           0 :             collidewall = vec(-x, -y, 0).rescale(1);
     173           0 :             return true;
     174             :         }
     175           0 :         if(d->o.z < yo.z)
     176             :         {
     177           0 :             if(dir.iszero() || (dir.z > 0 && (d->type!=physent::PhysEnt_Player || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f)))
     178             :             {
     179           0 :                 collidewall = vec(0, 0, -1);
     180           0 :                 return true;
     181             :             }
     182             :         }
     183           0 :         else if(dir.iszero() || (dir.z < 0 && (d->type!=physent::PhysEnt_Player || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f)))
     184             :         {
     185           0 :             collidewall = vec(0, 0, 1);
     186           0 :             return true;
     187             :         }
     188           0 :         collideinside++;
     189             :     }
     190           0 :     return false;
     191             : }
     192             : 
     193             : static constexpr int dynentcachesize = 1024;
     194             : 
     195             : static uint dynentframe = 0;
     196             : 
     197             : static struct dynentcacheentry
     198             : {
     199             :     int x, y;
     200             :     uint frame;
     201             :     std::vector<const physent *> dynents;
     202             : } dynentcache[dynentcachesize];
     203             : 
     204             : //resets the dynentcache[] array entries
     205             : //used in iengine
     206           0 : void cleardynentcache()
     207             : {
     208           0 :     dynentframe++;
     209           0 :     if(!dynentframe || dynentframe == 1)
     210             :     {
     211           0 :         for(int i = 0; i < dynentcachesize; ++i)
     212             :         {
     213           0 :             dynentcache[i].frame = 0;
     214             :         }
     215             :     }
     216           0 :     if(!dynentframe)
     217             :     {
     218           0 :         dynentframe = 1;
     219             :     }
     220           0 : }
     221             : 
     222             : //returns the dynent at location i in the dynents vector
     223             : //used in iengine
     224           0 : dynent *iterdynents(int i)
     225             : {
     226           0 :     if(i < static_cast<int>(dynents.size()))
     227             :     {
     228           0 :         return dynents[i];
     229             :     }
     230           0 :     return nullptr;
     231             : }
     232             : 
     233           0 : VARF(dynentsize, 4, 7, 12, cleardynentcache());
     234             : 
     235           0 : static int dynenthash(int x, int y)
     236             : {
     237           0 :     return (((((x)^(y))<<5) + (((x)^(y))>>5)) & (dynentcachesize - 1));
     238             : }
     239             : 
     240           0 : static const std::vector<const physent *> &checkdynentcache(int x, int y)
     241             : {
     242           0 :     dynentcacheentry &dec = dynentcache[dynenthash(x, y)];
     243           0 :     if(dec.x == x && dec.y == y && dec.frame == dynentframe)
     244             :     {
     245           0 :         return dec.dynents;
     246             :     }
     247           0 :     dec.x = x;
     248           0 :     dec.y = y;
     249           0 :     dec.frame = dynentframe;
     250           0 :     dec.dynents.clear();
     251           0 :     int numdyns = numdynents,
     252           0 :         dsize = 1<<dynentsize,
     253           0 :         dx = x<<dynentsize,
     254           0 :         dy = y<<dynentsize;
     255           0 :     for(int i = 0; i < numdyns; ++i)
     256             :     {
     257           0 :         dynent *d = iterdynents(i);
     258           0 :         if(d->ragdoll ||
     259           0 :            d->o.x+d->radius <= dx || d->o.x-d->radius >= dx+dsize ||
     260           0 :            d->o.y+d->radius <= dy || d->o.y-d->radius >= dy+dsize)
     261             :         {
     262           0 :             continue;
     263             :         }
     264           0 :         dec.dynents.push_back(d);
     265             :     }
     266           0 :     return dec.dynents;
     267             : }
     268             : 
     269             : //============================================================== LOOPDYNENTCACHE
     270             : #define LOOPDYNENTCACHE(curx, cury, o, radius) \
     271             :     for(int curx = std::max(static_cast<int>(o.x-radius), 0)>>dynentsize, endx = std::min(static_cast<int>(o.x+radius), rootworld.mapsize()-1)>>dynentsize; curx <= endx; curx++) \
     272             :         for(int cury = std::max(static_cast<int>(o.y-radius), 0)>>dynentsize, endy = std::min(static_cast<int>(o.y+radius), rootworld.mapsize()-1)>>dynentsize; cury <= endy; cury++)
     273             : 
     274             : //used in iengine
     275           0 : void updatedynentcache(physent *d)
     276             : {
     277           0 :     LOOPDYNENTCACHE(x, y, d->o, d->radius)
     278             :     {
     279           0 :         dynentcacheentry &dec = dynentcache[dynenthash(x, y)];
     280           0 :         if(dec.x != x || dec.y != y || dec.frame != dynentframe || (std::find(dec.dynents.begin(), dec.dynents.end(), d) != dec.dynents.end()))
     281             :         {
     282           0 :             continue;
     283             :         }
     284           0 :         dec.dynents.push_back(d);
     285             :     }
     286           0 : }
     287             : 
     288             : template<class O>
     289           0 : static bool plcollide(const physent *d, const vec &dir, const physent *o)
     290             : {
     291           0 :     mpr::EntOBB entvol(d);
     292           0 :     O obvol(o);
     293           0 :     vec cp;
     294           0 :     if(mpr::collide(entvol, obvol, nullptr, nullptr, &cp))
     295             :     {
     296           0 :         vec wn = cp.sub(obvol.center());
     297           0 :         collidewall = obvol.contactface(wn, dir.iszero() ? wn.neg() : dir);
     298           0 :         if(!collidewall.iszero())
     299             :         {
     300           0 :             return true;
     301             :         }
     302           0 :         collideinside++;
     303             :     }
     304           0 :     return false;
     305             : }
     306             : 
     307           0 : static bool plcollide(const physent *d, const vec &dir, const physent *o)
     308             : {
     309           0 :     switch(d->collidetype)
     310             :     {
     311           0 :         case Collide_Ellipse:
     312             :         {
     313           0 :             if(o->collidetype == Collide_Ellipse)
     314             :             {
     315           0 :                 return ellipsecollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight);
     316             :             }
     317             :             else
     318             :             {
     319           0 :                 return ellipseboxcollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight);
     320             :             }
     321             :         }
     322           0 :         case Collide_OrientedBoundingBox:
     323             :         {
     324           0 :             if(o->collidetype == Collide_Ellipse)
     325             :             {
     326           0 :                 return plcollide<mpr::EntCylinder>(d, dir, o);
     327             :             }
     328             :             else
     329             :             {
     330           0 :                 return plcollide<mpr::EntOBB>(d, dir, o);
     331             :             }
     332             :         }
     333           0 :         default:
     334             :         {
     335           0 :             return false;
     336             :         }
     337             :     }
     338             : }
     339             : 
     340           0 : bool plcollide(const physent *d, const vec &dir, bool insideplayercol)    // collide with player
     341             : {
     342           0 :     if(d->type==physent::PhysEnt_Camera)
     343             :     {
     344           0 :         return false;
     345             :     }
     346           0 :     int lastinside = collideinside;
     347           0 :     const physent *insideplayer = nullptr;
     348           0 :     LOOPDYNENTCACHE(x, y, d->o, d->radius)
     349             :     {
     350           0 :         const std::vector<const physent *> &dynents = checkdynentcache(x, y);
     351           0 :         for(const physent* const& o: dynents)
     352             :         {
     353           0 :             if(o==d || d->o.reject(o->o, d->radius+o->radius))
     354             :             {
     355           0 :                 continue;
     356             :             }
     357           0 :             if(plcollide(d, dir, o))
     358             :             {
     359           0 :                 collideplayer = o;
     360           0 :                 return true;
     361             :             }
     362           0 :             if(collideinside > lastinside)
     363             :             {
     364           0 :                 lastinside = collideinside;
     365           0 :                 insideplayer = o;
     366             :             }
     367             :         }
     368             :     }
     369           0 :     if(insideplayer && insideplayercol)
     370             :     {
     371           0 :         collideplayer = insideplayer;
     372           0 :         return true;
     373             :     }
     374           0 :     return false;
     375             : }
     376             : 
     377             : #undef LOOPDYNENTCACHE
     378             : //==============================================================================
     379             : 
     380             : template<class M>
     381           0 : static bool mmcollide(const physent *d, const vec &dir, const extentity &e, const vec &center, const vec &radius, int yaw, int pitch, int roll)
     382             : {
     383           0 :     mpr::EntOBB entvol(d);
     384           0 :     M mdlvol(e.o, center, radius, yaw, pitch, roll);
     385           0 :     vec cp;
     386           0 :     if(mpr::collide(entvol, mdlvol, nullptr, nullptr, &cp))
     387             :     {
     388           0 :         vec wn = cp.sub(mdlvol.center());
     389           0 :         collidewall = mdlvol.contactface(wn, dir.iszero() ? wn.neg() : dir);
     390           0 :         if(!collidewall.iszero())
     391             :         {
     392           0 :             return true;
     393             :         }
     394           0 :         collideinside++;
     395             :     }
     396           0 :     return false;
     397             : }
     398             : 
     399           0 : static bool fuzzycollidebox(const physent *d, const vec &dir, float cutoff, const vec &o, const vec &center, const vec &radius, int yaw, int pitch, int roll)
     400             : {
     401           0 :     mpr::ModelOBB mdlvol(o, center, radius, yaw, pitch, roll);
     402           0 :     vec bbradius = mdlvol.orient.abstransposedtransform(radius);
     403           0 :     if(std::fabs(d->o.x - mdlvol.o.x) > bbradius.x + d->radius || std::fabs(d->o.y - mdlvol.o.y) > bbradius.y + d->radius ||
     404           0 :        d->o.z + d->aboveeye < mdlvol.o.z - bbradius.z || d->o.z - d->eyeheight > mdlvol.o.z + bbradius.z)
     405             :     {
     406           0 :         return false;
     407             :     }
     408           0 :     mpr::EntCapsule entvol(d);
     409           0 :     collidewall = vec(0, 0, 0);
     410           0 :     float bestdist = -1e10f;
     411           0 :     for(int i = 0; i < 6; ++i)
     412             :     {
     413           0 :         vec w;
     414             :         float dist;
     415           0 :         switch(i)
     416             :         {
     417           0 :             default:
     418             :             case 0:
     419             :             {
     420           0 :                 w = mdlvol.orient.rowx().neg();
     421           0 :                 dist = -radius.x;
     422           0 :                 break;
     423             :             }
     424           0 :             case 1:
     425             :             {
     426           0 :                 w = mdlvol.orient.rowx();
     427           0 :                 dist = -radius.x;
     428           0 :                 break;
     429             :             }
     430           0 :             case 2:
     431             :             {
     432           0 :                 w = mdlvol.orient.rowy().neg();
     433           0 :                 dist = -radius.y;
     434           0 :                 break;
     435             :             }
     436           0 :             case 3:
     437             :             {
     438           0 :                 w = mdlvol.orient.rowy();
     439           0 :                 dist = -radius.y;
     440           0 :                 break;
     441             :             }
     442           0 :             case 4:
     443             :             {
     444           0 :                 w = mdlvol.orient.rowz().neg();
     445           0 :                 dist = -radius.z;
     446           0 :                 break;
     447             :             }
     448           0 :             case 5:
     449             :             {
     450           0 :                 w = mdlvol.orient.rowz();
     451           0 :                 dist = -radius.z;
     452           0 :                 break;
     453             :             }
     454             :         }
     455           0 :         vec pw = entvol.supportpoint(w.neg());
     456           0 :         dist += w.dot(pw.sub(mdlvol.o));
     457           0 :         if(dist >= 0)
     458             :         {
     459           0 :             return false;
     460             :         }
     461           0 :         if(dist <= bestdist)
     462             :         {
     463           0 :             continue;
     464             :         }
     465           0 :         collidewall = vec(0, 0, 0);
     466           0 :         bestdist = dist;
     467           0 :         if(!dir.iszero())
     468             :         {
     469           0 :             if(w.dot(dir) >= -cutoff*dir.magnitude())
     470             :             {
     471           0 :                 continue;
     472             :             }
     473             :             //nasty ternary in the indented part
     474           0 :             if(d->type==physent::PhysEnt_Player &&
     475           0 :                     dist < (dir.z*w.z < 0 ?
     476           0 :                         d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
     477           0 :                         (dir.x*w.x < 0 || dir.y*w.y < 0 ? -d->radius : 0)))
     478             :             {
     479           0 :                 continue;
     480             :             }
     481             :         }
     482           0 :         collidewall = w;
     483             :     }
     484           0 :     if(collidewall.iszero())
     485             :     {
     486           0 :         collideinside++;
     487           0 :         return false;
     488             :     }
     489           0 :     return true;
     490             : }
     491             : 
     492             : template<class E>
     493           0 : static bool fuzzycollideellipse(const physent *d, const vec &dir, float cutoff, const vec &o, const vec &center, const vec &radius, int yaw, int pitch, int roll)
     494             : {
     495           0 :     mpr::ModelEllipse mdlvol(o, center, radius, yaw, pitch, roll);
     496           0 :     vec bbradius = mdlvol.orient.abstransposedtransform(radius);
     497             : 
     498           0 :     if(std::fabs(d->o.x - mdlvol.o.x) > bbradius.x + d->radius ||
     499           0 :        std::fabs(d->o.y - mdlvol.o.y) > bbradius.y + d->radius ||
     500           0 :        d->o.z + d->aboveeye < mdlvol.o.z - bbradius.z ||
     501           0 :        d->o.z - d->eyeheight > mdlvol.o.z + bbradius.z)
     502             :     {
     503           0 :         return false;
     504             :     }
     505           0 :     E entvol(d);
     506           0 :     collidewall = vec(0, 0, 0);
     507           0 :     float bestdist = -1e10f;
     508           0 :     for(int i = 0; i < 3; ++i)
     509             :     {
     510           0 :         vec w;
     511             :         float dist;
     512           0 :         switch(i)
     513             :         {
     514           0 :             default:
     515             :             case 0:
     516             :             {
     517           0 :                 w = mdlvol.orient.rowz();
     518           0 :                 dist = -radius.z;
     519           0 :                 break;
     520             :             }
     521           0 :             case 1:
     522             :             {
     523           0 :                 w = mdlvol.orient.rowz().neg();
     524           0 :                 dist = -radius.z;
     525           0 :                 break;
     526             :             }
     527           0 :             case 2:
     528             :             {
     529           0 :                 vec2 ln(mdlvol.orient.transform(entvol.center().sub(mdlvol.o)));
     530           0 :                 float r = ln.magnitude();
     531           0 :                 if(r < 1e-6f)
     532             :                 {
     533           0 :                     continue;
     534             :                 }
     535           0 :                 vec2 lw = vec2(ln.x*radius.y, ln.y*radius.x).normalize();
     536           0 :                 w = mdlvol.orient.transposedtransform(lw);
     537           0 :                 dist = -vec2(ln.x*radius.x, ln.y*radius.y).dot(lw)/r;
     538           0 :                 break;
     539             :             }
     540             :         }
     541           0 :         vec pw = entvol.supportpoint(vec(w).neg());
     542           0 :         dist += w.dot(vec(pw).sub(mdlvol.o));
     543           0 :         if(dist >= 0)
     544             :         {
     545           0 :             return false;
     546             :         }
     547           0 :         if(dist <= bestdist)
     548             :         {
     549           0 :             continue;
     550             :         }
     551           0 :         collidewall = vec(0, 0, 0);
     552           0 :         bestdist = dist;
     553           0 :         if(!dir.iszero())
     554             :         {
     555           0 :             if(w.dot(dir) >= -cutoff*dir.magnitude())
     556             :             {
     557           0 :                 continue;
     558             :             }
     559           0 :             if(d->type==physent::PhysEnt_Player &&
     560           0 :                 dist < (dir.z*w.z < 0 ?
     561           0 :                     d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
     562           0 :                     (dir.x*w.x < 0 || dir.y*w.y < 0 ? -d->radius : 0)))
     563             :             {
     564           0 :                 continue;
     565             :             }
     566             :         }
     567           0 :         collidewall = w;
     568             :     }
     569           0 :     if(collidewall.iszero())
     570             :     {
     571           0 :         collideinside++;
     572           0 :         return false;
     573             :     }
     574           0 :     return true;
     575             : }
     576             : 
     577             : //force a collision type:
     578             : // 0: do not force
     579             : // 1: Collide_Ellipse
     580             : // 2: Collide_OrientedBoundingBox
     581             : VAR(testtricol, 0, 0, 2);
     582             : 
     583           0 : static bool mmcollide(const physent *d, const vec &dir, float cutoff, const octaentities &oc) // collide with a mapmodel
     584             : {
     585           0 :     const std::vector<extentity *> &ents = entities::getents();
     586           0 :     for(const int &i : oc.mapmodels)
     587             :     {
     588           0 :         extentity &e = *ents[i];
     589           0 :         if(e.flags&EntFlag_NoCollide || !(static_cast<int>(mapmodel::mapmodels.size()) > e.attr1))
     590             :         {
     591           0 :             continue;
     592             :         }
     593           0 :         mapmodelinfo &mmi = mapmodel::mapmodels[e.attr1];
     594           0 :         model *m = mmi.collide;
     595           0 :         if(!m)
     596             :         {
     597           0 :             if(!mmi.m && !loadmodel("", e.attr1))
     598             :             {
     599           0 :                 continue;
     600             :             }
     601           0 :             if(!mmi.m->collidemodel.empty())
     602             :             {
     603           0 :                 m = loadmodel(mmi.m->collidemodel);
     604             :             }
     605           0 :             if(!m)
     606             :             {
     607           0 :                 m = mmi.m;
     608             :             }
     609           0 :             mmi.collide = m;
     610             :         }
     611           0 :         int mcol = mmi.m->collide;
     612           0 :         if(!mcol)
     613             :         {
     614           0 :             continue;
     615             :         }
     616           0 :         vec center, radius;
     617           0 :         float rejectradius = m->collisionbox(center, radius),
     618           0 :               scale = e.attr5 > 0 ? e.attr5/100.0f : 1;
     619           0 :         center.mul(scale);
     620           0 :         if(d->o.reject(vec(e.o).add(center), d->radius + rejectradius*scale))
     621             :         {
     622           0 :             continue;
     623             :         }
     624           0 :         int yaw   = e.attr2,
     625           0 :             pitch = e.attr3,
     626           0 :             roll  = e.attr4;
     627           0 :         if(mcol == Collide_TRI || testtricol)
     628             :         {
     629           0 :             if(!m->bih && !m->setBIH())
     630             :             {
     631           0 :                 continue;
     632             :             }
     633           0 :             switch(testtricol ? testtricol : d->collidetype)
     634             :             {
     635           0 :                 case Collide_Ellipse:
     636             :                 {
     637           0 :                     if(m->bih->ellipsecollide(d, dir, cutoff, e.o, yaw, pitch, roll, scale))
     638             :                     {
     639           0 :                         return true;
     640             :                     }
     641           0 :                     break;
     642             :                 }
     643           0 :                 case Collide_OrientedBoundingBox:
     644             :                 {
     645           0 :                     if(m->bih->boxcollide(d, dir, cutoff, e.o, yaw, pitch, roll, scale))
     646             :                     {
     647           0 :                         return true;
     648             :                     }
     649           0 :                     break;
     650             :                 }
     651           0 :                 default:
     652             :                 {
     653           0 :                     continue;
     654             :                 }
     655             :             }
     656             :         }
     657             :         else
     658             :         {
     659           0 :             radius.mul(scale);
     660           0 :             switch(d->collidetype)
     661             :             {
     662           0 :                 case Collide_Ellipse:
     663             :                 {
     664           0 :                     if(mcol == Collide_Ellipse)
     665             :                     {
     666           0 :                         if(pitch || roll)
     667             :                         {
     668           0 :                             if(fuzzycollideellipse<mpr::EntCapsule>(d, dir, cutoff, e.o, center, radius, yaw, pitch, roll))
     669             :                             {
     670           0 :                                 return true;
     671             :                             }
     672             :                         }
     673           0 :                         else if(ellipsecollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z))
     674             :                         {
     675           0 :                             return true;
     676             :                         }
     677             :                     }
     678           0 :                     else if(pitch || roll)
     679             :                     {
     680           0 :                         if(fuzzycollidebox(d, dir, cutoff, e.o, center, radius, yaw, pitch, roll))
     681             :                         {
     682           0 :                             return true;
     683             :                         }
     684             :                     }
     685           0 :                     else if(ellipseboxcollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z))
     686             :                     {
     687           0 :                         return true;
     688             :                     }
     689           0 :                     break;
     690             :                 }
     691           0 :                 case Collide_OrientedBoundingBox:
     692             :                 {
     693           0 :                     if(mcol == Collide_Ellipse)
     694             :                     {
     695           0 :                         if(mmcollide<mpr::ModelEllipse>(d, dir, e, center, radius, yaw, pitch, roll))
     696             :                         {
     697           0 :                             return true;
     698             :                         }
     699             :                     }
     700           0 :                     else if(mmcollide<mpr::ModelOBB>(d, dir, e, center, radius, yaw, pitch, roll))
     701             :                     {
     702           0 :                         return true;
     703             :                     }
     704           0 :                     break;
     705             :                 }
     706           0 :                 default:
     707             :                 {
     708           0 :                     continue;
     709             :                 }
     710             :             }
     711             :         }
     712             :     }
     713           0 :     return false;
     714             : }
     715             : 
     716           0 : static bool checkside(const physent &d, int side, const vec &dir, const int visible, const float cutoff, float distval, float dotval, float margin, vec normal, vec &collidewall, float &bestdist)
     717             : {
     718           0 :     if(visible&(1<<side))
     719             :     {
     720           0 :         float dist = distval;
     721           0 :         if(dist > 0)
     722             :         {
     723           0 :             return false;
     724             :         }
     725           0 :         if(dist <= bestdist)
     726             :         {
     727           0 :             return true;
     728             :         }
     729           0 :         if(!dir.iszero())
     730             :         {
     731           0 :             if(dotval >= -cutoff*dir.magnitude())
     732             :             {
     733           0 :                 return true;
     734             :             }
     735           0 :             if(d.type==physent::PhysEnt_Player && dotval < 0 && dist < margin)
     736             :             {
     737           0 :                 return true;
     738             :             }
     739             :         }
     740           0 :         collidewall = normal;
     741           0 :         bestdist = dist;
     742             :     }
     743           0 :     return true;
     744             : }
     745             : 
     746           0 : static bool fuzzycollidesolid(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with solid cube geometry
     747             : {
     748           0 :     int crad = size/2;
     749           0 :     if(std::fabs(d->o.x - co.x - crad) > d->radius + crad || std::fabs(d->o.y - co.y - crad) > d->radius + crad ||
     750           0 :        d->o.z + d->aboveeye < co.z || d->o.z - d->eyeheight > co.z + size)
     751             :     {
     752           0 :         return false;
     753             :     }
     754           0 :     collidewall = vec(0, 0, 0);
     755           0 :     float bestdist = -1e10f;
     756           0 :     int visible = !(c.visible&0x80) || d->type==physent::PhysEnt_Player ? c.visible : 0xFF;
     757             : 
     758             :     //if any of these checks are false (NAND of all of these checks)
     759           0 :     if(!( checkside(*d, Orient_Left, dir, visible, cutoff, co.x - (d->o.x + d->radius), -dir.x, -d->radius, vec(-1, 0, 0), collidewall, bestdist)
     760           0 :        && checkside(*d, Orient_Right, dir, visible, cutoff, d->o.x - d->radius - (co.x + size), dir.x, -d->radius, vec(1, 0, 0), collidewall, bestdist)
     761           0 :        && checkside(*d, Orient_Back, dir, visible, cutoff, co.y - (d->o.y + d->radius), -dir.y, -d->radius, vec(0, -1, 0), collidewall, bestdist)
     762           0 :        && checkside(*d, Orient_Front, dir, visible, cutoff, d->o.y - d->radius - (co.y + size), dir.y, -d->radius, vec(0, 1, 0), collidewall, bestdist)
     763           0 :        && checkside(*d, Orient_Bottom, dir, visible, cutoff, co.z - (d->o.z + d->aboveeye), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1), collidewall, bestdist)
     764           0 :        && checkside(*d, Orient_Top, dir, visible, cutoff, d->o.z - d->eyeheight - (co.z + size), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1), collidewall, bestdist))
     765             :        )
     766             :     {
     767           0 :         return false;
     768             :     }
     769           0 :     if(collidewall.iszero())
     770             :     {
     771           0 :         collideinside++;
     772           0 :         return false;
     773             :     }
     774           0 :     return true;
     775             : }
     776             : 
     777             : template<class E>
     778           0 : static bool clampcollide(const clipplanes &p, const E &entvol, const plane &w, const vec &pw)
     779             : {
     780           0 :     if(w.x && (w.y || w.z) && std::fabs(pw.x - p.o.x) > p.r.x)
     781             :     {
     782           0 :         vec c = entvol.center();
     783           0 :         float fv = pw.x < p.o.x ? p.o.x-p.r.x : p.o.x+p.r.x,
     784           0 :               fdist = (w.x*fv + w.y*c.y + w.z*c.z + w.offset) / (w.y*w.y + w.z*w.z);
     785           0 :         vec fdir(fv - c.x, -w.y*fdist, -w.z*fdist);
     786           0 :         if((pw.y-c.y-fdir.y)*w.y + (pw.z-c.z-fdir.z)*w.z >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen())
     787             :         {
     788           0 :             return true;
     789             :         }
     790             :     }
     791           0 :     if(w.y && (w.x || w.z) && std::fabs(pw.y - p.o.y) > p.r.y)
     792             :     {
     793           0 :         vec c = entvol.center();
     794           0 :         float fv = pw.y < p.o.y ? p.o.y-p.r.y : p.o.y+p.r.y,
     795           0 :               fdist = (w.x*c.x + w.y*fv + w.z*c.z + w.offset) / (w.x*w.x + w.z*w.z);
     796           0 :         vec fdir(-w.x*fdist, fv - c.y, -w.z*fdist);
     797           0 :         if((pw.x-c.x-fdir.x)*w.x + (pw.z-c.z-fdir.z)*w.z >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen())
     798             :         {
     799           0 :             return true;
     800             :         }
     801             :     }
     802           0 :     if(w.z && (w.x || w.y) && std::fabs(pw.z - p.o.z) > p.r.z)
     803             :     {
     804           0 :         vec c = entvol.center();
     805           0 :         float fv = pw.z < p.o.z ? p.o.z-p.r.z : p.o.z+p.r.z,
     806           0 :               fdist = (w.x*c.x + w.y*c.y + w.z*fv + w.offset) / (w.x*w.x + w.y*w.y);
     807           0 :         vec fdir(-w.x*fdist, -w.y*fdist, fv - c.z);
     808           0 :         if((pw.x-c.x-fdir.x)*w.x + (pw.y-c.y-fdir.y)*w.y >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen())
     809             :         {
     810           0 :             return true;
     811             :         }
     812             :     }
     813           0 :     return false;
     814             : }
     815             : 
     816           0 : static bool fuzzycollideplanes(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with deformed cube geometry
     817             : {
     818           0 :     clipplanes &p = getclipbounds(c, co, size, *d);
     819             : 
     820           0 :     if(std::fabs(d->o.x - p.o.x) > p.r.x + d->radius || std::fabs(d->o.y - p.o.y) > p.r.y + d->radius ||
     821           0 :        d->o.z + d->aboveeye < p.o.z - p.r.z || d->o.z - d->eyeheight > p.o.z + p.r.z)
     822             :     {
     823           0 :         return false;
     824             :     }
     825           0 :     collidewall = vec(0, 0, 0);
     826           0 :     float bestdist = -1e10f;
     827           0 :     int visible = forceclipplanes(c, co, size, p);
     828             : 
     829           0 :     if(!( checkside(*d, Orient_Left, dir, visible, cutoff,   p.o.x - p.r.x - (d->o.x + d->radius),   -dir.x, -d->radius, vec(-1, 0, 0), collidewall, bestdist)
     830           0 :        && checkside(*d, Orient_Right, dir, visible, cutoff,  d->o.x - d->radius - (p.o.x + p.r.x),    dir.x, -d->radius, vec(1, 0, 0), collidewall, bestdist)
     831           0 :        && checkside(*d, Orient_Back, dir, visible, cutoff,   p.o.y - p.r.y - (d->o.y + d->radius),   -dir.y, -d->radius, vec(0, -1, 0), collidewall, bestdist)
     832           0 :        && checkside(*d, Orient_Front, dir, visible, cutoff,  d->o.y - d->radius - (p.o.y + p.r.y),    dir.y, -d->radius, vec(0, 1, 0), collidewall, bestdist)
     833           0 :        && checkside(*d, Orient_Bottom, dir, visible, cutoff, p.o.z - p.r.z - (d->o.z + d->aboveeye), -dir.z,  d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1), collidewall, bestdist)
     834           0 :        && checkside(*d, Orient_Top, dir, visible, cutoff,    d->o.z - d->eyeheight - (p.o.z + p.r.z), dir.z,  d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1), collidewall, bestdist))
     835             :        )
     836             :     {
     837           0 :         return false;
     838             :     }
     839             : 
     840           0 :     mpr::EntCapsule entvol(d);
     841           0 :     int bestplane = -1;
     842           0 :     for(int i = 0; i < p.size; ++i)
     843             :     {
     844           0 :         const plane &w = p.p[i];
     845           0 :         vec pw = entvol.supportpoint(vec(w).neg());
     846           0 :         float dist = w.dist(pw);
     847           0 :         if(dist >= 0)
     848             :         {
     849           0 :             return false;
     850             :         }
     851           0 :         if(dist <= bestdist)
     852             :         {
     853           0 :             continue;
     854             :         }
     855           0 :         bestplane = -1;
     856           0 :         bestdist = dist;
     857           0 :         if(!dir.iszero())
     858             :         {
     859           0 :             if(w.dot(dir) >= -cutoff*dir.magnitude())
     860             :             {
     861           0 :                 continue;
     862             :             }
     863             :             //nasty ternary
     864           0 :             if(d->type==physent::PhysEnt_Player &&
     865           0 :                 dist < (dir.z*w.z < 0 ?
     866           0 :                         d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
     867           0 :                         (dir.x*w.x < 0 || dir.y*w.y < 0 ? -d->radius : 0)))
     868             :             {
     869           0 :                 continue;
     870             :             }
     871             :         }
     872           0 :         if(clampcollide(p, entvol, w, pw))
     873             :         {
     874           0 :             continue;
     875             :         }
     876           0 :         bestplane = i;
     877             :     }
     878             : 
     879           0 :     if(bestplane >= 0)
     880             :     {
     881           0 :         collidewall = p.p[bestplane];
     882             :     }
     883           0 :     else if(collidewall.iszero())
     884             :     {
     885           0 :         collideinside++;
     886           0 :         return false;
     887             :     }
     888           0 :     return true;
     889             : }
     890             : 
     891           0 : static bool cubecollidesolid(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with solid cube geometry
     892             : {
     893           0 :     int crad = size/2;
     894           0 :     if(std::fabs(d->o.x - co.x - crad) > d->radius + crad || std::fabs(d->o.y - co.y - crad) > d->radius + crad ||
     895           0 :        d->o.z + d->aboveeye < co.z || d->o.z - d->eyeheight > co.z + size)
     896             :     {
     897           0 :         return false;
     898             :     }
     899           0 :     mpr::EntOBB entvol(d);
     900           0 :     bool collided = mpr::collide(mpr::SolidCube(co, size), entvol);
     901           0 :     if(!collided)
     902             :     {
     903           0 :         return false;
     904             :     }
     905           0 :     collidewall = vec(0, 0, 0);
     906           0 :     float bestdist = -1e10f;
     907           0 :     int visible = !(c.visible&0x80) || d->type==physent::PhysEnt_Player ? c.visible : 0xFF;
     908             : 
     909           0 :     if(!( checkside(*d, Orient_Left, dir, visible, cutoff, co.x - entvol.right(), -dir.x, -d->radius, vec(-1, 0, 0), collidewall, bestdist)
     910           0 :        && checkside(*d, Orient_Right, dir, visible, cutoff, entvol.left() - (co.x + size), dir.x, -d->radius, vec(1, 0, 0), collidewall, bestdist)
     911           0 :        && checkside(*d, Orient_Back, dir, visible, cutoff, co.y - entvol.front(), -dir.y, -d->radius, vec(0, -1, 0), collidewall, bestdist)
     912           0 :        && checkside(*d, Orient_Front, dir, visible, cutoff, entvol.back() - (co.y + size), dir.y, -d->radius, vec(0, 1, 0), collidewall, bestdist)
     913           0 :        && checkside(*d, Orient_Bottom, dir, visible, cutoff, co.z - entvol.top(), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1), collidewall, bestdist)
     914           0 :        && checkside(*d, Orient_Top, dir, visible, cutoff, entvol.bottom() - (co.z + size), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1), collidewall, bestdist))
     915             :       )
     916             :     {
     917           0 :         return false;
     918             :     }
     919             : 
     920           0 :     if(collidewall.iszero())
     921             :     {
     922           0 :         collideinside++;
     923           0 :         return false;
     924             :     }
     925           0 :     return true;
     926             : }
     927             : 
     928           0 : static bool cubecollideplanes(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with deformed cube geometry
     929             : {
     930           0 :     clipplanes &p = getclipbounds(c, co, size, *d);
     931           0 :     if(std::fabs(d->o.x - p.o.x) > p.r.x + d->radius || std::fabs(d->o.y - p.o.y) > p.r.y + d->radius ||
     932           0 :        d->o.z + d->aboveeye < p.o.z - p.r.z || d->o.z - d->eyeheight > p.o.z + p.r.z)
     933             :     {
     934           0 :         return false;
     935             :     }
     936           0 :     mpr::EntOBB entvol(d);
     937           0 :     bool collided = mpr::collide(mpr::CubePlanes(p), entvol);
     938           0 :     if(!collided)
     939             :     {
     940           0 :         return false;
     941             :     }
     942           0 :     collidewall = vec(0, 0, 0);
     943           0 :     float bestdist = -1e10f;
     944           0 :     int visible = forceclipplanes(c, co, size, p);
     945           0 :     if(!( checkside(*d, Orient_Left, dir, visible, cutoff, p.o.x - p.r.x - entvol.right(),  -dir.x, -d->radius, vec(-1, 0, 0), collidewall, bestdist)
     946           0 :        && checkside(*d, Orient_Right, dir, visible, cutoff, entvol.left() - (p.o.x + p.r.x), dir.x, -d->radius, vec(1, 0, 0), collidewall, bestdist)
     947           0 :        && checkside(*d, Orient_Back, dir, visible, cutoff, p.o.y - p.r.y - entvol.front(),  -dir.y, -d->radius, vec(0, -1, 0), collidewall, bestdist)
     948           0 :        && checkside(*d, Orient_Front, dir, visible, cutoff, entvol.back() - (p.o.y + p.r.y), dir.y, -d->radius, vec(0, 1, 0), collidewall, bestdist)
     949           0 :        && checkside(*d, Orient_Bottom, dir, visible, cutoff, p.o.z - p.r.z - entvol.top(),  -dir.z,  d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1), collidewall, bestdist)
     950           0 :        && checkside(*d, Orient_Top, dir, visible, cutoff, entvol.bottom() - (p.o.z + p.r.z), dir.z,  d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1), collidewall, bestdist))
     951             :       )
     952             :     {
     953           0 :         return false;
     954             :     }
     955             : 
     956           0 :     int bestplane = -1;
     957           0 :     for(int i = 0; i < p.size; ++i)
     958             :     {
     959           0 :         const plane &w = p.p[i];
     960           0 :         vec pw = entvol.supportpoint(vec(w).neg());
     961           0 :         float dist = w.dist(pw);
     962           0 :         if(dist <= bestdist)
     963             :         {
     964           0 :             continue;
     965             :         }
     966           0 :         bestplane = -1;
     967           0 :         bestdist = dist;
     968           0 :         if(!dir.iszero())
     969             :         {
     970           0 :             if(w.dot(dir) >= -cutoff*dir.magnitude())
     971             :             {
     972           0 :                 continue;
     973             :             }
     974           0 :             if(d->type==physent::PhysEnt_Player &&
     975           0 :                 dist < (dir.z*w.z < 0 ?
     976           0 :                 d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
     977           0 :                 (dir.x*w.x < 0 || dir.y*w.y < 0 ? -d->radius : 0)))
     978             :             {
     979           0 :                 continue;
     980             :             }
     981             :         }
     982           0 :         if(clampcollide(p, entvol, w, pw))
     983             :         {
     984           0 :             continue;
     985             :         }
     986           0 :         bestplane = i;
     987             :     }
     988             : 
     989           0 :     if(bestplane >= 0)
     990             :     {
     991           0 :         collidewall = p.p[bestplane];
     992             :     }
     993           0 :     else if(collidewall.iszero())
     994             :     {
     995           0 :         collideinside++;
     996           0 :         return false;
     997             :     }
     998           0 :     return true;
     999             : }
    1000             : 
    1001           0 : static bool cubecollide(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size, bool solid)
    1002             : {
    1003           0 :     switch(d->collidetype)
    1004             :     {
    1005           0 :         case Collide_OrientedBoundingBox:
    1006             :         {
    1007           0 :             if(c.issolid() || solid)
    1008             :             {
    1009           0 :                 return cubecollidesolid(d, dir, cutoff, c, co, size);
    1010             :             }
    1011             :             else
    1012             :             {
    1013           0 :                 return cubecollideplanes(d, dir, cutoff, c, co, size);
    1014             :             }
    1015             :         }
    1016           0 :         case Collide_Ellipse:
    1017             :         {
    1018           0 :             if(c.issolid() || solid)
    1019             :             {
    1020           0 :                 return fuzzycollidesolid(d, dir, cutoff, c, co, size);
    1021             :             }
    1022             :             else
    1023             :             {
    1024           0 :                 return fuzzycollideplanes(d, dir, cutoff, c, co, size);
    1025             :             }
    1026             :         }
    1027           0 :         default:
    1028             :         {
    1029           0 :             return false;
    1030             :         }
    1031             :     }
    1032             : }
    1033             : 
    1034           0 : static bool octacollide(const physent *d, const vec &dir, float cutoff, const ivec &bo, const ivec &bs, const std::array<cube, 8> &c, const ivec &cor, int size) // collide with octants
    1035             : {
    1036           0 :     LOOP_OCTA_BOX(cor, size, bo, bs)
    1037             :     {
    1038           0 :         if(c[i].ext && c[i].ext->ents)
    1039             :         {
    1040           0 :             if(mmcollide(d, dir, cutoff, *c[i].ext->ents))
    1041             :             {
    1042           0 :                 return true;
    1043             :             }
    1044             :         }
    1045           0 :         ivec o(i, cor, size);
    1046           0 :         if(c[i].children)
    1047             :         {
    1048           0 :             if(octacollide(d, dir, cutoff, bo, bs, *(c[i].children), o, size>>1))
    1049             :             {
    1050           0 :                 return true;
    1051             :             }
    1052             :         }
    1053             :         else
    1054             :         {
    1055           0 :             bool solid = false;
    1056           0 :             switch(c[i].material&MatFlag_Clip)
    1057             :             {
    1058           0 :                 case Mat_NoClip:
    1059             :                 {
    1060           0 :                     continue;
    1061             :                 }
    1062           0 :                 case Mat_Clip:
    1063             :                 {
    1064           0 :                     if(IS_CLIPPED(c[i].material&MatFlag_Volume) || d->type==physent::PhysEnt_Player)
    1065             :                     {
    1066           0 :                         solid = true;
    1067             :                     }
    1068           0 :                     break;
    1069             :                 }
    1070             :             }
    1071           0 :             if(!solid && c[i].isempty())
    1072             :             {
    1073           0 :                 continue;
    1074             :             }
    1075           0 :             if(cubecollide(d, dir, cutoff, c[i], o, size, solid))
    1076             :             {
    1077           0 :                 return true;
    1078             :             }
    1079             :         }
    1080             :     }
    1081           0 :     return false;
    1082             : }
    1083             : 
    1084           0 : bool cubeworld::octacollide(const physent *d, const vec &dir, float cutoff, const ivec &bo, const ivec &bs) const
    1085             : {
    1086           0 :     int diff = (bo.x^bs.x) | (bo.y^bs.y) | (bo.z^bs.z),
    1087           0 :         scale = worldscale-1;
    1088           0 :     if(diff&~((1<<scale)-1) || static_cast<uint>(bo.x|bo.y|bo.z|bs.x|bs.y|bs.z) >= static_cast<uint>(mapsize()))
    1089             :     {
    1090           0 :        return ::octacollide(d, dir, cutoff, bo, bs, *worldroot, ivec(0, 0, 0), mapsize()>>1);
    1091             :     }
    1092           0 :     const cube *c = &((*worldroot)[OCTA_STEP(bo.x, bo.y, bo.z, scale)]);
    1093           0 :     if(c->ext && c->ext->ents && mmcollide(d, dir, cutoff, *c->ext->ents))
    1094             :     {
    1095           0 :         return true;
    1096             :     }
    1097           0 :     scale--;
    1098           0 :     while(c->children && !(diff&(1<<scale)))
    1099             :     {
    1100           0 :         c = &((*c->children)[OCTA_STEP(bo.x, bo.y, bo.z, scale)]);
    1101           0 :         if(c->ext && c->ext->ents && mmcollide(d, dir, cutoff, *c->ext->ents))
    1102             :         {
    1103           0 :             return true;
    1104             :         }
    1105           0 :         scale--;
    1106             :     }
    1107           0 :     if(c->children)
    1108             :     {
    1109           0 :         return ::octacollide(d, dir, cutoff, bo, bs, *c->children, ivec(bo).mask(~((2<<scale)-1)), 1<<scale);
    1110             :     }
    1111           0 :     bool solid = false;
    1112           0 :     switch(c->material&MatFlag_Clip)
    1113             :     {
    1114           0 :         case Mat_NoClip:
    1115             :         {
    1116           0 :             return false;
    1117             :         }
    1118           0 :         case Mat_Clip:
    1119             :         {
    1120           0 :             if(IS_CLIPPED(c->material&MatFlag_Volume) || d->type==physent::PhysEnt_Player)
    1121             :             {
    1122           0 :                 solid = true;
    1123             :             }
    1124           0 :             break;
    1125             :         }
    1126             :     }
    1127           0 :     if(!solid && c->isempty())
    1128             :     {
    1129           0 :         return false;
    1130             :     }
    1131           0 :     int csize = 2<<scale,
    1132           0 :         cmask = ~(csize-1);
    1133           0 :     return cubecollide(d, dir, cutoff, *c, ivec(bo).mask(cmask), csize, solid);
    1134             : }
    1135             : 
    1136             : // all collision happens here
    1137           0 : bool collide(const physent *d, const vec &dir, float cutoff, bool playercol, bool insideplayercol)
    1138             : {
    1139           0 :     collideinside = 0;
    1140           0 :     collideplayer = nullptr;
    1141           0 :     collidewall = vec(0, 0, 0);
    1142           0 :     ivec bo(static_cast<int>(d->o.x-d->radius), static_cast<int>(d->o.y-d->radius), static_cast<int>(d->o.z-d->eyeheight)),
    1143           0 :          bs(static_cast<int>(d->o.x+d->radius), static_cast<int>(d->o.y+d->radius), static_cast<int>(d->o.z+d->aboveeye));
    1144           0 :     bo.sub(1);
    1145           0 :     bs.add(1);  // guard space for rounding errors
    1146           0 :     return rootworld.octacollide(d, dir, cutoff, bo, bs) || (playercol && plcollide(d, dir, insideplayercol)); // collide with world
    1147             : }
    1148             : 
    1149           0 : void recalcdir(const physent *d, const vec &oldvel, vec &dir)
    1150             : {
    1151           0 :     float speed = oldvel.magnitude();
    1152           0 :     if(speed > 1e-6f)
    1153             :     {
    1154           0 :         float step = dir.magnitude();
    1155           0 :         dir = d->vel;
    1156           0 :         dir.add(d->falling);
    1157           0 :         dir.mul(step/speed);
    1158             :     }
    1159           0 : }
    1160             : 
    1161           0 : void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor, bool slidecollide)
    1162             : {
    1163           0 :     vec wall(obstacle);
    1164           0 :     if(foundfloor ? wall.z > 0 : slidecollide)
    1165             :     {
    1166           0 :         wall.z = 0;
    1167           0 :         if(!wall.iszero())
    1168             :         {
    1169           0 :             wall.normalize();
    1170             :         }
    1171             :     }
    1172           0 :     vec oldvel(d->vel);
    1173           0 :     oldvel.add(d->falling);
    1174           0 :     d->vel.project(wall);
    1175           0 :     d->falling.project(wall);
    1176           0 :     recalcdir(d, oldvel, dir);
    1177           0 : }
    1178             : 
    1179           0 : void avoidcollision(physent *d, const vec &dir, const physent *obstacle, float space)
    1180             : {
    1181           0 :     float rad = obstacle->radius+d->radius;
    1182           0 :     vec bbmin(obstacle->o);
    1183           0 :     bbmin.x -= rad;
    1184           0 :     bbmin.y -= rad;
    1185           0 :     bbmin.z -= obstacle->eyeheight+d->aboveeye;
    1186           0 :     bbmin.sub(space);
    1187           0 :     vec bbmax(obstacle->o);
    1188           0 :     bbmax.x += rad;
    1189           0 :     bbmax.y += rad;
    1190           0 :     bbmax.z += obstacle->aboveeye+d->eyeheight;
    1191           0 :     bbmax.add(space);
    1192             : 
    1193           0 :     for(int i = 0; i < 3; ++i)
    1194             :     {
    1195           0 :         if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i])
    1196             :         {
    1197           0 :             return;
    1198             :         }
    1199             :     }
    1200             : 
    1201           0 :     float mindist = 1e16f;
    1202           0 :     for(int i = 0; i < 3; ++i)
    1203             :     {
    1204           0 :         if(dir[i] != 0)
    1205             :         {
    1206           0 :             float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i];
    1207           0 :             mindist = std::min(mindist, dist);
    1208             :         }
    1209             :     }
    1210           0 :     if(mindist >= 0.0f && mindist < 1e15f)
    1211             :     {
    1212           0 :         d->o.add(static_cast<vec>(dir).mul(mindist));
    1213             :     }
    1214             : }
    1215             : 
    1216           0 : bool movecamera(physent *pl, const vec &dir, float dist, float stepdist)
    1217             : {
    1218           0 :     int steps = static_cast<int>(ceil(dist/stepdist));
    1219           0 :     if(steps <= 0)
    1220             :     {
    1221           0 :         return true;
    1222             :     }
    1223           0 :     vec d(dir);
    1224           0 :     d.mul(dist/steps);
    1225           0 :     for(int i = 0; i < steps; ++i)
    1226             :     {
    1227           0 :         vec oldpos(pl->o);
    1228           0 :         pl->o.add(d);
    1229           0 :         if(collide(pl, vec(0, 0, 0), 0, false))
    1230             :         {
    1231           0 :             pl->o = oldpos;
    1232           0 :             return false;
    1233             :         }
    1234             :     }
    1235           0 :     return true;
    1236             : }
    1237             : 
    1238           0 : bool droptofloor(vec &o, float radius, float height)
    1239             : {
    1240             :     static struct dropent : physent
    1241             :     {
    1242           0 :         dropent()
    1243           0 :         {
    1244           0 :             type = PhysEnt_Bounce;
    1245           0 :             vel = vec(0, 0, -1);
    1246           0 :         }
    1247           0 :     } d;
    1248           0 :     d.o = o;
    1249           0 :     if(!insideworld(d.o))
    1250             :     {
    1251           0 :         if(d.o.z < rootworld.mapsize())
    1252             :         {
    1253           0 :             return false;
    1254             :         }
    1255           0 :         d.o.z = rootworld.mapsize() - 1e-3f;
    1256           0 :         if(!insideworld(d.o))
    1257             :         {
    1258           0 :             return false;
    1259             :         }
    1260             :     }
    1261           0 :     vec v(0.0001f, 0.0001f, -1);
    1262           0 :     v.normalize();
    1263           0 :     if(rootworld.raycube(d.o, v, rootworld.mapsize()) >= rootworld.mapsize())
    1264             :     {
    1265           0 :         return false;
    1266             :     }
    1267           0 :     d.radius = d.xradius = d.yradius = radius;
    1268           0 :     d.eyeheight = height;
    1269           0 :     d.aboveeye = radius;
    1270           0 :     if(!movecamera(&d, d.vel, rootworld.mapsize(), 1))
    1271             :     {
    1272           0 :         o = d.o;
    1273           0 :         return true;
    1274             :     }
    1275           0 :     return false;
    1276             : }
    1277             : 
    1278           0 : float dropheight(const entity &e)
    1279             : {
    1280           0 :     switch(e.type)
    1281             :     {
    1282           0 :         case EngineEnt_Particles:
    1283             :         case EngineEnt_Mapmodel:
    1284             :         {
    1285           0 :             return 0.0f;
    1286             :         }
    1287           0 :         default:
    1288             :         {
    1289           0 :             return 4.0f;
    1290             :         }
    1291             :     }
    1292             : }
    1293             : 
    1294           0 : void dropenttofloor(entity *e)
    1295             : {
    1296           0 :     droptofloor(e->o, 1.0f, dropheight(*e));
    1297           0 : }
    1298             : 
    1299           0 : void vecfromyawpitch(float yaw, float pitch, int move, int strafe, vec &m)
    1300             : {
    1301           0 :     if(move)
    1302             :     {
    1303           0 :         m.x = move*-std::sin(yaw/RAD);
    1304           0 :         m.y = move*std::cos(yaw/RAD);
    1305             :     }
    1306             :     else
    1307             :     {
    1308           0 :         m.x = m.y = 0;
    1309             :     }
    1310             : 
    1311           0 :     if(pitch)
    1312             :     {
    1313           0 :         m.x *= std::cos(pitch/RAD);
    1314           0 :         m.y *= std::cos(pitch/RAD);
    1315           0 :         m.z = move*std::sin(pitch/RAD);
    1316             :     }
    1317             :     else
    1318             :     {
    1319           0 :         m.z = 0;
    1320             :     }
    1321             : 
    1322           0 :     if(strafe)
    1323             :     {
    1324           0 :         m.x += strafe*std::cos(yaw/RAD);
    1325           0 :         m.y += strafe*std::sin(yaw/RAD);
    1326             :     }
    1327           0 : }
    1328             : 
    1329           0 : bool entinmap(dynent *d, bool avoidplayers)        // brute force but effective way to find a free spawn spot in the map
    1330             : {
    1331           0 :     d->o.z += d->eyeheight; // pos specified is at feet
    1332           0 :     vec orig = d->o;
    1333             :     // try max 100 times
    1334           0 :     for(int i = 0; i < 100; ++i)
    1335             :     {
    1336           0 :         if(i)
    1337             :         {
    1338           0 :             d->o = orig;
    1339           0 :             d->o.x += (randomint(21)-10)*i/5;  // increasing distance
    1340           0 :             d->o.y += (randomint(21)-10)*i/5;
    1341           0 :             d->o.z += (randomint(21)-10)*i/5;
    1342             :         }
    1343           0 :         if(!collide(d) && !collideinside)
    1344             :         {
    1345           0 :             if(collideplayer)
    1346             :             {
    1347           0 :                 if(!avoidplayers)
    1348             :                 {
    1349           0 :                     continue;
    1350             :                 }
    1351           0 :                 d->o = orig;
    1352           0 :                 d->resetinterp();
    1353           0 :                 return false;
    1354             :             }
    1355             : 
    1356           0 :             d->resetinterp();
    1357           0 :             return true;
    1358             :         }
    1359             :     }
    1360             :     // leave ent at original pos, possibly stuck
    1361           0 :     d->o = orig;
    1362           0 :     d->resetinterp();
    1363           0 :     conoutf(Console_Warn, "can't find entity spawn spot! (%.1f, %.1f, %.1f)", d->o.x, d->o.y, d->o.z);
    1364           0 :     return false;
    1365             : }

Generated by: LCOV version 1.14