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

Generated by: LCOV version 2.0-1