From 981622f96901d30ce4b7c7892fc2be0a652bddf9 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Mon, 6 Dec 2010 15:30:56 +0900 Subject: [PATCH] Make MOD_TraceLine behave "correctly". MOD_TraceLine now behaves the same as id's SV_RecursiveHullCheck (from WinQuake). This means that even if the trace would escape from solid space into non-solid space, the trace is treated as allsolid if it crosses from one solid space to another before hitting the empty space. trace-id.c is used only for establishing the behaviour of id's code. --- .gitignore | 1 + libs/models/Makefile.am | 11 +- libs/models/testclip.c | 237 ++++++++++++++++++++++++++++++++++++++++ libs/models/trace-id.c | 155 ++++++++++++++++++++++++++ libs/models/trace.c | 54 +++++---- 5 files changed, 432 insertions(+), 26 deletions(-) create mode 100644 libs/models/testclip.c create mode 100644 libs/models/trace-id.c diff --git a/.gitignore b/.gitignore index b2e11ecb3..811823d1d 100644 --- a/.gitignore +++ b/.gitignore @@ -131,6 +131,7 @@ core # /libs/image/ # /libs/models/ +/libs/models/testclip # /libs/models/alias/ diff --git a/libs/models/Makefile.am b/libs/models/Makefile.am index f070844a8..c859568a5 100644 --- a/libs/models/Makefile.am +++ b/libs/models/Makefile.am @@ -4,9 +4,16 @@ SUBDIRS= alias brush sprite CFLAGS+= @PREFER_PIC@ INCLUDES= -I$(top_srcdir)/include +check_PROGRAMS=testclip + +testclip_SOURCES=testclip.c +testclip_LDADD= libQFmodels.la $(top_builddir)/libs/util/libQFutil.la +testclip_DEPENDENCIES= + lib_LTLIBRARIES= libQFmodels.la @VID_MODEL_TARGETS@ -EXTRA_LTLIBRARIES= \ - libQFmodels_gl.la libQFmodels_sw.la +EXTRA_LTLIBRARIES= libQFmodels_gl.la libQFmodels_sw.la +EXTRA_DIST= trace-id.c +TESTS=$(check_PROGRAMS) models_sources = clip_hull.c model.c trace.c diff --git a/libs/models/testclip.c b/libs/models/testclip.c new file mode 100644 index 000000000..b8ff79afa --- /dev/null +++ b/libs/models/testclip.c @@ -0,0 +1,237 @@ +#include +#include "QF/va.h" + +#include "getopt.h" +#include "world.h" + +#undef DIST_EPSILON +#define DIST_EPSILON 0 + +#ifdef TEST_ID +# include "trace-id.c" +#else +# include "trace.c" +#endif + +mclipnode_t clipnodes0[] = { + { 0, { 1, -1}}, + { 1, {-1, -2}}, +}; + +mplane_t planes0[] = { + {{1, 0, 0}, 0, 0, 0}, // 0 + {{0.8, 0, 0.6}, 0, 4, 0}, // 1 +}; + +hull_t hull0 = { + clipnodes0, + planes0, + 0, + 1, + {0, 0, 0}, + {0, 0, 0}, +}; + +mclipnode_t clipnodes1[] = { + { 0, { 1, -2}}, + { 1, { 2, -2}}, + { 2, {-2, -1}}, +}; + +mplane_t planes1[] = { + {{1, 0, 0}, -32, 0, 0}, + {{1, 0, 0}, 32, 0, 0}, + {{1, 0, 0}, 48, 0, 0}, +}; + +hull_t hull1 = { + clipnodes1, + planes1, + 0, + 2, + {0, 0, 0}, + {0, 0, 0}, +}; + +mclipnode_t clipnodes2[] = { + { 0, { 2, 1}}, + { 1, {-2, -2}}, + { 2, {-2, -1}}, +}; + +mplane_t planes2[] = { + {{1, 0, 0}, 32, 0, 0}, + {{1, 0, 0}, -32, 0, 0}, + {{1, 0, 0}, 48, 0, 0}, +}; + +hull_t hull2 = { + clipnodes2, + planes2, + 0, + 2, + {0, 0, 0}, + {0, 0, 0}, +}; + +typedef struct { + vec3_t extents; +} box_t; + +typedef struct { + const char *desc; + box_t *box; + hull_t *hull; + vec3_t start; + vec3_t end; + struct { + float frac; + qboolean allsolid; + qboolean startsolid; + qboolean inopen; + qboolean inwater; + } expect; +} test_t; + +box_t point = { {0, 0, 0} }; +box_t player = { {16, 16, 28} }; + +test_t tests[] = { + {0, &point, &hull1, {-64, 0, 0}, { 64, 0, 0}, { 1, 1, 1, 0, 0}}, + {0, &point, &hull1, { 0, 0, 0}, { 40, 0, 0}, { 1, 0, 1, 1, 0}}, + {0, &point, &hull1, { 40, 0, 0}, {-88, 0, 0}, {0.0625, 0, 0, 1, 0}}, + {0, &point, &hull1, { 0, 0, 0}, { 64, 0, 0}, { 0.75, 0, 1, 1, 0}}, + + {0, &point, &hull2, {-64, 0, 0}, { 64, 0, 0}, { 1, 1, 1, 0, 0}}, + {0, &point, &hull2, { 0, 0, 0}, { 40, 0, 0}, { 1, 0, 1, 1, 0}}, + {0, &point, &hull2, { 40, 0, 0}, {-88, 0, 0}, {0.0625, 0, 0, 1, 0}}, + {0, &point, &hull2, { 0, 0, 0}, { 64, 0, 0}, { 0.75, 0, 1, 1, 0}}, +}; +#define num_tests (sizeof (tests) / sizeof (tests[0])) + +static int test_enabled[num_tests] = { 0 }; + +int verbose = 0; + +static trace_t +do_trace (box_t *box, hull_t *hull, vec3_t start, vec3_t end) +{ + trace_t trace; + + trace.allsolid = true; + trace.startsolid = false; + trace.inopen = false; + trace.inwater = false; + trace.fraction = 1; + VectorCopy (box->extents, trace.extents); + trace.isbox = true; + VectorCopy (end, trace.endpos); + MOD_TraceLine (hull, 0, start, end, &trace); + return trace; +} + +static int +run_test (test_t *test) +{ + const char *desc; + vec3_t end; + int res = 0; + char *expect; + char *got; + static int output = 0; + + VectorSubtract (test->end, test->start, end); + VectorMultAdd (test->start, test->expect.frac, end, end); + expect = nva ("expect: (%g %g %g) -> (%g %g %g) => (%g %g %g)" + " %3g %d %d %d %d", + test->start[0], test->start[1], test->start[2], + test->end[0], test->end[1], test->end[2], + end[0], end[1], end[2], + test->expect.frac, + test->expect.allsolid, test->expect.startsolid, + test->expect.inopen, test->expect.inwater); + trace_t trace = do_trace (test->box, test->hull, test->start, test->end); + got = nva (" got: (%g %g %g) -> (%g %g %g) => (%g %g %g)" + " %3g %d %d %d %d", + test->start[0], test->start[1], test->start[2], + test->end[0], test->end[1], test->end[2], + trace.endpos[0], trace.endpos[1], trace.endpos[2], + trace.fraction, + trace.allsolid, trace.startsolid, + trace.inopen, trace.inwater); + if (VectorCompare (end, trace.endpos) + && test->expect.frac == trace.fraction + && test->expect.allsolid == trace.allsolid + && test->expect.startsolid == trace.startsolid + && test->expect.inopen == trace.inopen + && test->expect.inwater == trace.inwater) + res = 1; + + if (test->desc) + desc = va ("(%d) %s", (int)(long)(test - tests), test->desc); + else + desc = va ("test #%d", (int)(long)(test - tests)); + if (verbose >= 0 || !res) { + if (output) + puts(""); + output = 1; + puts (expect); + puts (got); + printf ("%s: %s\n", res ? "PASS" : "FAIL", desc); + } + free (expect); + free (got); + return res; +} + +int +main (int argc, char **argv) +{ +// vec3_t start, end; + int c; + size_t i, test; + int pass = 1; + + while ((c = getopt (argc, argv, "qvt:")) != EOF) { + switch (c) { + case 'q': + verbose--; + break; + case 'v': + verbose++; + break; + case 't': + test = atoi (optarg); + if (test < num_tests) { + test_enabled[test] = 1; + } else { + fprintf (stderr, "Bad test number (0 - %zd)\n", num_tests); + return 1; + } + break; + default: + fprintf (stderr, "-q (quiet) -v (verbose) and/or -t TEST " + "(test number)\n"); + return 1; + } + } + + for (i = 0; i < num_tests; i++) + if (test_enabled[i]) + break; + if (i == num_tests) { + for (i = 0; i < num_tests; i++) + test_enabled[i] = 1; + } + + if (verbose > 0) + printf ("start -> end => stop frac allsolid startsolid inopen " + "inwater\n"); + for (i = 0; i < num_tests; i++) { + if (!test_enabled[i]) + continue; + pass &= run_test (&tests[i]); + } + + return !pass; +} diff --git a/libs/models/trace-id.c b/libs/models/trace-id.c new file mode 100644 index 000000000..2e9898423 --- /dev/null +++ b/libs/models/trace-id.c @@ -0,0 +1,155 @@ +int SV_HullPointContents (hull_t *hull, int num, const vec3_t p) +{ + float d; + mclipnode_t *node; + mplane_t *plane; + + while (num >= 0) + { + if (num < hull->firstclipnode || num > hull->lastclipnode) + Sys_Error ("SV_HullPointContents: bad node number"); + + node = hull->clipnodes + num; + plane = hull->planes + node->planenum; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + return num; +} + +// 1/32 epsilon to keep floating point happy +#ifndef DIST_EPSILON +#define DIST_EPSILON (0.03125) +#endif + +qboolean SV_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, const vec3_t p1, const vec3_t p2, trace_t *trace) +{ + mclipnode_t *node; + mplane_t *plane; + float t1, t2; + float frac; + int i; + vec3_t mid; + int side; + float midf; + +// check for empty + if (num < 0) + { + if (num != CONTENTS_SOLID) + { + trace->allsolid = false; + if (num == CONTENTS_EMPTY) + trace->inopen = true; + else + trace->inwater = true; + } + else + trace->startsolid = true; + return true; // empty + } + + if (num < hull->firstclipnode || num > hull->lastclipnode) + Sys_Error ("SV_RecursiveHullCheck: bad node number"); + +// +// find the point distances +// + node = hull->clipnodes + num; + plane = hull->planes + node->planenum; + + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + } + + if (t1 >= 0 && t2 >= 0) + return SV_RecursiveHullCheck (hull, node->children[0], p1f, p2f, p1, p2, trace); + if (t1 < 0 && t2 < 0) + return SV_RecursiveHullCheck (hull, node->children[1], p1f, p2f, p1, p2, trace); + +// put the crosspoint DIST_EPSILON pixels on the near side + if (t1 < 0) + frac = (t1 + DIST_EPSILON)/(t1-t2); + else + frac = (t1 - DIST_EPSILON)/(t1-t2); + if (frac < 0) + frac = 0; + if (frac > 1) + frac = 1; + + midf = p1f + (p2f - p1f)*frac; + for (i=0 ; i<3 ; i++) + mid[i] = p1[i] + frac*(p2[i] - p1[i]); + + side = (t1 < 0); + +// move up to the node + if (!SV_RecursiveHullCheck (hull, node->children[side], p1f, midf, p1, mid, trace) ) + return false; + + if (SV_HullPointContents (hull, node->children[side^1], mid) + != CONTENTS_SOLID) +// go past the node + return SV_RecursiveHullCheck (hull, node->children[side^1], midf, p2f, mid, p2, trace); + + if (trace->allsolid) + return false; // never got out of the solid area + +//================== +// the other side of the node is solid, this is the impact point +//================== + if (!side) + { + VectorCopy (plane->normal, trace->plane.normal); + trace->plane.dist = plane->dist; + } + else + { + VectorSubtract (vec3_origin, plane->normal, trace->plane.normal); + trace->plane.dist = -plane->dist; + } + + while (SV_HullPointContents (hull, hull->firstclipnode, mid) + == CONTENTS_SOLID) + { // shouldn't really happen, but does occasionally + frac -= 0.1; + if (frac < 0) + { + trace->fraction = midf; + VectorCopy (mid, trace->endpos); + //Con_DPrintf ("backup past 0\n"); + return false; + } + midf = p1f + (p2f - p1f)*frac; + for (i=0 ; i<3 ; i++) + mid[i] = p1[i] + frac*(p2[i] - p1[i]); + } + + trace->fraction = midf; + VectorCopy (mid, trace->endpos); + + return false; +} + +void +MOD_TraceLine (hull_t *hull, int num, + const vec3_t start_point, const vec3_t end_point, + trace_t *trace) +{ + SV_RecursiveHullCheck (hull, num, 0, 1, start_point, end_point, trace); +} diff --git a/libs/models/trace.c b/libs/models/trace.c index 49bc7abc2..fbff60d7c 100644 --- a/libs/models/trace.c +++ b/libs/models/trace.c @@ -50,7 +50,9 @@ static __attribute__ ((used)) const char rcsid[] = /* LINE TESTING IN HULLS */ // 1/32 epsilon to keep floating point happy +#ifndef DIST_EPSILON #define DIST_EPSILON (0.03125) +#endif typedef struct { vec3_t end; @@ -92,7 +94,8 @@ MOD_TraceLine (hull_t *hull, int num, { vec_t start_dist, end_dist, frac; vec3_t start, end, dist; - int side, empty, solid; + int side; + qboolean empty, solid; tracestack_t *tstack; tracestack_t tracestack[256]; mclipnode_t *node; @@ -106,40 +109,41 @@ MOD_TraceLine (hull_t *hull, int num, solid = 0; split_plane = 0; + trace->allsolid = false; + trace->startsolid = false; + trace->inopen = false; + trace->inwater = false; + trace->fraction = 1.0; + while (1) { while (num < 0) { - if (!solid && num != CONTENTS_SOLID) { - empty = 1; + if (num == CONTENTS_SOLID) { + if (!empty && !solid) { + // this is the first leaf visited, thus the start leaf + trace->startsolid = solid = true; + } else if (solid) { + // if crossing from one solid leaf to another, treat the + // whole trace as solid (this is what id does) + trace->allsolid = true; + return; + } else if (empty) { + // crossing from an empty leaf to a solid leaf: the trace + // has collided. + calc_impact (trace, start_point, end_point, split_plane); + return; + } + } else { + empty = true; + solid = false; if (num == CONTENTS_EMPTY) trace->inopen = true; else trace->inwater = true; - } else if (!empty && num == CONTENTS_SOLID) { - solid = 1; - } else if (solid && num != CONTENTS_SOLID) { - //FIXME not sure what I want - //made it out of the solid and into open space, continue - //on as if we were always in empty space - empty = 1; - solid = 0; - trace->startsolid = 1; - if (num == CONTENTS_EMPTY) - trace->inopen = true; - else - trace->inwater = true; - } else if (empty/* || solid*/) {//FIXME not sure what I want - // DONE! - trace->allsolid = solid & (num == CONTENTS_SOLID); - trace->startsolid = solid; - calc_impact (trace, start_point, end_point, split_plane); - return; } // pop up the stack for a back side if (tstack-- == tracestack) { // we've finished. - trace->allsolid = solid & (num == CONTENTS_SOLID); - trace->startsolid = solid; return; } @@ -171,6 +175,8 @@ MOD_TraceLine (hull_t *hull, int num, continue; } + // cross the plane + side = start_dist < 0; frac = start_dist / (start_dist - end_dist); frac = bound (0, frac, 1);