LCOV - code coverage report
Current view: top level - engine/world - physics.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 0 731 0.0 %
Date: 2025-01-07 07:51:37 Functions: 0 42 0.0 %

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

Generated by: LCOV version 1.14