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

Generated by: LCOV version 2.0-1