Public feed #531974

JumpVEL

public cpp v1 · immutable
Raw Edit
·published 2007-06-03 00:47 UTC
rendered paste body
// physics.cpp: no physics books were hurt nor consulted in the construction of this code.// All physics computations and constants were invented on the fly and simply tweaked until// they "felt right", and have no basis in reality. Collision detection is simplistic but// very robust (uses discrete steps at fixed fps).#include "pch.h"#include "engine.h"const int MAXCLIPPLANES = 1000;clipplanes clipcache[MAXCLIPPLANES], *nextclip = NULL;void setcubeclip(cube &c, int x, int y, int z, int size){    if(nextclip == NULL)    {        loopi(MAXCLIPPLANES)        {            clipcache[i].next = clipcache+i+1;            clipcache[i].prev = clipcache+i-1;            clipcache[i].backptr = NULL;        }        clipcache[MAXCLIPPLANES-1].next = clipcache;        clipcache[0].prev = clipcache+MAXCLIPPLANES-1;        nextclip = clipcache;    }    if(c.ext && c.ext->clip)    {        if(nextclip == c.ext->clip) return;        clipplanes *&n = c.ext->clip->next;        clipplanes *&p = c.ext->clip->prev;        n->prev = p;        p->next = n;        n = nextclip;        p = nextclip->prev;        n->prev = c.ext->clip;        p->next = c.ext->clip;    }    else    {        if(nextclip->backptr) *nextclip->backptr = NULL;        nextclip->backptr = &ext(c).clip;        c.ext->clip = nextclip;        genclipplanes(c, x, y, z, size, *c.ext->clip);        nextclip = nextclip->next;    }}void freeclipplanes(cube &c){    if(!c.ext || !c.ext->clip) return;    c.ext->clip->backptr = NULL;    c.ext->clip = NULL;}/////////////////////////  ray - cube collision ///////////////////////////////////////////////static inline void pushvec(vec &o, const vec &ray, float dist){    vec d(ray);    d.mul(dist);    o.add(d);}static inline bool pointinbox(const vec &v, const vec &bo, const vec &br){    return v.x <= bo.x+br.x &&           v.x >= bo.x-br.x &&           v.y <= bo.y+br.y &&           v.y >= bo.y-br.y &&           v.z <= bo.z+br.z &&           v.z >= bo.z-br.z;}bool pointincube(const clipplanes &p, const vec &v){    if(!pointinbox(v, p.o, p.r)) return false;    loopi(p.size) if(p.p[i].dist(v)>1e-3f) return false;    return true;}vec hitslope;bool raycubeintersect(const cube &c, const vec &o, const vec &ray, float &dist){    clipplanes &p = *c.ext->clip;    if(pointincube(p, o)) { dist = 0; return true; }    loopi(p.size)    {        float a = ray.dot(p.p[i]);        if(a>=0) continue;        float f = -p.p[i].dist(o)/a;        if(f + dist < 0) continue;        vec d(o);        pushvec(d, ray, f+0.1f);        if(pointincube(p, d))        {            hitslope = p.p[i];            dist = f+0.1f;            return true;        }    }    return false;}extern void entselectionbox(const entity &e, vec &eo, vec &es);extern int entselradius;float hitentdist;int hitent, hitorient;static float disttoent(octaentities *oc, octaentities *last, const vec &o, const vec &ray, float radius, int mode, extentity *t){    vec eo, es;    int orient;    float dist = 1e16f, f = 0.0f;    if(oc == last || oc == NULL) return dist;    const vector<extentity *> &ents = et->getents();    #define entintersect(mask, type, func) {\        if((mode&(mask))==(mask)) \            loopv(oc->type) \                if(!last || last->type.find(oc->type[i])<0) \                { \                    extentity &e = *ents[oc->type[i]]; \                    if(!e.inoctanode || &e==t) continue; \                    func; \                    if(f<dist && f>0) { \                        hitentdist = dist = f; \                        hitent = oc->type[i]; \                        hitorient = orient; \                    } \                } \    }    entintersect(RAY_POLY, mapmodels,        if(e.attr3 && (e.triggerstate == TRIGGER_DISAPPEARED || !checktriggertype(e.attr3, TRIG_COLLIDE) || e.triggerstate == TRIGGERED) && (mode&RAY_ENTS)!=RAY_ENTS) continue;        orient = 0; // FIXME, not set        if(!mmintersect(e, o, ray, radius, mode, f)) continue;    );    entintersect(RAY_ENTS, other,        entselectionbox(e, eo, es);        if(!rayrectintersect(eo, es, o, ray, f, orient)) continue;    );    entintersect(RAY_ENTS, mapmodels,        entselectionbox(e, eo, es);        if(!rayrectintersect(eo, es, o, ray, f, orient)) continue;    );    return dist;}// optimized shadow versionstatic float shadowent(octaentities *oc, octaentities *last, const vec &o, const vec &ray, float radius, int mode, extentity *t){    float dist = 1e16f, f = 0.0f;    if(oc == last || oc == NULL) return dist;    const vector<extentity *> &ents = et->getents();    loopv(oc->mapmodels) if(!last || last->mapmodels.find(oc->mapmodels[i])<0)    {        extentity &e = *ents[oc->mapmodels[i]];        if(!e.inoctanode || &e==t) continue;        if(e.attr3 && (e.triggerstate == TRIGGER_DISAPPEARED || !checktriggertype(e.attr3, TRIG_COLLIDE) || e.triggerstate == TRIGGERED)) continue;        if(!mmintersect(e, o, ray, radius, mode, f)) continue;        if(f>0 && f<dist) dist = f;    }     return dist;}bool insideworld(const vec &o){    loopi(3) if(o[i]<0 || o[i]>=hdr.worldsize) return false;    return true;}#define INITRAYCUBE \    octaentities *oclast = NULL; \    float dist = 0, dent = mode&RAY_BB ? 1e16f : 1e14f; \    cube *last = NULL; \    vec v(o), invray(ray.x ? 1/ray.x : 0, ray.y ? 1/ray.y : 0, ray.z ? 1/ray.z : 0); \    static cube *levels[32]; \    levels[0] = worldroot; \    int l = 0, lsize = hdr.worldsize; \    ivec lo(0, 0, 0);#define CHECKINSIDEWORLD \    if(!insideworld(v)) \    { \        float disttoworld = 1e16f, exitworld = 1e16f; \        loopi(3) if(invray[i]!=0) \        { \            float c = v[i]; \            if(c>=0 && c<hdr.worldsize) \            { \                float d = ((invray[i]>0?hdr.worldsize:0)-c)*invray[i]; \                exitworld = min(exitworld, d); \            } \            else \            { \                float d = ((invray[i]>0?0:hdr.worldsize)-c)*invray[i]; \                if(d<0) return (radius>0?radius:-1); \                disttoworld = min(disttoworld, 0.1f + d); \            } \        } \        if(disttoworld > exitworld) return (radius>0?radius:-1); \        pushvec(v, ray, disttoworld); \        dist += disttoworld; \    }#define DOWNOCTREE(disttoent, earlyexit) \        cube *lc = levels[l]; \        for(;;) \        { \            lsize >>= 1; \            if(z>=lo.z+lsize) { lo.z += lsize; lc += 4; } \            if(y>=lo.y+lsize) { lo.y += lsize; lc += 2; } \            if(x>=lo.x+lsize) { lo.x += lsize; lc += 1; } \            if(lc->ext && lc->ext->ents && dent > 1e15f) \            { \                dent = disttoent(lc->ext->ents, oclast, o, ray, radius, mode, t); \                if(dent < 1e15f earlyexit) return min(dent, dist); \                oclast = lc->ext->ents; \            } \            if(lc->children==NULL) break; \            lc = lc->children; \            levels[++l] = lc; \        }#define FINDCLOSEST \        float disttonext = 1e16f; \        loopi(3) if(invray[i]!=0) \        { \            float d = (lo[i]+(invray[i]>0?lsize:0)-v[i])*invray[i]; \            if(d >= 0) disttonext = min(disttonext, d); \        } \		disttonext += 0.1f; \        pushvec(v, ray, disttonext); \        dist += disttonext; \        last = &c;#define UPOCTREE \        x = int(v.x); \        y = int(v.y); \        z = int(v.z); \        for(;;) \        { \            lo.x &= ~lsize; \            lo.y &= ~lsize; \            lo.z &= ~lsize; \            lsize <<= 1; \            if(x<lo.x+lsize && y<lo.y+lsize && z<lo.z+lsize && x>=lo.x && y>=lo.y && z>=lo.z) break; \            if(!l) break; \            --l; \        }float raycube(const vec &o, const vec &ray, float radius, int mode, int size, extentity *t){    if(ray.iszero()) return 0;    INITRAYCUBE    CHECKINSIDEWORLD    int x = int(v.x), y = int(v.y), z = int(v.z);    for(;;)    {        DOWNOCTREE(disttoent, && (mode&RAY_SHADOW));        cube &c = *lc;        if((dist>0 || !(mode&RAY_SKIPFIRST)) &&           (((mode&RAY_CLIPMAT) && c.ext && isclipped(c.ext->material) && c.ext->material != MAT_CLIP) ||            ((mode&RAY_EDITMAT) && c.ext && c.ext->material != MAT_AIR) ||            (!(mode&RAY_PASS) && lsize==size && !isempty(c)) ||            isentirelysolid(c) ||            dent < dist ||            last==&c))        {            if(last==&c && radius>0) dist = radius;            return min(dent, dist);        }        else if(!isempty(c))        {            float f = 0;            setcubeclip(c, lo.x, lo.y, lo.z, lsize);            if(raycubeintersect(c, v, ray, f) && (dist+f>0 || !(mode&RAY_SKIPFIRST)))                return min(dent, dist+f);        }        FINDCLOSEST        if(radius>0 && dist>=radius) return min(dent, dist);        UPOCTREE    }}// optimized version for lightmap shadowing... every cycle here counts!!!float shadowray(const vec &o, const vec &ray, float radius, int mode, extentity *t){    INITRAYCUBE    CHECKINSIDEWORLD     int x = int(v.x), y = int(v.y), z = int(v.z);    for(;;)    {        DOWNOCTREE(shadowent, );        cube &c = *lc;        if(isentirelysolid(c)) return dist;        else if(last==&c) return radius;                else if(!isempty(c))        {            float f = 0;            setcubeclip(c, lo.x, lo.y, lo.z, lsize);            if(raycubeintersect(c, v, ray, f)) return dist+f;        }        FINDCLOSEST        if(dist>=radius) return dist;        UPOCTREE    }}int rayent(const vec &o, const vec &ray, int &orient){    hitent = -1;    float d = raycube(o, ray, hitentdist = 1e20f, RAY_ENTS);    orient = hitorient;    if(hitentdist == d)         return hitent;    else        return -1;}float raycubepos(const vec &o, vec &ray, vec &hitpos, float radius, int mode, int size){    ray.normalize();    hitpos = ray;    float dist = raycube(o, ray, radius, mode, size);    hitpos.mul(dist);     hitpos.add(o);    return dist; }bool raycubelos(vec &o, vec &dest, vec &hitpos){    vec ray(dest);    ray.sub(o);    float mag = ray.magnitude();    float distance = raycubepos(o, ray, hitpos, mag, RAY_CLIPMAT|RAY_POLY);    return distance >= mag;}float rayfloor(const vec &o, vec &floor, int mode){    if(o.z<=0) return -1;    hitslope = vec(0, 0, 1);    float dist = raycube(o, vec(0, 0, -1), o.z, mode);    if(dist>=o.z) return -1;    floor = hitslope;    return dist;}/////////////////////////  entity collision  ///////////////////////////////////////////////// info about collisionsbool inside; // whether an internal collision happenedphysent *hitplayer; // whether the collection hit a playervec wall; // just the normal vectors.float walldistance;int strength = 12;const float STAIRHEIGHT = 4.1f;const float FLOORZ = 0.867f;const float SLOPEZ = 0.5f;const float WALLZ = 0.2f;const float JUMPVEL = 125.0f;if(strength < 10) {const float JUMPVEL = 120.0f;}else {const float JUMPVEL = 150.0f;}const float GRAVITY = 200.0f;const float STEPSPEED = 1.0f;bool ellipsecollide(dynent *d, const vec &dir, dynent *e){    float below = (e->o.z-e->eyeheight) - (d->o.z+d->aboveeye),          above = (d->o.z-d->eyeheight) - (e->o.z+e->aboveeye);    if(below>=0 || above>=0) return true;    float x = e->o.x - d->o.x, y = e->o.y - d->o.y;    float angle = atan2f(y, x), dangle = angle-(d->yaw+90)*RAD, eangle = angle-(e->yaw+90)*RAD;    float dx = d->xradius*cosf(dangle), dy = d->yradius*sinf(dangle);    float ex = e->xradius*cosf(eangle), ey = e->yradius*sinf(eangle);    float dist = sqrtf(x*x + y*y) - sqrtf(dx*dx + dy*dy) - sqrtf(ex*ex + ey*ey);    if(dist < 0)    {        if(dir.iszero() || ((d->type>=ENT_CAMERA || below >= -(d->eyeheight+d->aboveeye)/4.0f) && dir.z > 0))                                    {                wall = vec(0, 0, -1);             return false;        }        if(dir.iszero() || ((d->type>=ENT_CAMERA || above >= -(d->eyeheight+d->aboveeye)/3.0f) && dir.z < 0))                     {             wall = vec(0, 0, 1);             return false;        }        if(dir.iszero() || -x*dir.x + -y*dir.y < 0)        {                wall = vec(-x, -y, 0);            wall.normalize();            return false;        }    }    return true;}bool rectcollide(physent *d, const vec &dir, const vec &o, float xr, float yr,  float hi, float lo, uchar visible = 0xFF, bool collideonly = true, float cutoff = 0){    if(collideonly && !visible) return true;    vec s(d->o);    s.sub(o);    xr += d->radius;    yr += d->radius;    walldistance = -1e10f;    float zr = s.z>0 ? d->eyeheight+hi : d->aboveeye+lo;    float ax = fabs(s.x)-xr;    float ay = fabs(s.y)-yr;    float az = fabs(s.z)-zr;    if(ax>0 || ay>0 || az>0) return true;    wall.x = wall.y = wall.z = 0;#define TRYCOLLIDE(dim, ON, OP, N, P) \    { \        if(s.dim<0) { if(visible&(1<<ON) && (dir.iszero() || (dir.dim>0 && (d->type>=ENT_CAMERA || (N))))) { walldistance = a ## dim; wall.dim = -1; return false; } } \        else if(visible&(1<<OP) && (dir.iszero() || (dir.dim<0 && (d->type>=ENT_CAMERA || (P))))) { walldistance = a ## dim; wall.dim = 1; return false; } \    }    if(ax>ay && ax>az) TRYCOLLIDE(x, O_LEFT, O_RIGHT, ax > -d->radius, ax > -d->radius);    if(ay>az) TRYCOLLIDE(y, O_BACK, O_FRONT, ay > -d->radius, ay > -d->radius);    TRYCOLLIDE(z, O_BOTTOM, O_TOP,         az >= -(d->eyeheight+d->aboveeye)/4.0f,         az >= -(d->eyeheight+d->aboveeye)/3.0f);    if(collideonly) inside = true;    return collideonly;}#define DYNENTCACHESIZE 1024static uint dynentframe = 0;static struct dynentcacheentry{    int x, y;    uint frame;    vector<physent *> dynents;} dynentcache[DYNENTCACHESIZE];void cleardynentcache(){    dynentframe++;    if(!dynentframe || dynentframe == 1) loopi(DYNENTCACHESIZE) dynentcache[i].frame = 0;    if(!dynentframe) dynentframe = 1;}VARF(dynentsize, 6, 8, 12, cleardynentcache());#define DYNENTHASH(x, y) (((((x)^(y))<<5) + (((x)^(y))>>5)) & (DYNENTCACHESIZE - 1))const vector<physent *> &checkdynentcache(int x, int y){    dynentcacheentry &dec = dynentcache[DYNENTHASH(x, y)];    if(dec.x == x && dec.y == y && dec.frame == dynentframe) return dec.dynents;    dec.x = x;    dec.y = y;    dec.frame = dynentframe;    dec.dynents.setsize(0);    int numdyns = cl->numdynents(), dsize = 1<<dynentsize, dx = x<<dynentsize, dy = y<<dynentsize;    loopi(numdyns)    {        dynent *d = cl->iterdynents(i);        if(!d || d->state != CS_ALIVE ||           d->o.x+d->radius <= dx || d->o.x-d->radius >= dx+dsize ||           d->o.y+d->radius <= dy || d->o.y-d->radius >= dy+dsize)            continue;        dec.dynents.add(d);    }    return dec.dynents;}void updatedynentcache(physent *d){    for(int x = int(max(d->o.x-d->radius, 0))>>dynentsize, ex = int(min(d->o.x+d->radius, hdr.worldsize-1))>>dynentsize; x <= ex; x++)    for(int y = int(max(d->o.y-d->radius, 0))>>dynentsize, ey = int(min(d->o.y+d->radius, hdr.worldsize-1))>>dynentsize; y <= ey; y++)    {        dynentcacheentry &dec = dynentcache[DYNENTHASH(x, y)];        if(dec.x != x || dec.y != y || dec.frame != dynentframe || dec.dynents.find(d) >= 0) continue;        dec.dynents.add(d);    }}bool overlapsdynent(const vec &o, float radius){    for(int x = int(max(o.x-radius, 0))>>dynentsize, ex = int(min(o.x+radius, hdr.worldsize-1))>>dynentsize; x <= ex; x++)    for(int y = int(max(o.y-radius, 0))>>dynentsize, ey = int(min(o.y+radius, hdr.worldsize-1))>>dynentsize; y <= ey; y++)    {        const vector<physent *> &dynents = checkdynentcache(x, y);        loopv(dynents)        {            physent *d = dynents[i];            if(o.dist(d->o)-d->radius < radius) return true;        }    }    return false;}bool plcollide(physent *d, const vec &dir)    // collide with player or monster{    if(d->type==ENT_CAMERA || d->state!=CS_ALIVE) return true;    for(int x = int(max(d->o.x-d->radius, 0))>>dynentsize, ex = int(min(d->o.x+d->radius, hdr.worldsize-1))>>dynentsize; x <= ex; x++)    for(int y = int(max(d->o.y-d->radius, 0))>>dynentsize, ey = int(min(d->o.y+d->radius, hdr.worldsize-1))>>dynentsize; y <= ey; y++)    {        const vector<physent *> &dynents = checkdynentcache(x, y);        loopv(dynents)        {            physent *o = dynents[i];            if(o==d || d->o.reject(o->o, d->radius+o->radius)) continue;            if(!ellipsecollide((dynent *)d, dir, (dynent *)o))            {                hitplayer = o;                return false;            }        }    }    return true;}void rotatebb(vec &center, vec &radius, int yaw){    yaw = (yaw+7)-(yaw+7)%15;    yaw = 180-yaw;    if(yaw<0) yaw += 360;    switch(yaw)    {        case 0: break;        case 180:             center.x = -center.x;            center.y = -center.y;            break;        case 90:            swap(float, radius.x, radius.y);            swap(float, center.x, center.y);            center.x = -center.x;            break;        case 270:             swap(float, radius.x, radius.y);             swap(float, center.x, center.y);            center.y = -center.y;            break;        default:             radius.x = radius.y = max(radius.x, radius.y) + max(fabs(center.x), fabs(center.y));            center.x = center.y = 0.0f;            break;    }}bool mmcollide(physent *d, const vec &dir, octaentities &oc)               // collide with a mapmodel{    const vector<extentity *> &ents = et->getents();    loopv(oc.mapmodels)    {        extentity &e = *ents[oc.mapmodels[i]];        if(e.attr3 && (e.triggerstate == TRIGGER_DISAPPEARED || !checktriggertype(e.attr3, TRIG_COLLIDE) || e.triggerstate == TRIGGERED)) continue;        model *m = loadmodel(NULL, e.attr2);        if(!m || !m->collide) continue;        vec center, radius;        m->collisionbox(0, center, radius);        center.z -= radius.z;        radius.z *= 2;        rotatebb(center, radius, e.attr1);        if(!rectcollide(d, dir, center.add(e.o), radius.x, radius.y, radius.z, 0)) return false;    }    return true;}bool cubecollide(physent *d, const vec &dir, float cutoff, cube &c, int x, int y, int z, int size) // collide with cube geometry{    if(isentirelysolid(c) || (c.ext && (d->type<ENT_CAMERA || c.ext->material != MAT_CLIP) && isclipped(c.ext->material)))    {        int s2 = size>>1;        vec o = vec(x+s2, y+s2, z+s2);        vec r = vec(s2, s2, s2);        return rectcollide(d, dir, o, r.x, r.y, r.z, r.z, isentirelysolid(c) ? (c.ext ? c.ext->visible : 0) : 0xFF, true, cutoff);    }    setcubeclip(c, x, y, z, size);    clipplanes &p = *c.ext->clip;    float r = d->radius,          zr = (d->aboveeye+d->eyeheight)/2;    vec o(d->o), *w = &wall;    o.z += zr - d->eyeheight;    if(rectcollide(d, dir, p.o, p.r.x, p.r.y, p.r.z, p.r.z, c.ext->visible, !p.size, cutoff)) return true;    if(p.size)    {        if(!wall.iszero())        {            vec wo(o), wrad(r, r, zr);            loopi(3) if(wall[i]) { wo[i] = p.o[i]+wall[i]*p.r[i]; wrad[i] = 0; break; }            loopi(p.size)            {                plane &f = p.p[i];                if(!wall.dot(f)) continue;                if(f.dist(wo) >= vec(f.x*wrad.x, f.y*wrad.y, f.z*wrad.z).magnitude())                 {                     wall = vec(0, 0, 0);                    walldistance = -1e10f;                    break;                }            }        }        float m = walldistance;        loopi(p.size)        {            plane &f = p.p[i];            float dist = f.dist(o) - vec(f.x*r, f.y*r, f.z*zr).magnitude();            if(dist > 0) return true;            if(dist > m)            {                if(!dir.iszero())                {                    if(f.dot(dir) >= -cutoff) continue;                    if(d->type<ENT_CAMERA && dist < (dir.z*f.z < 0 ? -(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) : ((dir.x*f.x < 0 || dir.y*f.y < 0) ? -r : 0))) continue;                }                if(p.p[i].z) { if((p.p[i].z>0 ? o.z-p.o.z : p.o.z-o.z) + p.r.z - zr < dist) continue; }                else                {                    if(p.p[i].x) { if((p.p[i].x>0 ? o.x-p.o.x : p.o.x-o.x) + p.r.x - r < dist) continue; }                     if(p.p[i].y) { if((p.p[i].y>0 ? o.y-p.o.y : p.o.y-o.y) + p.r.y - r < dist) continue; }                }                w = &p.p[i];                m = dist;            }        }        wall = *w;        if(wall.iszero())        {            inside = true;            return true;        }    }    return false;}bool octacollide(physent *d, const vec &dir, float cutoff, const ivec &bo, const ivec &bs, cube *c, const ivec &cor, int size) // collide with octants{    loopoctabox(cor, size, bo, bs)    {        if(c[i].ext && c[i].ext->ents) if(!mmcollide(d, dir, *c[i].ext->ents)) return false;        ivec o(i, cor.x, cor.y, cor.z, size);        if(c[i].children)        {            if(!octacollide(d, dir, cutoff, bo, bs, c[i].children, o, size>>1)) return false;        }        else if(c[i].ext && c[i].ext->material!=MAT_NOCLIP && (!isempty(c[i]) || ((d->type<ENT_CAMERA || c[i].ext->material != MAT_CLIP) && isclipped(c[i].ext->material))))        {            if(!cubecollide(d, dir, cutoff, c[i], o.x, o.y, o.z, size)) return false;        }    }    return true;}// all collision happens herebool collide(physent *d, const vec &dir, float cutoff){    inside = false;    hitplayer = NULL;    wall.x = wall.y = wall.z = 0;    ivec bo(int(d->o.x-d->radius), int(d->o.y-d->radius), int(d->o.z-d->eyeheight)),         bs(int(d->radius)*2, int(d->radius)*2, int(d->eyeheight+d->aboveeye));    bs.add(2);  // guard space for rounding errors    if(!octacollide(d, dir, cutoff, bo, bs, worldroot, ivec(0, 0, 0), hdr.worldsize>>1)) return false; // collide with world    return plcollide(d, dir);}void slopegravity(float g, const vec &slope, vec &gvec){    float k = slope.z*g/(slope.x*slope.x + slope.y*slope.y);    gvec.x = slope.x*k;    gvec.y = slope.y*k;    gvec.z = -g;}void slideagainst(physent *d, vec &dir, const vec &obstacle){    vec wdir(obstacle), wvel(obstacle);    wdir.z = wvel.z = d->physstate >= PHYS_SLOPE ? 0.0f : min(obstacle.z, 0.0f);//    if(obstacle.z < 0.0f && dir.z > 0.0f) dir.z = d->vel.z = 0.0f;    wdir.mul(obstacle.dot(dir));    wvel.mul(obstacle.dot(d->vel));    dir.sub(wdir);    d->vel.sub(wvel);}void switchfloor(physent *d, vec &dir, bool landing, const vec &floor){    if(d->physstate == PHYS_FALL || d->floor.z < FLOORZ)    {        if(landing)        {            if(floor.z >= FLOORZ)            {                if(d->vel.z + d->gravity.z > 0) d->vel.add(d->gravity);                else if(d->vel.z > 0) d->vel.z = 0.0f;                d->gravity = vec(0, 0, 0);            }            else            {                float gmag = d->gravity.magnitude();                if(gmag > 1e-4f)                {                    vec g;                    slopegravity(-d->gravity.z, floor, g);                    if(d->physstate == PHYS_FALL || d->floor != floor)                    {                        float c = d->gravity.dot(floor)/gmag;                        g.normalize();                        g.mul(min(1.0f+c, 1.0f)*gmag);                    }                    d->gravity = g;                }            }        }    }    if(((d->physstate == PHYS_SLIDE || (d->physstate == PHYS_FALL && floor.z < 1.0f)) && landing) ||        (d->physstate >= PHYS_SLOPE && fabs(dir.dot(d->floor)/dir.magnitude()) < 0.01f))    {        if(floor.z > 0 && floor.z < WALLZ) { slideagainst(d, dir, floor); return; }        float dmag = dir.magnitude(), dz = -(dir.x*floor.x + dir.y*floor.y)/floor.z;        dir.z = dz;        float dfmag = dir.magnitude();        if(dfmag > 0) dir.mul(dmag/dfmag);        float vmag = d->vel.magnitude(), vz = -(d->vel.x*floor.x + d->vel.y*floor.y)/floor.z;        d->vel.z = vz;        float vfmag = d->vel.magnitude();        if(vfmag > 0) d->vel.mul(vmag/vfmag);    }}bool trystepup(physent *d, vec &dir, float maxstep){    vec old(d->o);    /* check if there is space atop the stair to move to */    if(d->physstate != PHYS_STEP_UP)    {        d->o.add(dir);        d->o.z += maxstep + 0.1f;        if(!collide(d))        {            d->o = old;            return false;        }    }    /* try stepping up */    d->o = old;    d->o.z += dir.magnitude()*STEPSPEED;    if(collide(d, vec(0, 0, 1)))    {        if(d->physstate == PHYS_FALL)        {            d->timeinair = 0;            d->floor = vec(0, 0, 1);            switchfloor(d, dir, true, d->floor);        }        d->physstate = PHYS_STEP_UP;        return true;    }    d->o = old;    return false;}#if 0bool trystepdown(physent *d, vec &dir, float step, float a, float b){    vec old(d->o);    vec dv(dir.x*a, dir.y*a, -step*b), v(dv);    v.mul(STAIRHEIGHT/(step*b));    d->o.add(v);    if(!collide(d, vec(0, 0, -1), SLOPEZ))    {        d->o = old;        d->o.add(dv);        if(collide(d, vec(0, 0, -1))) return true;    }    d->o = old;    return false;}#endifvoid falling(physent *d, vec &dir, const vec &floor){#if 0    if(d->physstate >= PHYS_FLOOR && (floor.z == 0.0f || floor.z == 1.0f))    {        vec moved(d->o);        d->o.z -= STAIRHEIGHT + 0.1f;        if(!collide(d, vec(0, 0, -1), SLOPEZ))        {            d->o = moved;            d->physstate = PHYS_STEP_DOWN;            return;        }        else d->o = moved;    }#endif    bool sliding = floor.z > 0.0f && floor.z < SLOPEZ;    switchfloor(d, dir, sliding, sliding ? floor : vec(0, 0, 1));    if(sliding)    {        if(d->timeinair > 0) d->timeinair = 0;        d->physstate = PHYS_SLIDE;        d->floor = floor;    }    else d->physstate = PHYS_FALL;}void landing(physent *d, vec &dir, const vec &floor){    if(d->physstate == PHYS_FALL)    {        d->timeinair = 0;        if(dir.z < 0.0f) dir.z = d->vel.z = 0.0f;    }    switchfloor(d, dir, true, floor);    if(floor.z >= FLOORZ) d->physstate = PHYS_FLOOR;    else d->physstate = PHYS_SLOPE;    d->floor = floor;}bool findfloor(physent *d, bool collided, const vec &obstacle, bool &slide, vec &floor){    bool found = false;    vec moved(d->o);    d->o.z -= 0.1f;    if(!collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE ? SLOPEZ : FLOORZ))    {        floor = wall;        found = true;    }    else if(collided && obstacle.z >= SLOPEZ)    {        floor = obstacle;        found = true;        slide = false;    }    else    {        if(d->physstate == PHYS_STEP_UP || d->physstate == PHYS_SLIDE)        {            if(!collide(d, vec(0, 0, -1)) && wall.z > 0.0f)            {                floor = wall;                if(floor.z > SLOPEZ) found = true;            }        }        else        {            d->o.z -= d->radius;            if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f && !collide(d, vec(0, 0, -1)))            {                floor = wall;                if(floor.z >= SLOPEZ && floor.z < 1.0f) found = true;            }        }        if(collided && (!found || obstacle.z > floor.z)) floor = obstacle;    }    d->o = moved;    return found;}bool move(physent *d, vec &dir){    vec old(d->o);#if 0    if(d->physstate == PHYS_STEP_DOWN && dir.z <= 0.0f && (d->move || d->strafe))    {        float step = dir.magnitude()*STEPSPEED;        if(trystepdown(d, dir, step, 0.75f, 0.25f)) return true;        if(trystepdown(d, dir, step, 0.5f, 0.5f)) return true;        if(trystepdown(d, dir, step, 0.25f, 0.75f)) return true;        d->o.z -= step;        if(collide(d, vec(0, 0, -1))) return true;        d->o = old;    }#endif    bool collided = false;    vec obstacle;    d->o.add(dir);    if(!collide(d, d->type!=ENT_CAMERA ? dir : vec(0, 0, 0)) || (d->type==ENT_AI && !collide(d)))    {        d->o = old;        if(d->type == ENT_CAMERA) return false;        obstacle = wall;        d->o.z -= (d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f ? d->radius+0.1f : STAIRHEIGHT);        if((d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR) || (!collide(d, vec(0, 0, -1), SLOPEZ) && (d->physstate == PHYS_STEP_UP || wall.z == 1.0f)))        {            d->o = old;            float floorz = (d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR ? d->floor.z : wall.z);            if(trystepup(d, dir, floorz < 1.0f ? d->radius+0.1f : STAIRHEIGHT)) return true;        }        else d->o = old;        /* can't step over the obstacle, so just slide against it */        collided = true;    }    vec floor(0, 0, 0);    bool slide = collided && obstacle.z < 1.0f,         found = findfloor(d, collided, obstacle, slide, floor);    if(slide)    {        slideagainst(d, dir, obstacle);        if(d->type == ENT_AI) d->blocked = true;    }    if(found)    {        if(d->type == ENT_CAMERA) return false;        landing(d, dir, floor);    }    else falling(d, dir, floor);    return !collided;}bool bounce(physent *d, float secs, float elasticity, float waterfric){    cube &c = lookupcube(int(d->o.x), int(d->o.y), int(d->o.z));    bool water = c.ext && isliquid(c.ext->material);    if(water)    {        if(d->vel.z > 0 && d->vel.z + d->gravity.z < 0) d->vel.z = 0.0f;        d->vel.mul(1.0f - secs/waterfric);        d->gravity.z = -4.0f*GRAVITY*secs;    }     else d->gravity.z -= GRAVITY*secs;    vec old(d->o);    loopi(2)    {        vec dir(d->vel);        if(water) dir.mul(0.5f);        dir.add(d->gravity);        dir.mul(secs);        d->o.add(dir);        if(collide(d, dir))        {            if(inside)            {                d->o = old;                d->gravity.mul(-elasticity);                d->vel.mul(-elasticity);            }            break;        }        else if(hitplayer) break;        d->o = old;        vec dvel(d->vel), wvel(wall);        dvel.add(d->gravity);        float c = wall.dot(dvel),              k = 1.0f + (1.0f-elasticity)*c/dvel.magnitude();        wvel.mul(elasticity*2.0f*c);        d->gravity.mul(k);        d->vel.mul(k);        d->vel.sub(wvel);    }    if(d->physstate!=PHYS_BOUNCE)    {        // make sure bouncers don't start inside geometry        if(d->o == old) return !hitplayer;        d->physstate = PHYS_BOUNCE;    }    return hitplayer!=0;}void avoidcollision(physent *d, const vec &dir, physent *obstacle, float space){    float rad = obstacle->radius+d->radius;    vec bbmin(obstacle->o);    bbmin.x -= rad;    bbmin.y -= rad;    bbmin.z -= obstacle->eyeheight+d->aboveeye;    bbmin.sub(space);    vec bbmax(obstacle->o);    bbmax.x += rad;    bbmax.y += rad;    bbmax.z += obstacle->aboveeye+d->eyeheight;    bbmax.add(space);    loopi(3) if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i]) return;    float mindist = 1e16f;    loopi(3) if(dir[i] != 0)    {        float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i];        mindist = min(mindist, dist);    }    if(mindist >= 0.0f && mindist < 1e15f) d->o.add(vec(dir).mul(mindist));}void dropenttofloor(entity *e){    if(!insideworld(e->o)) return;    vec v(0.0001f, 0.0001f, -1);    v.normalize();    if(raycube(e->o, v, hdr.worldsize) >= hdr.worldsize) return;    physent d;    d.type = ENT_CAMERA;    d.o = e->o;    d.vel = vec(0, 0, -1);    d.radius = 1.0f;    d.eyeheight = et->dropheight(*e);    d.aboveeye = 1.0f;    loopi(hdr.worldsize) if(!move(&d, v)) break;    e->o = d.o;}void phystest(){    static const char *states[] = {"float", "fall", "slide", "slope", "floor", "step up", "step down", "bounce"};    printf ("PHYS(pl): %s, air %d, floor: (%f, %f, %f), vel: (%f, %f, %f), g: (%f, %f, %f)\n", states[player->physstate], player->timeinair, player->floor.x, player->floor.y, player->floor.z, player->vel.x, player->vel.y, player->vel.z, player->gravity.x, player->gravity.y, player->gravity.z);    printf ("PHYS(cam): %s, air %d, floor: (%f, %f, %f), vel: (%f, %f, %f), g: (%f, %f, %f)\n", states[camera1->physstate], camera1->timeinair, camera1->floor.x, camera1->floor.y, camera1->floor.z, camera1->vel.x, camera1->vel.y, camera1->vel.z, camera1->gravity.x, camera1->gravity.y, camera1->gravity.z);}COMMAND(phystest, "");void vecfromyawpitch(float yaw, float pitch, int move, int strafe, vec &m){    if(move)    {        m.x = move*sinf(RAD*yaw);        m.y = move*-cosf(RAD*yaw);    }    else m.x = m.y = 0;    if(pitch)    {        m.x *= cosf(RAD*pitch);        m.y *= cosf(RAD*pitch);        m.z = move*sinf(RAD*pitch);    }    else m.z = 0;    if(strafe)    {        m.x += strafe*-cosf(RAD*yaw);        m.y += strafe*-sinf(RAD*yaw);    }}void vectoyawpitch(const vec &v, float &yaw, float &pitch){    yaw = -(float)atan2(v.x, v.y)/RAD + 180;    pitch = asin(v.z/v.magnitude())/RAD;}VARP(maxroll, 0, 3, 20);void modifyvelocity(physent *pl, bool local, bool water, bool floating, int curtime){    if(floating)    {        if(pl->jumpnext)        {            pl->jumpnext = false;            pl->vel.z = JUMPVEL;        }    }    else    if(pl->physstate >= PHYS_SLOPE || water)    {        if(water && pl->timeinair > 0)        {            pl->timeinair = 0;            pl->vel.z = 0;        }        if(pl->jumpnext)        {            pl->jumpnext = false;            pl->vel.z = JUMPVEL; // physics impulse upwards            if(water) { pl->vel.x /= 8.0f; pl->vel.y /= 8.0f; } // dampen velocity change even harder, gives correct water feel            cl->physicstrigger(pl, local, 1, 0);        }    }    else    {        pl->timeinair += curtime;    }    vec m(0.0f, 0.0f, 0.0f);    if(pl->type==ENT_AI)    {        dynent *d = (dynent *)pl;        if(d->rotspeed && d->yaw!=d->targetyaw)        {            float oldyaw = d->yaw, diff = d->rotspeed*curtime/1000.0f, maxdiff = fabs(d->targetyaw-d->yaw);            if(diff >= maxdiff)             {                d->yaw = d->targetyaw;                d->rotspeed = 0;            }            else d->yaw += (d->targetyaw>d->yaw ? 1 : -1) * min(diff, maxdiff);            d->normalize_yaw(d->targetyaw);            if(!plcollide(d, vec(0, 0, 0)))            {                d->yaw = oldyaw;                m.x = d->o.x - hitplayer->o.x;                m.y = d->o.y - hitplayer->o.y;                if(!m.iszero()) m.normalize();            }        }    }    if(m.iszero() && (pl->move || pl->strafe))    {        vecfromyawpitch(pl->yaw, floating || water || pl->type==ENT_CAMERA ? pl->pitch : 0, pl->move, pl->strafe, m);        if(!floating && pl->physstate >= PHYS_SLIDE)        {            /* move up or down slopes in air             * but only move up slopes in water             */            float dz = -(m.x*pl->floor.x + m.y*pl->floor.y)/pl->floor.z;            if(water) m.z = max(m.z, dz);            else if(pl->floor.z >= WALLZ) m.z = dz;        }        m.normalize();    }    vec d(m);    d.mul(pl->maxspeed);    if(!water && !floating) d.mul((pl->move && !pl->strafe ? 1.3f : 1.0f) * (pl->physstate < PHYS_SLOPE && pl->move>=0 ? 1.3f : 1.0f)); // EXPERIMENTAL    float friction = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f);    float fpsfric = friction/curtime*20.0f;    pl->vel.mul(fpsfric-1);    pl->vel.add(d);    pl->vel.div(fpsfric);}void modifygravity(physent *pl, bool water, float secs){    vec g(0, 0, 0);    if(pl->physstate == PHYS_FALL) g.z -= GRAVITY*secs;    else if(!pl->floor.iszero() && pl->floor.z < FLOORZ)    {        float c = min(FLOORZ - pl->floor.z, FLOORZ-SLOPEZ)/(FLOORZ-SLOPEZ);        slopegravity(GRAVITY*secs*c, pl->floor, g);    }    if(water) pl->gravity = pl->move || pl->strafe ? vec(0, 0, 0) : g.mul(4.0f);    else pl->gravity.add(g);}// main physics routine, moves a player/monster for a curtime step// moveres indicated the physics precision (which is lower for monsters and multiplayer prediction)// local is false for multiplayer predictionbool moveplayer(physent *pl, int moveres, bool local, int curtime){    cube &c = lookupcube(int(pl->o.x), int(pl->o.y), int(pl->o.z));    bool water = c.ext && isliquid(c.ext->material);    bool floating = (editmode && local) || pl->state==CS_EDITING || pl->state==CS_SPECTATOR;    float secs = curtime/1000.f;    // apply any player generated changes in velocity    modifyvelocity(pl, local, water, floating, curtime);    // apply gravity    if(!floating && pl->type!=ENT_CAMERA) modifygravity(pl, water, secs);    vec d(pl->vel);    if(!floating && pl->type!=ENT_CAMERA && water) d.mul(0.5f);    d.add(pl->gravity);    d.mul(secs);    pl->blocked = false;    pl->moving = true;    if(floating)                // just apply velocity    {        if(pl->physstate != PHYS_FLOAT)        {            pl->physstate = PHYS_FLOAT;            pl->timeinair = 0;            pl->gravity = vec(0, 0, 0);        }        pl->o.add(d);    }    else                        // apply velocity with collision    {        const float f = 1.0f/moveres;        const int timeinair = pl->timeinair;        int collisions = 0;        d.mul(f);        loopi(moveres) if(!move(pl, d)) { if(pl->type==ENT_CAMERA) return false; if(++collisions<5) i--; } // discrete steps collision detection & sliding        if(timeinair > 800 && !pl->timeinair) // if we land after long time must have been a high jump, make thud sound        {            cl->physicstrigger(pl, local, -1, 0);        }    }    if(pl->state==CS_ALIVE) updatedynentcache(pl);    if(!pl->timeinair && pl->physstate >= PHYS_FLOOR && pl->vel.squaredlen() < 1e-4f && pl->gravity.iszero()) pl->moving = false;    // automatically apply smooth roll when strafing    if(pl->strafe==0)    {        pl->roll = pl->roll/(1+(float)sqrtf((float)curtime)/25);    }    else    {        pl->roll += pl->strafe*curtime/-30.0f;        if(pl->roll>maxroll) pl->roll = (float)maxroll;        if(pl->roll<-maxroll) pl->roll = (float)-maxroll;    }    // play sounds on water transitions    if(pl->type!=ENT_CAMERA)    {        cube &c = lookupcube((int)pl->o.x, (int)pl->o.y, (int)pl->o.z+1);        bool inwater = c.ext && isliquid(c.ext->material);        if(!pl->inwater && inwater) cl->physicstrigger(pl, local, 0, -1);        else if(pl->inwater && !inwater) cl->physicstrigger(pl, local, 0, 1);        pl->inwater = inwater;    }    return true;}VARP(minframetime, 5, 10, 20);int physicsfraction = 0, physicsrepeat = 0;void physicsframe()          // optimally schedule physics frames inside the graphics frames{    if(curtime>=minframetime)    {        int faketime = curtime+physicsfraction;        physicsrepeat = faketime/minframetime;        physicsfraction = faketime%minframetime;    }    else    {        physicsrepeat = 1;    }    cleardynentcache();}void moveplayer(physent *pl, int moveres, bool local){    loopi(physicsrepeat) moveplayer(pl, moveres, local, min(curtime, minframetime));    if(pl->o.z<0 && pl->state==CS_ALIVE) cl->worldhurts(pl, 400);}void updatephysstate(physent *d){    if(d->physstate == PHYS_FALL) return;    d->timeinair = 0;    vec old(d->o);    /* Attempt to reconstruct the floor state.     * May be inaccurate since movement collisions are not considered.     * If good floor is not found, just keep the old floor and hope it's correct enough.     */    switch(d->physstate)    {        case PHYS_SLOPE:        case PHYS_FLOOR:            d->o.z -= 0.1f;            if(!collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE ? SLOPEZ : FLOORZ))                d->floor = wall;            else if(d->physstate == PHYS_SLOPE)            {                d->o.z -= d->radius;                if(!collide(d, vec(0, 0, -1), SLOPEZ))                    d->floor = wall;            }            break;        case PHYS_STEP_UP:            d->o.z -= STAIRHEIGHT+0.1f;            if(!collide(d, vec(0, 0, -1), SLOPEZ))                d->floor = wall;            break;        case PHYS_SLIDE:            d->o.z -= d->radius+0.1f;            if(!collide(d, vec(0, 0, -1)) && wall.z < SLOPEZ)                d->floor = wall;            break;    }    if(d->physstate > PHYS_FALL && d->floor.z <= 0) d->floor = vec(0, 0, 1);    d->o = old;}bool intersect(physent *d, vec &from, vec &to)   // if lineseg hits entity bounding box{    vec v = to, w = d->o, *p;    v.sub(from);    w.sub(from);    float c1 = w.dot(v);    if(c1<=0) p = &from;    else    {        float c2 = v.dot(v);        if(c2<=c1) p = &to;        else        {            float f = c1/c2;            v.mul(f);            v.add(from);            p = &v;        }    }    return p->x <= d->o.x+d->radius        && p->x >= d->o.x-d->radius        && p->y <= d->o.y+d->radius        && p->y >= d->o.y-d->radius        && p->z <= d->o.z+d->aboveeye        && p->z >= d->o.z-d->eyeheight;}#define dir(name,v,d,s,os) ICOMMAND(name, "D", { player->s = args!=NULL; player->v = player->s ? d : (player->os ? -(d) : 0); });dir(backward, move,   -1, k_down,  k_up);dir(forward,  move,    1, k_up,    k_down);dir(left,     strafe,  1, k_left,  k_right);dir(right,    strafe, -1, k_right, k_left);ICOMMAND(jump,   "D", { if(cl->canjump()) player->jumpnext = args!=NULL; });ICOMMAND(attack, "D", { cl->doattack(args!=NULL); });VARP(sensitivity, 0, 10, 1000);VARP(sensitivityscale, 1, 1, 100);VARP(invmouse, 0, 0, 1);void fixcamerarange(){    const float MAXPITCH = 90.0f;    if(camera1->pitch>MAXPITCH) camera1->pitch = MAXPITCH;    if(camera1->pitch<-MAXPITCH) camera1->pitch = -MAXPITCH;    while(camera1->yaw<0.0f) camera1->yaw += 360.0f;    while(camera1->yaw>=360.0f) camera1->yaw -= 360.0f;}void mousemove(int dx, int dy){    const float SENSF = 33.0f;     // try match quake sens    camera1->yaw += (dx/SENSF)*(sensitivity/(float)sensitivityscale);    camera1->pitch -= (dy/SENSF)*(sensitivity/(float)sensitivityscale)*(invmouse ? -1 : 1);    fixcamerarange();    if(camera1!=player && player->state!=CS_DEAD)    {        player->yaw = camera1->yaw;        player->pitch = camera1->pitch;    }}bool entinmap(dynent *d, bool avoidplayers)        // brute force but effective way to find a free spawn spot in the map{    d->o.z += d->eyeheight;     // pos specified is at feet    vec orig = d->o;    loopi(100)                  // try max 100 times    {        if(collide(d) && !inside) return true;        if(hitplayer && avoidplayers)        {            d->o = orig;            return false;        }        d->o = orig;        d->o.x += (rnd(21)-10)*i/5;  // increasing distance        d->o.y += (rnd(21)-10)*i/5;        d->o.z += (rnd(21)-10)*i/5;    }    conoutf("can't find entity spawn spot! (%d, %d)", d->o.x, d->o.y);    // leave ent at original pos, possibly stuck    d->o = orig;    return false;}