From 4e77b91c0ee3792d745bcca445bd1c84113743b8 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sun, 28 May 2017 00:37:13 +0200 Subject: [PATCH] re-check line-of-sight depth readout from the graphics driver - manually intersect line-of-sight with all map geometry - shorten sight vector if necessary --- source/src/main.cpp | 13 ++- source/src/physics.cpp | 8 +- source/src/protos.h | 2 + source/src/rendergl.cpp | 1 + source/src/tools.h | 1 + source/src/weapon.cpp | 197 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 209 insertions(+), 13 deletions(-) diff --git a/source/src/main.cpp b/source/src/main.cpp index 29e7d3bd..57a5a8e1 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -976,12 +976,21 @@ void initclientlog() // rotate old logfiles and create new one DELETEP(bootclientlog); } +#ifdef _DEBUG +void sanitychecks() +{ + ASSERT((GMMASK__BOT ^ GMMASK__MP ^ GMMASK__TEAM ^ GMMASK__FFA ^ GMMASK__TEAMSPAWN ^ GMMASK__FFASPAWN) == GMMASK__ALL); + float fa = 1 << LARGEST_FACTOR, fb = fa; + while(fa != fa + fb) fb /= 2; + ASSERT(2 * fb < NEARZERO); +} +#endif + #define DEFAULTPROFILEPATH "profile" int main(int argc, char **argv) { - ASSERT((GMMASK__BOT ^ GMMASK__MP ^ GMMASK__TEAM ^ GMMASK__FFA ^ GMMASK__TEAMSPAWN ^ GMMASK__FFASPAWN) == GMMASK__ALL); - + DEBUGCODE(sanitychecks()); extern struct servercommandline scl; #ifdef WIN32 //atexit((void (__cdecl *)(void))_CrtDumpMemoryLeaks); diff --git a/source/src/physics.cpp b/source/src/physics.cpp index 14ffa14d..29819959 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -133,7 +133,7 @@ static int cornersurface = 0; bool mmcollide(physent *d, float &hi, float &lo) // collide with a mapmodel { - const float eyeheight = d->eyeheight; + const float eyeheight = d->eyeheight, SQRT2HALF = SQRT2 / 2.0f; const float playerheight = eyeheight + d->aboveeye; if(editmode) clentstats.firstclip = 0; for(int i = clentstats.firstclip; i < ents.length(); i++) @@ -150,7 +150,7 @@ bool mmcollide(physent *d, float &hi, float &lo) // collide with a map break; case 3: // clip rotated 45° { - float rx = (e.x - d->o.x) * 0.707106781f, ry = (e.y - d->o.y) * 0.707106781f, rr = d->radius * 1.414213562f; // rotate player instead of clip (adjust player radius to compensate) + float rx = (e.x - d->o.x) * SQRT2HALF, ry = (e.y - d->o.y) * SQRT2HALF, rr = d->radius * SQRT2; // rotate player instead of clip (adjust player radius to compensate) float a1 = fabs(rx - ry) - float(e.attr3) / ENTSCALE5 - rr, a2 = fabs(rx + ry) - float(e.attr2) / ENTSCALE5 - rr; if(a1 < 0 && a2 < 0) { @@ -243,8 +243,8 @@ bool collide(physent *d, bool spawn, float drop, float rise) case CORNER: { - sqr *ns = NULL, *h = NULL; - int bx = x, by = y, bs = 1; + sqr *ns, *h = NULL; + int bx, by, bs; int q = cornertest(x, y, bx, by, bs, ns, h); bool matter = false, match = false; switch(q) // 0XX3 diff --git a/source/src/protos.h b/source/src/protos.h index 1fd13119..4fa24cae 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -853,6 +853,7 @@ extern int loadallxmaps(); // physics extern float raycube(const vec &o, const vec &ray, vec &surface); extern bool raycubelos(const vec &from, const vec &to, float margin = 0); +extern int cornertest(int x, int y, int &bx, int &by, int &bs, sqr *&s, sqr *&h); extern void moveplayer(physent *pl, int moveres, bool local); extern void moveplayer(physent *pl, int moveres, bool local, int curtime); extern void movebounceent(bounceent *p, int moveres, bool local); @@ -899,6 +900,7 @@ extern playerent *playerincrosshair(); extern int magsize(int gun); extern void setscope(bool activate); extern void setburst(bool activate); +extern void intersectgeometry(const vec &from, vec &to); extern int intersect(playerent *d, const vec &from, const vec &to, vec *end = NULL); extern bool intersect(entity *e, const vec &from, const vec &to, vec *end = NULL); extern void damageeffect(int damage, playerent *d); diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index 0873e9ab..c9ba603c 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -1004,6 +1004,7 @@ void readdepth(int w, int h, vec &pos) vec4 world; invmvpmatrix.transform(screen, world); pos = vec(world.x, world.y, world.z).div(world.w); + intersectgeometry(camera1->o, pos); } VARP(ignoreoverride_nowaterreflect, 0, 0, 1); diff --git a/source/src/tools.h b/source/src/tools.h index 8213f9b2..d47a7cc9 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -83,6 +83,7 @@ inline int iabs(int n) { return labs(n); } #define SQRT2 (1.414213562f) #define SQRT3 (1.732050808f) #define RAD (0.01745329252f) /* PI / 180 */ +#define NEARZERO (0.000245f) /* epsilon for 1<eyeheight; - vec o(d->o); - o.z += (d->aboveeye - eyeheight)/2; - return intersectbox(o, vec(d->radius, d->radius, (d->aboveeye + eyeheight)/2), from, to, end) ? 1 : 0; -#endif } bool intersect(entity *e, const vec &from, const vec &to, vec *end) @@ -375,6 +368,196 @@ playerent *playerincrosshair() else return NULL; } +inline bool intersecttriangle(const vec &from, const vec &dir, const vec &v0, const vec &v1, const vec &v2, vec *end, float *_t) // precise but rather expensive, based on Moeller–Trumbore intersection algorithm +{ + const float EPSILON = 0.00001f; + vec edge1 = v1; edge1.sub(v0); // edge1 = v1 - v0 + vec edge2 = v2; edge2.sub(v0); // edge2 = v2 - v0 + vec pvec; pvec.cross(dir, edge2); // pvec = dir x edge2 + float det = edge1.dot(pvec); // det = edge1 * pvec + if(fabs(det) < EPSILON) return false; + float invdet = 1.0f / det; + vec tvec = from; tvec.sub(v0); // tvec = from - v0 + float u = invdet * tvec.dot(pvec); // u = tvec * pvec / det + if(u < 0.0f || u > 1.0f) return false; + vec qvec; qvec.cross(tvec, edge1); // qvec = tvec x edge1 + float v = invdet * dir.dot(qvec); // v = dir * qvec / det + if(v < 0.0f || u + v > 1.0f) return false; + float t = invdet * edge2.dot(qvec); // t = edge2 * qvec / det + if(t < EPSILON) return false; + if(_t) *_t = t; // 0..1 if intersection is between from and to + if(end) *end = dir, end->mul(t).add(from); // calculate point of intersection + return true; +} + +inline bool intersecttriangle2(const vec &from, const vec &to, const vec &v0, const vec &v1, const vec &v2, vec *end, float *_t) +{ + vec dir = to; dir.sub(from); // dir = to - from + return intersecttriangle(from, dir, v0, v1, v2, end, _t); +} + +inline bool intersectcorner(const vec &from, const vec &dir, int x, int y, int size, bool cdir, vec *end, float *_t) +{ + float fsx, fsy, dsx, dsy; + if(cdir) + { + dsx = dir.x + dir.y; fsx = from.x - x + from.y - y - size; + dsy = dir.y - dir.x; fsy = from.y - y - from.x + x + size; + } + else + { + dsx = dir.x - dir.y; fsx = from.x - x - from.y + y; + dsy = dir.y + dir.x; fsy = from.y - y + from.x - x; + } + if(!dsx) return false; + float t = -fsx / dsx; + float dy = dsy * t + fsy; + if(fabs(t * dsx + fsx) < NEARZERO && dy >= 0 && dy <= 2 * size) + { + if(_t) *_t = t; + if(end) *end = dir, end->mul(t).add(from); + return true; + } + return false; +} + +void intersectgeometry(const vec &from, vec &to) // check line for contact with map geometry, shorten if necessary +{ + int x = from.x, y = from.y, hfnb[4] = { 0, 1, ssize, ssize +1 }; + if(OUTBORD(x, y) || from.z < -127.0f || from.z > 127.0f) return; + vec d = to; // d: direction + d.sub(from); + + float distmin = 1.0f, vdelta[4]; + sqr *r[2], *s, *nb[4]; + if(fabs(d.x) + fabs(d.y) < 2.0f) d.mul((distmin = 32.0f)); + loop(xy, 2) // first check x == const planes and then y == const planes + { + float dxy = xy ? d.y : d.x, fromxy = xy ? from.y : from.x; + bool dxynz = fabs(dxy) < NEARZERO; + if(dxy == 0.0f) dxy = 1e-20; // hack + + int ixy = fromxy, step = dxy < 0 ? -1 : 1, steps = fabs(dxy) + 1, sqrdir = xy ? -ssize : -1; + while(steps-- > 0) + { + // intersect d with plane + float t = dxynz ? distmin : (ixy - fromxy) / dxy; + vec p = d; + p.mul(t).add(from); + + // check position + if(xy) x = p.x, y = ixy; + else x = ixy, y = p.y; + if(t > distmin || OUTBORD(x, y)) break; + + // always check cubes on both sides of the plane + r[0] = S(x, y); + r[1] = r[0] + sqrdir; + if(t > 0) loopk(2) + { + s = r[k]; + if(SOLID(s) || s->floor > p.z || s->ceil < p.z || s->type == CORNER) + { // cube s is a probable hit, examine further + if(k) + { // match x|y back to s + if(xy) y--; + else x--; + } + + if(s->type == CORNER) + { + sqr *ns, *h = NULL, *n; + int bx, by, bs; + int q = cornertest(x, y, bx, by, bs, ns, h); + vec newto; float newdist; + if(intersectcorner(from, d, bx, by, bs, !(q & 1), &newto, &newdist) && newdist < distmin && (!h || newto.z < ns->floor || newto.z > ns->ceil)) to = newto, distmin = newdist; + if(d.z) + { // intersect with z == const plane where the corner ends + bool downwards = d.z < 0.0f; + n = h ? h : ns; + float endplate = downwards ? n->floor : n->ceil; + float tz = (endplate - from.z) / d.z; + vec pz = d; + pz.mul(tz).add(from); + if(pz.x >= bx && pz.x <= bx + bs && pz.y >= by && pz.y <= by + bs && tz < distmin) to = pz, distmin = tz; + if(h) + { // intersect with the triangles where socket corners end + float sockplate = downwards ? ns->floor : ns->ceil; + tz = (sockplate - from.z) / d.z; + pz = d; pz.mul(tz).add(from); + float cx = pz.x - bx, cy = pz.y - by; + bool hit = false; + switch(q) // 0XX3 + { // XXXX + case 0: hit = cx + cy >= bs; break; // 1XX2 + case 1: hit = cx >= cy; break; + case 2: hit = cx + cy <= bs; break; + case 3: hit = cx <= cy; break; + default: hit = true; break; // annoy bad mappers + } + if(hit && cx >= 0 && cy >= 0 && cx <= bs && cy <= bs && tz < distmin) to = pz, distmin = tz; + } + } + } + else + { + // finish checking walls + if(SOLID(s) || s->type == SPACE || (s->type == CHF && s->floor > p.z) || (s->type == FHF && s->ceil < p.z)) + { + if(t < distmin) to = p, distmin = t; + } + else if(s->type == CHF || s->type == FHF) + { + loopi(4) vdelta[i] = ((nb[i] = s + hfnb[i]))->vdelta; // 23 + int nb2 = "\002\001\013\023"[xy + 2 * k], nb1 = nb2 >> 3; nb2 &= 3; + if(s->type == FHF) + { // FHF side surfaces + float a = s->floor - vdelta[nb1] / 4.0f, b = (vdelta[nb1] - vdelta[nb2]) / 4.0f, dummy, i = modff(xy ? p.x : p.y, &dummy); + if(a + i * b > p.z && t < distmin) to = p, distmin = t; + } + else + { // CHF side surfaces + float a = s->ceil + vdelta[nb1] / 4.0f, b = (vdelta[nb2] - vdelta[nb1]) / 4.0f, dummy, i = modff(xy ? p.x : p.y, &dummy); + if(a + i * b < p.z && t < distmin) to = p, distmin = t; + } + } + + // check floor and ceiling + bool isflat = true, downwards = d.z < 0.0f; + if(s->type == CHF || s->type == FHF) loopi(3) if(vdelta[3] != vdelta[i]) isflat = false; + float h = downwards ? s->floor - (isflat && s->type == FHF ? vdelta[0] / 4.0f : 0) : s->ceil + (isflat && s->type == CHF ? vdelta[0] / 4.0f : 0); + if(isflat || (downwards && s->type == CHF) || (!downwards && s->type == FHF)) + { // intersect with z == const plane on either SPACE or flat ends of FHF and CHF + float tz = d.z != 0.0f ? (h - from.z) / d.z : distmin; + vec pz = d; + pz.mul(tz).add(from); + if(int(pz.x) == x && int(pz.y) == y && tz < distmin) to = pz, distmin = tz; + } + if(!isflat) + { // sloped CHF or FHF: check two triangles, regardless of "downwards" or not (FHF slopes can be seen looking upwards) + if(s->type == FHF) loopi(4) vdelta[i] *= -1.0f; + float hh = s->type == FHF ? s->floor : s->ceil; + vec v0(x, y, hh + vdelta[0] / 4.0f), v1(x + 1, y, hh + vdelta[1] / 4.0f), v2(x, y + 1, hh + vdelta[2] / 4.0f), v3(x + 1, y + 1, hh + vdelta[3] / 4.0f); + float newdist; vec newto; + if(s->type == FHF) + { + if(intersecttriangle(from, d, v0, v1, v2, &newto, &newdist) && newdist < distmin) to = newto, distmin = newdist; + if(intersecttriangle(from, d, v3, v1, v2, &newto, &newdist) && newdist < distmin) to = newto, distmin = newdist; + } + else + { + if(intersecttriangle(from, d, v0, v1, v3, &newto, &newdist) && newdist < distmin) to = newto, distmin = newdist; + if(intersecttriangle(from, d, v2, v0, v3, &newto, &newdist) && newdist < distmin) to = newto, distmin = newdist; + } + } + } + } + } + ixy += step; + } + } +} + void damageeffect(int damage, playerent *d) { particle_splash(PART_BLOOD, damage/10, 1000, d->o);