]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - collision.c
globally rename model_t to dp_model_t for OpenSolaris. Sorry, guys who now have svn...
[xonotic/darkplaces.git] / collision.c
index ca95a54b1487eb45f916811bc0c095c3f13b4cf8..73a1bd0a5b374fa1d2c1a847d0150242c8ffae74 100644 (file)
@@ -4,13 +4,15 @@
 
 #define COLLISION_SNAPSCALE (32.0f)
 #define COLLISION_SNAP (1.0f / COLLISION_SNAPSCALE)
-#define COLLISION_PLANE_DIST_EPSILON (1.0f / 32.0f)
+#define COLLISION_SNAP2 (2.0f / COLLISION_SNAPSCALE)
+#define COLLISION_PLANE_DIST_EPSILON (2.0f / COLLISION_SNAPSCALE)
 
 cvar_t collision_impactnudge = {0, "collision_impactnudge", "0.03125", "how much to back off from the impact"};
 cvar_t collision_startnudge = {0, "collision_startnudge", "0", "how much to bias collision trace start"};
 cvar_t collision_endnudge = {0, "collision_endnudge", "0", "how much to bias collision trace end"};
 cvar_t collision_enternudge = {0, "collision_enternudge", "0", "how much to bias collision entry fraction"};
 cvar_t collision_leavenudge = {0, "collision_leavenudge", "0", "how much to bias collision exit fraction"};
+cvar_t collision_prefernudgedfraction = {0, "collision_prefernudgedfraction", "1", "whether to sort collision events by nudged fraction (1) or real fraction (0)"};
 
 void Collision_Init (void)
 {
@@ -19,6 +21,7 @@ void Collision_Init (void)
        Cvar_RegisterVariable(&collision_endnudge);
        Cvar_RegisterVariable(&collision_enternudge);
        Cvar_RegisterVariable(&collision_leavenudge);
+       Cvar_RegisterVariable(&collision_prefernudgedfraction);
 }
 
 
@@ -108,6 +111,8 @@ void Collision_ValidateBrush(colbrushf_t *brush)
 float nearestplanedist_float(const float *normal, const colpointf_t *points, int numpoints)
 {
        float dist, bestdist;
+       if (!numpoints)
+               return 0;
        bestdist = DotProduct(points->v, normal);
        points++;
        while(--numpoints)
@@ -122,6 +127,8 @@ float nearestplanedist_float(const float *normal, const colpointf_t *points, int
 float furthestplanedist_float(const float *normal, const colpointf_t *points, int numpoints)
 {
        float dist, bestdist;
+       if (!numpoints)
+               return 0;
        bestdist = DotProduct(points->v, normal);
        points++;
        while(--numpoints)
@@ -137,7 +144,7 @@ float furthestplanedist_float(const float *normal, const colpointf_t *points, in
 colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalplanes, const colplanef_t *originalplanes, int supercontents)
 {
        // TODO: planesbuf could be replaced by a remapping table
-       int j, k, m, w;
+       int j, k, m, w, xyzflags;
        int numpointsbuf = 0, maxpointsbuf = 256, numplanesbuf = 0, maxplanesbuf = 256, numelementsbuf = 0, maxelementsbuf = 256;
        double maxdist;
        colbrushf_t *brush;
@@ -159,9 +166,9 @@ colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalpla
        // figure out how large a bounding box we need to properly compute this brush
        maxdist = 0;
        for (j = 0;j < numoriginalplanes;j++)
-               maxdist = max(maxdist, originalplanes[j].dist);
+               maxdist = max(maxdist, fabs(originalplanes[j].dist));
        // now make it large enough to enclose the entire brush, and round it off to a reasonable multiple of 1024
-       maxdist = floor(maxdist * (4.0 / 1024.0) + 1) * 1024.0;
+       maxdist = floor(maxdist * (4.0 / 1024.0) + 2) * 1024.0;
        // construct a collision brush (points, planes, and renderable mesh) from
        // a set of planes, this also optimizes out any unnecessary planes (ones
        // whose polygon is clipped away by the other planes)
@@ -177,17 +184,26 @@ colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalpla
                // check if there are too many and skip the brush
                if (numplanesbuf >= maxplanesbuf)
                {
-                       Con_Print("Collision_NewBrushFromPlanes: failed to build collision brush: too many planes for buffer\n");
+                       Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many planes for buffer\n");
                        return NULL;
                }
 
+               // add the new plane
+               VectorCopy(originalplanes[j].normal, planesbuf[numplanesbuf].normal);
+               planesbuf[numplanesbuf].dist = originalplanes[j].dist;
+               planesbuf[numplanesbuf].q3surfaceflags = originalplanes[j].q3surfaceflags;
+               planesbuf[numplanesbuf].texture = originalplanes[j].texture;
+               numplanesbuf++;
+
                // create a large polygon from the plane
                w = 0;
                PolygonD_QuadForPlane(p[w], originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist, maxdist);
                pnumpoints = 4;
                // clip it by all other planes
-               for (k = 0;k < numoriginalplanes && pnumpoints && pnumpoints <= pmaxpoints;k++)
+               for (k = 0;k < numoriginalplanes && pnumpoints >= 3 && pnumpoints <= pmaxpoints;k++)
                {
+                       // skip the plane this polygon
+                       // (nothing happens if it is processed, this is just an optimization)
                        if (k != j)
                        {
                                // we want to keep the inside of the brush plane so we flip
@@ -196,10 +212,11 @@ colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalpla
                                w = !w;
                        }
                }
+
                // if nothing is left, skip it
                if (pnumpoints < 3)
                {
-                       //Con_Printf("Collision_NewBrushFromPlanes: warning: polygon for plane %f %f %f %f clipped away\n", originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist);
+                       //Con_DPrintf("Collision_NewBrushFromPlanes: warning: polygon for plane %f %f %f %f clipped away\n", originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist);
                        continue;
                }
 
@@ -215,29 +232,33 @@ colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalpla
                }
                if (k < pnumpoints)
                {
-                       Con_Printf("Collision_NewBrushFromPlanes: warning: polygon point does not lie on at least 3 planes\n");
+                       Con_DPrintf("Collision_NewBrushFromPlanes: warning: polygon point does not lie on at least 3 planes\n");
                        //return NULL;
                }
 
                // check if there are too many polygon vertices for buffer
                if (pnumpoints > pmaxpoints)
                {
-                       Con_Print("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n");
+                       Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n");
                        return NULL;
                }
 
                // check if there are too many triangle elements for buffer
                if (numelementsbuf + (pnumpoints - 2) * 3 > maxelementsbuf)
                {
-                       Con_Print("Collision_NewBrushFromPlanes: failed to build collision brush: too many triangle elements for buffer\n");
+                       Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many triangle elements for buffer\n");
                        return NULL;
                }
 
                for (k = 0;k < pnumpoints;k++)
                {
+                       float v[3];
+                       // downgrade to float precision before comparing
+                       VectorCopy(&p[w][k*3], v);
+
                        // check if there is already a matching point (no duplicates)
                        for (m = 0;m < numpointsbuf;m++)
-                               if (VectorDistance2(&p[w][k*3], pointsbuf[m].v) < COLLISION_SNAP)
+                               if (VectorDistance2(v, pointsbuf[m].v) < COLLISION_SNAP2)
                                        break;
 
                        // if there is no match, add a new one
@@ -246,7 +267,7 @@ colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalpla
                                // check if there are too many and skip the brush
                                if (numpointsbuf >= maxpointsbuf)
                                {
-                                       Con_Print("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n");
+                                       Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n");
                                        return NULL;
                                }
                                // add the new one
@@ -266,28 +287,25 @@ colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalpla
                        elementsbuf[numelementsbuf++] = polypointbuf[k + 1];
                        elementsbuf[numelementsbuf++] = polypointbuf[k + 2];
                }
+       }
 
-               // add the new plane
-               VectorCopy(originalplanes[j].normal, planesbuf[numplanesbuf].normal);
-               planesbuf[numplanesbuf].dist = originalplanes[j].dist;
-               planesbuf[numplanesbuf].q3surfaceflags = originalplanes[j].q3surfaceflags;
-               planesbuf[numplanesbuf].texture = originalplanes[j].texture;
-               numplanesbuf++;
+       // if nothing is left, there's nothing to allocate
+       if (numplanesbuf < 4)
+       {
+               Con_DPrintf("Collision_NewBrushFromPlanes: failed to build collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf);
+               return NULL;
        }
 
+       // if no triangles or points could be constructed, then this routine failed but the brush is not discarded
+       if (numelementsbuf < 12 || numpointsbuf < 4)
+               Con_DPrintf("Collision_NewBrushFromPlanes: unable to rebuild triangles/points for collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf);
+
        // validate plane distances
        for (j = 0;j < numplanesbuf;j++)
        {
                float d = furthestplanedist_float(planesbuf[j].normal, pointsbuf, numpointsbuf);
                if (fabs(planesbuf[j].dist - d) > COLLISION_PLANE_DIST_EPSILON)
-                       Con_Printf("plane %f %f %f %f mismatches dist %f\n", planesbuf[j].normal[0], planesbuf[j].normal[1], planesbuf[j].normal[2], planesbuf[j].dist, d);
-       }
-
-       // if nothing is left, there's nothing to allocate
-       if (numelementsbuf < 12 || numplanesbuf < 4 || numpointsbuf < 4)
-       {
-               Con_Printf("Collision_NewBrushFromPlanes: failed to build collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf);
-               return NULL;
+                       Con_DPrintf("plane %f %f %f %f mismatches dist %f\n", planesbuf[j].normal[0], planesbuf[j].normal[1], planesbuf[j].normal[2], planesbuf[j].dist, d);
        }
 
        // allocate the brush and copy to it
@@ -316,16 +334,34 @@ colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalpla
        }
        for (j = 0;j < brush->numtriangles * 3;j++)
                brush->elements[j] = elementsbuf[j];
-       VectorCopy(brush->points[0].v, brush->mins);
-       VectorCopy(brush->points[0].v, brush->maxs);
-       for (j = 1;j < brush->numpoints;j++)
+
+       xyzflags = 0;
+       VectorClear(brush->mins);
+       VectorClear(brush->maxs);
+       for (j = 0;j < min(6, numoriginalplanes);j++)
        {
-               brush->mins[0] = min(brush->mins[0], brush->points[j].v[0]);
-               brush->mins[1] = min(brush->mins[1], brush->points[j].v[1]);
-               brush->mins[2] = min(brush->mins[2], brush->points[j].v[2]);
-               brush->maxs[0] = max(brush->maxs[0], brush->points[j].v[0]);
-               brush->maxs[1] = max(brush->maxs[1], brush->points[j].v[1]);
-               brush->maxs[2] = max(brush->maxs[2], brush->points[j].v[2]);
+                    if (originalplanes[j].normal[0] ==  1) {xyzflags |=  1;brush->maxs[0] =  originalplanes[j].dist;}
+               else if (originalplanes[j].normal[0] == -1) {xyzflags |=  2;brush->mins[0] = -originalplanes[j].dist;}
+               else if (originalplanes[j].normal[1] ==  1) {xyzflags |=  4;brush->maxs[1] =  originalplanes[j].dist;}
+               else if (originalplanes[j].normal[1] == -1) {xyzflags |=  8;brush->mins[1] = -originalplanes[j].dist;}
+               else if (originalplanes[j].normal[2] ==  1) {xyzflags |= 16;brush->maxs[2] =  originalplanes[j].dist;}
+               else if (originalplanes[j].normal[2] == -1) {xyzflags |= 32;brush->mins[2] = -originalplanes[j].dist;}
+       }
+       // if not all xyzflags were set, then this is not a brush from q3map/q3map2, and needs reconstruction of the bounding box
+       // (this case works for any brush with valid points, but sometimes brushes are not reconstructed properly and hence the points are not valid, so this is reserved as a fallback case)
+       if (xyzflags != 63)
+       {
+               VectorCopy(brush->points[0].v, brush->mins);
+               VectorCopy(brush->points[0].v, brush->maxs);
+               for (j = 1;j < brush->numpoints;j++)
+               {
+                       brush->mins[0] = min(brush->mins[0], brush->points[j].v[0]);
+                       brush->mins[1] = min(brush->mins[1], brush->points[j].v[1]);
+                       brush->mins[2] = min(brush->mins[2], brush->points[j].v[2]);
+                       brush->maxs[0] = max(brush->maxs[0], brush->points[j].v[0]);
+                       brush->maxs[1] = max(brush->maxs[1], brush->points[j].v[1]);
+                       brush->maxs[2] = max(brush->maxs[2], brush->points[j].v[2]);
+               }
        }
        brush->mins[0] -= 1;
        brush->mins[1] -= 1;
@@ -524,7 +560,7 @@ colbrushf_t *Collision_AllocBrushFromPermanentPolygonFloat(mempool_t *mempool, i
 // NOTE: start and end of each brush pair must have same numplanes/numpoints
 void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const colbrushf_t *thatbrush_start, const colbrushf_t *thatbrush_end)
 {
-       int nplane, nplane2, fstartsolid = true, fendsolid = true, brushsolid, hitq3surfaceflags = 0;
+       int nplane, nplane2, hitq3surfaceflags = 0;
        float enterfrac = -1, leavefrac = 1, d1, d2, f, imove, newimpactnormal[3], enterfrac2 = -1;
        const colplanef_t *startplane, *endplane;
        texture_t *hittexture = NULL;
@@ -575,21 +611,34 @@ void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *thisbrush
                }
                //Con_Printf("%c%i: d1 = %f, d2 = %f, d1 / (d1 - d2) = %f\n", nplane2 != nplane ? 'b' : 'a', nplane2, d1, d2, d1 / (d1 - d2));
 
-               if(d1 > d2)
+               if (d1 > d2)
                {
                        // moving into brush
-                       if(d2 > 0)
+                       if (d2 >= collision_enternudge.value)
                                return;
-                       if(d1 > 0)
+                       if (d1 > 0)
                        {
                                // enter
-                               fstartsolid = false;
                                imove = 1 / (d1 - d2);
                                f = (d1 - collision_enternudge.value) * imove;
+                               if (f < 0)
+                                       f = 0;
+                               // check if this will reduce the collision time range
                                if (enterfrac < f)
                                {
+                                       // reduced collision time range
                                        enterfrac = f;
-                                       enterfrac2 = f - collision_impactnudge.value * imove;
+                                       // if the collision time range is now empty, no collision
+                                       if (enterfrac > leavefrac)
+                                               return;
+                                       // if the collision would be further away than the trace's
+                                       // existing collision data, we don't care about this
+                                       // collision
+                                       if (enterfrac > trace->realfraction)
+                                               return;
+                                       // calculate the nudged fraction and impact normal we'll
+                                       // need if we accept this collision later
+                                       enterfrac2 = (d1 - collision_impactnudge.value) * imove;
                                        VectorLerp(startplane->normal, enterfrac, endplane->normal, newimpactnormal);
                                        hitq3surfaceflags = startplane->q3surfaceflags;
                                        hittexture = startplane->texture;
@@ -599,77 +648,64 @@ void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *thisbrush
                else
                {
                        // moving out of brush
-                       if(d1 > 0)
+                       if (d1 > 0)
                                return;
-                       if(d2 > 0)
+                       if (d2 > 0)
                        {
                                // leave
-                               fendsolid = false;
                                f = (d1 + collision_leavenudge.value) / (d1 - d2);
+                               if (f > 1)
+                                       f = 1;
+                               // check if this will reduce the collision time range
                                if (leavefrac > f)
+                               {
+                                       // reduced collision time range
                                        leavefrac = f;
+                                       // if the collision time range is now empty, no collision
+                                       if (enterfrac > leavefrac)
+                                               return;
+                               }
                        }
                }
        }
 
-       brushsolid = trace->hitsupercontentsmask & thatbrush_start->supercontents;
-       if (fstartsolid)
-       {
-               trace->startsupercontents |= thatbrush_start->supercontents;
-               if (brushsolid)
-               {
-                       trace->startsolid = true;
-                       if (fendsolid)
-                               trace->allsolid = true;
-               }
-       }
+       // at this point we know the trace overlaps the brush because it was not
+       // rejected at any point in the loop above
 
-       // LordHavoc: we need an epsilon nudge here because for a point trace the
-       // penetrating line segment is normally zero length if this brush was
-       // generated from a polygon (infinitely thin), and could even be slightly
-       // positive or negative due to rounding errors in that case.
-       if (brushsolid && enterfrac > -1 && enterfrac < trace->realfraction && enterfrac - (1.0f / 1024.0f) <= leavefrac)
+       // see if the trace started outside the brush or not
+       if (enterfrac > -1)
        {
-#if 0
-               // broken
-               if (thatbrush_start->ispolygon)
-               {
-                       d1 = nearestplanedist_float(thatbrush_start->planes[0].normal, thisbrush_start->points, thisbrush_start->numpoints) - thatbrush_start->planes[0].dist - collision_startnudge.value;
-                       d2 = nearestplanedist_float(thatbrush_end->planes[0].normal, thisbrush_end->points, thisbrush_end->numpoints) - thatbrush_end->planes[0].dist - collision_endnudge.value;
-                       move = d1 - d2;
-                       if (move <= 0 || d2 > collision_enternudge.value || d1 < 0)
-                               return;
-                       // enter
-                       imove = 1 / move;
-                       enterfrac = (d1 - collision_enternudge.value) * imove;
-                       if (enterfrac < trace->realfraction)
-                       {
-                               enterfrac2 = enterfrac - collision_impactnudge.value * imove;
-                               trace->hitsupercontents = thatbrush_start->supercontents;
-                               trace->hitq3surfaceflags = thatbrush_start->planes[0].q3surfaceflags;
-                               trace->hittexture = thatbrush_start->planes[0].texture;
-                               trace->realfraction = bound(0, enterfrac, 1);
-                               trace->fraction = bound(0, enterfrac2, 1);
-                               VectorLerp(thatbrush_start->planes[0].normal, enterfrac, thatbrush_end->planes[0].normal, trace->plane.normal);
-                       }
-               }
-               else
-#endif
+               // started outside, and overlaps, therefore there is a collision here
+               // store out the impact information
+               if (trace->hitsupercontentsmask & thatbrush_start->supercontents)
                {
                        trace->hitsupercontents = thatbrush_start->supercontents;
                        trace->hitq3surfaceflags = hitq3surfaceflags;
                        trace->hittexture = hittexture;
                        trace->realfraction = bound(0, enterfrac, 1);
                        trace->fraction = bound(0, enterfrac2, 1);
+                       if (collision_prefernudgedfraction.integer)
+                               trace->realfraction = trace->fraction;
                        VectorCopy(newimpactnormal, trace->plane.normal);
                }
        }
+       else
+       {
+               // started inside, update startsolid and friends
+               trace->startsupercontents |= thatbrush_start->supercontents;
+               if (trace->hitsupercontentsmask & thatbrush_start->supercontents)
+               {
+                       trace->startsolid = true;
+                       if (leavefrac < 1)
+                               trace->allsolid = true;
+               }
+       }
 }
 
 // NOTE: start and end brush pair must have same numplanes/numpoints
 void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const colbrushf_t *thatbrush_start, const colbrushf_t *thatbrush_end)
 {
-       int nplane, fstartsolid = true, fendsolid = true, brushsolid, hitq3surfaceflags = 0;
+       int nplane, hitq3surfaceflags = 0;
        float enterfrac = -1, leavefrac = 1, d1, d2, f, imove, newimpactnormal[3], enterfrac2 = -1;
        const colplanef_t *startplane, *endplane;
        texture_t *hittexture = NULL;
@@ -701,18 +737,31 @@ void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const
                if (d1 > d2)
                {
                        // moving into brush
-                       if (d2 > 0)
+                       if (d2 >= collision_enternudge.value)
                                return;
                        if (d1 > 0)
                        {
                                // enter
-                               fstartsolid = false;
                                imove = 1 / (d1 - d2);
                                f = (d1 - collision_enternudge.value) * imove;
+                               if (f < 0)
+                                       f = 0;
+                               // check if this will reduce the collision time range
                                if (enterfrac < f)
                                {
+                                       // reduced collision time range
                                        enterfrac = f;
-                                       enterfrac2 = f - collision_impactnudge.value * imove;
+                                       // if the collision time range is now empty, no collision
+                                       if (enterfrac > leavefrac)
+                                               return;
+                                       // if the collision would be further away than the trace's
+                                       // existing collision data, we don't care about this
+                                       // collision
+                                       if (enterfrac > trace->realfraction)
+                                               return;
+                                       // calculate the nudged fraction and impact normal we'll
+                                       // need if we accept this collision later
+                                       enterfrac2 = (d1 - collision_impactnudge.value) * imove;
                                        VectorLerp(startplane->normal, enterfrac, endplane->normal, newimpactnormal);
                                        hitq3surfaceflags = startplane->q3surfaceflags;
                                        hittexture = startplane->texture;
@@ -727,76 +776,70 @@ void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const
                        if (d2 > 0)
                        {
                                // leave
-                               fendsolid = false;
                                f = (d1 + collision_leavenudge.value) / (d1 - d2);
+                               // check if this will reduce the collision time range
                                if (leavefrac > f)
+                               {
+                                       // reduced collision time range
                                        leavefrac = f;
+                                       // if the collision time range is now empty, no collision
+                                       if (enterfrac > leavefrac)
+                                               return;
+                               }
                        }
                }
        }
 
-       brushsolid = trace->hitsupercontentsmask & thatbrush_start->supercontents;
-       if (fstartsolid)
-       {
-               trace->startsupercontents |= thatbrush_start->supercontents;
-               if (brushsolid)
-               {
-                       trace->startsolid = true;
-                       if (fendsolid)
-                               trace->allsolid = true;
-               }
-       }
+       // at this point we know the trace overlaps the brush because it was not
+       // rejected at any point in the loop above
 
-       // LordHavoc: we need an epsilon nudge here because for a point trace the
-       // penetrating line segment is normally zero length if this brush was
-       // generated from a polygon (infinitely thin), and could even be slightly
-       // positive or negative due to rounding errors in that case.
-       if (brushsolid && enterfrac > -1 && enterfrac < trace->realfraction && enterfrac <= leavefrac)
+       // see if the trace started outside the brush or not
+       if (enterfrac > -1)
        {
-#if 0
-               // broken
-               if (thatbrush_start->ispolygon)
-               {
-                       d1 = DotProduct(thatbrush_start->planes[0].normal, linestart) - thatbrush_start->planes[0].dist - collision_startnudge.value;
-                       d2 = DotProduct(thatbrush_end->planes[0].normal, lineend) - thatbrush_end->planes[0].dist - collision_endnudge.value;
-                       move = d1 - d2;
-                       if (move <= 0 || d2 > collision_enternudge.value || d1 < 0)
-                               return;
-                       // enter
-                       imove = 1 / move;
-                       enterfrac = (d1 - collision_enternudge.value) * imove;
-                       if (enterfrac < trace->realfraction)
-                       {
-                               enterfrac2 = enterfrac - collision_impactnudge.value * imove;
-                               trace->hitsupercontents = thatbrush_start->supercontents;
-                               trace->hitq3surfaceflags = hitq3surfaceflags;
-                               trace->hittexture = hittexture;
-                               trace->realfraction = bound(0, enterfrac, 1);
-                               trace->fraction = bound(0, enterfrac2, 1);
-                               VectorLerp(thatbrush_start->planes[0].normal, enterfrac, thatbrush_end->planes[0].normal, trace->plane.normal);
-                       }
-               }
-               else
-#endif
+               // started outside, and overlaps, therefore there is a collision here
+               // store out the impact information
+               if (trace->hitsupercontentsmask & thatbrush_start->supercontents)
                {
                        trace->hitsupercontents = thatbrush_start->supercontents;
                        trace->hitq3surfaceflags = hitq3surfaceflags;
                        trace->hittexture = hittexture;
                        trace->realfraction = bound(0, enterfrac, 1);
                        trace->fraction = bound(0, enterfrac2, 1);
+                       if (collision_prefernudgedfraction.integer)
+                               trace->realfraction = trace->fraction;
                        VectorCopy(newimpactnormal, trace->plane.normal);
                }
        }
+       else
+       {
+               // started inside, update startsolid and friends
+               trace->startsupercontents |= thatbrush_start->supercontents;
+               if (trace->hitsupercontentsmask & thatbrush_start->supercontents)
+               {
+                       trace->startsolid = true;
+                       if (leavefrac < 1)
+                               trace->allsolid = true;
+               }
+       }
 }
 
-void Collision_TracePointBrushFloat(trace_t *trace, const vec3_t point, const colbrushf_t *thatbrush)
+qboolean Collision_PointInsideBrushFloat(const vec3_t point, const colbrushf_t *brush)
 {
        int nplane;
        const colplanef_t *plane;
 
-       for (nplane = 0, plane = thatbrush->planes;nplane < thatbrush->numplanes;nplane++, plane++)
+       if (!BoxesOverlap(point, point, brush->mins, brush->maxs))
+               return false;
+       for (nplane = 0, plane = brush->planes;nplane < brush->numplanes;nplane++, plane++)
                if (DotProduct(plane->normal, point) > plane->dist)
-                       return;
+                       return false;
+       return true;
+}
+
+void Collision_TracePointBrushFloat(trace_t *trace, const vec3_t point, const colbrushf_t *thatbrush)
+{
+       if (!Collision_PointInsideBrushFloat(point, thatbrush))
+               return;
 
        trace->startsupercontents |= thatbrush->supercontents;
        if (trace->hitsupercontentsmask & thatbrush->supercontents)
@@ -812,11 +855,12 @@ static colbrushf_t polyf_brush;
 
 void Collision_SnapCopyPoints(int numpoints, const colpointf_t *in, colpointf_t *out, float fractionprecision, float invfractionprecision)
 {
-       while (numpoints--)
+       int i;
+       for (i = 0;i < numpoints;i++)
        {
-               out->v[0] = floor(in->v[0] * fractionprecision + 0.5f) * invfractionprecision;
-               out->v[1] = floor(in->v[1] * fractionprecision + 0.5f) * invfractionprecision;
-               out->v[2] = floor(in->v[2] * fractionprecision + 0.5f) * invfractionprecision;
+               out[i].v[0] = floor(in[i].v[0] * fractionprecision + 0.5f) * invfractionprecision;
+               out[i].v[1] = floor(in[i].v[1] * fractionprecision + 0.5f) * invfractionprecision;
+               out[i].v[2] = floor(in[i].v[2] * fractionprecision + 0.5f) * invfractionprecision;
        }
 }
 
@@ -833,7 +877,7 @@ void Collision_TraceBrushPolygonFloat(trace_t *trace, const colbrushf_t *thisbru
        polyf_brush.planes = polyf_planes;
        polyf_brush.supercontents = supercontents;
        polyf_brush.points = polyf_points;
-       Collision_SnapCopyPoints(numpoints, (colpointf_t *)points, polyf_points, COLLISION_SNAPSCALE, COLLISION_SNAP);
+       Collision_SnapCopyPoints(polyf_brush.numpoints, (colpointf_t *)points, polyf_points, COLLISION_SNAPSCALE, COLLISION_SNAP);
        Collision_CalcPlanesForPolygonBrushFloat(&polyf_brush);
        //Collision_PrintBrushAsQHull(&polyf_brush, "polyf_brush");
        Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &polyf_brush, &polyf_brush);
@@ -842,7 +886,6 @@ void Collision_TraceBrushPolygonFloat(trace_t *trace, const colbrushf_t *thisbru
 void Collision_TraceBrushTriangleMeshFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int numtriangles, const int *element3i, const float *vertex3f, int supercontents, int q3surfaceflags, texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs)
 {
        int i;
-       float facemins[3], facemaxs[3];
        polyf_brush.numpoints = 3;
        polyf_brush.numplanes = 5;
        polyf_brush.points = polyf_points;
@@ -855,18 +898,12 @@ void Collision_TraceBrushTriangleMeshFloat(trace_t *trace, const colbrushf_t *th
        }
        for (i = 0;i < numtriangles;i++, element3i += 3)
        {
-               VectorCopy(vertex3f + element3i[0] * 3, polyf_points[0].v);
-               VectorCopy(vertex3f + element3i[1] * 3, polyf_points[1].v);
-               VectorCopy(vertex3f + element3i[2] * 3, polyf_points[2].v);
-               Collision_SnapCopyPoints(3, polyf_points, polyf_points, COLLISION_SNAPSCALE, COLLISION_SNAP);
-               facemins[0] = min(polyf_points[0].v[0], min(polyf_points[1].v[0], polyf_points[2].v[0]));
-               facemins[1] = min(polyf_points[0].v[1], min(polyf_points[1].v[1], polyf_points[2].v[1]));
-               facemins[2] = min(polyf_points[0].v[2], min(polyf_points[1].v[2], polyf_points[2].v[2]));
-               facemaxs[0] = max(polyf_points[0].v[0], max(polyf_points[1].v[0], polyf_points[2].v[0]));
-               facemaxs[1] = max(polyf_points[0].v[1], max(polyf_points[1].v[1], polyf_points[2].v[1]));
-               facemaxs[2] = max(polyf_points[0].v[2], max(polyf_points[1].v[2], polyf_points[2].v[2]));
-               if (BoxesOverlap(segmentmins, segmentmaxs, facemins, facemaxs))
+               if (TriangleOverlapsBox(vertex3f + element3i[0]*3, vertex3f + element3i[1]*3, vertex3f + element3i[2]*3, segmentmins, segmentmaxs))
                {
+                       VectorCopy(vertex3f + element3i[0] * 3, polyf_points[0].v);
+                       VectorCopy(vertex3f + element3i[1] * 3, polyf_points[1].v);
+                       VectorCopy(vertex3f + element3i[2] * 3, polyf_points[2].v);
+                       Collision_SnapCopyPoints(polyf_brush.numpoints, polyf_points, polyf_points, COLLISION_SNAPSCALE, COLLISION_SNAP);
                        Collision_CalcPlanesForPolygonBrushFloat(&polyf_brush);
                        //Collision_PrintBrushAsQHull(&polyf_brush, "polyf_brush");
                        Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &polyf_brush, &polyf_brush);
@@ -885,7 +922,7 @@ void Collision_TraceLinePolygonFloat(trace_t *trace, const vec3_t linestart, con
        polyf_brush.numplanes = numpoints + 2;
        //polyf_brush.points = (colpointf_t *)points;
        polyf_brush.points = polyf_points;
-       Collision_SnapCopyPoints(numpoints, (colpointf_t *)points, polyf_points, COLLISION_SNAPSCALE, COLLISION_SNAP);
+       Collision_SnapCopyPoints(polyf_brush.numpoints, (colpointf_t *)points, polyf_points, COLLISION_SNAPSCALE, COLLISION_SNAP);
        polyf_brush.planes = polyf_planes;
        polyf_brush.supercontents = supercontents;
        Collision_CalcPlanesForPolygonBrushFloat(&polyf_brush);
@@ -914,19 +951,12 @@ void Collision_TraceLineTriangleMeshFloat(trace_t *trace, const vec3_t linestart
        }
        for (i = 0;i < numtriangles;i++, element3i += 3)
        {
-               float facemins[3], facemaxs[3];
-               VectorCopy(vertex3f + element3i[0] * 3, polyf_points[0].v);
-               VectorCopy(vertex3f + element3i[1] * 3, polyf_points[1].v);
-               VectorCopy(vertex3f + element3i[2] * 3, polyf_points[2].v);
-               Collision_SnapCopyPoints(numpoints, polyf_points, polyf_points, COLLISION_SNAPSCALE, COLLISION_SNAP);
-               facemins[0] = min(polyf_points[0].v[0], min(polyf_points[1].v[0], polyf_points[2].v[0]));
-               facemins[1] = min(polyf_points[0].v[1], min(polyf_points[1].v[1], polyf_points[2].v[1]));
-               facemins[2] = min(polyf_points[0].v[2], min(polyf_points[1].v[2], polyf_points[2].v[2]));
-               facemaxs[0] = max(polyf_points[0].v[0], max(polyf_points[1].v[0], polyf_points[2].v[0]));
-               facemaxs[1] = max(polyf_points[0].v[1], max(polyf_points[1].v[1], polyf_points[2].v[1]));
-               facemaxs[2] = max(polyf_points[0].v[2], max(polyf_points[1].v[2], polyf_points[2].v[2]));
-               if (BoxesOverlap(segmentmins, segmentmaxs, facemins, facemaxs))
+               if (TriangleOverlapsBox(vertex3f + element3i[0]*3, vertex3 + [element3i[1]*3, vertex3f + element3i[2]*3, segmentmins, segmentmaxs))
                {
+                       VectorCopy(vertex3f + element3i[0] * 3, polyf_points[0].v);
+                       VectorCopy(vertex3f + element3i[1] * 3, polyf_points[1].v);
+                       VectorCopy(vertex3f + element3i[2] * 3, polyf_points[2].v);
+                       Collision_SnapCopyPoints(polyf_brush.numpoints, polyf_points, polyf_points, COLLISION_SNAPSCALE, COLLISION_SNAP);
                        Collision_CalcPlanesForPolygonBrushFloat(&polyf_brush);
                        //Collision_PrintBrushAsQHull(&polyf_brush, "polyf_brush");
                        Collision_TraceLineBrushFloat(trace, linestart, lineend, &polyf_brush, &polyf_brush);
@@ -967,8 +997,8 @@ void Collision_TraceBrushPolygonTransformFloat(trace_t *trace, const colbrushf_t
                polyf_brushstart.planes[i].q3surfaceflags = q3surfaceflags;
                polyf_brushstart.planes[i].texture = texture;
        }
-       Collision_SnapCopyPoints(numpoints, polyf_pointsstart, polyf_pointsstart, COLLISION_SNAPSCALE, COLLISION_SNAP);
-       Collision_SnapCopyPoints(numpoints, polyf_pointsend, polyf_pointsend, COLLISION_SNAPSCALE, COLLISION_SNAP);
+       Collision_SnapCopyPoints(polyf_brushstart.numpoints, polyf_pointsstart, polyf_pointsstart, COLLISION_SNAPSCALE, COLLISION_SNAP);
+       Collision_SnapCopyPoints(polyf_brushend.numpoints, polyf_pointsend, polyf_pointsend, COLLISION_SNAPSCALE, COLLISION_SNAP);
        Collision_CalcPlanesForPolygonBrushFloat(&polyf_brushstart);
        Collision_CalcPlanesForPolygonBrushFloat(&polyf_brushend);
 
@@ -981,7 +1011,9 @@ void Collision_TraceBrushPolygonTransformFloat(trace_t *trace, const colbrushf_t
 
 
 #define MAX_BRUSHFORBOX 16
-static int brushforbox_index = 0;
+static unsigned int brushforbox_index = 0;
+// note: this relies on integer overflow to be consistent with modulo
+// MAX_BRUSHFORBOX, or in other words, MAX_BRUSHFORBOX must be a power of two!
 static colpointf_t brushforbox_point[MAX_BRUSHFORBOX*8];
 static colplanef_t brushforbox_plane[MAX_BRUSHFORBOX*6];
 static colbrushf_t brushforbox_brush[MAX_BRUSHFORBOX];
@@ -1124,7 +1156,7 @@ float Collision_ClipTrace_Line_Sphere(double *linestart, double *lineend, double
        if (deviationdist > sphereradius*sphereradius)
                return 1; // miss (off to the side)
        // nudge back to find the correct impact distance
-       impactdist += deviationdist - sphereradius;
+       impactdist -= sphereradius - deviationdist/sphereradius;
        if (impactdist >= linelength)
                return 1; // miss (not close enough)
        if (impactdist < 0)
@@ -1245,6 +1277,9 @@ void Collision_TraceLineTriangleFloat(trace_t *trace, const vec3_t linestart, co
        // (the main fraction remains perfect)
        trace->fraction = f - collision_impactnudge.value * d;
 
+       if (collision_prefernudgedfraction.integer)
+               trace->realfraction = trace->fraction;
+
        // store the new trace plane (because collisions only happen from
        // the front this is always simply the triangle normal, never flipped)
        d = 1.0 / sqrt(faceplanenormallength2);
@@ -1428,3 +1463,79 @@ void Collision_BoundingBoxOfBrushTraceSegment(const colbrushf_t *start, const co
        maxs[2] += 1;
 }
 
+//===========================================
+
+void Collision_ClipToGenericEntity(trace_t *trace, dp_model_t *model, int frame, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask)
+{
+       float tempnormal[3], starttransformed[3], endtransformed[3];
+
+       memset(trace, 0, sizeof(*trace));
+       trace->fraction = trace->realfraction = 1;
+       VectorCopy(end, trace->endpos);
+
+       Matrix4x4_Transform(inversematrix, start, starttransformed);
+       Matrix4x4_Transform(inversematrix, end, endtransformed);
+#if COLLISIONPARANOID >= 3
+       Con_Printf("trans(%f %f %f -> %f %f %f, %f %f %f -> %f %f %f)", start[0], start[1], start[2], starttransformed[0], starttransformed[1], starttransformed[2], end[0], end[1], end[2], endtransformed[0], endtransformed[1], endtransformed[2]);
+#endif
+
+       if (model && model->TraceBox)
+               model->TraceBox(model, bound(0, frame, (model->numframes - 1)), trace, starttransformed, mins, maxs, endtransformed, hitsupercontentsmask);
+       else
+               Collision_ClipTrace_Box(trace, bodymins, bodymaxs, starttransformed, mins, maxs, endtransformed, hitsupercontentsmask, bodysupercontents, 0, NULL);
+       trace->fraction = bound(0, trace->fraction, 1);
+       trace->realfraction = bound(0, trace->realfraction, 1);
+
+       if (trace->fraction < 1)
+       {
+               VectorLerp(start, trace->fraction, end, trace->endpos);
+               VectorCopy(trace->plane.normal, tempnormal);
+               Matrix4x4_Transform3x3(matrix, tempnormal, trace->plane.normal);
+               // FIXME: should recalc trace->plane.dist
+       }
+}
+
+void Collision_ClipToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontents)
+{
+       memset(trace, 0, sizeof(*trace));
+       trace->fraction = trace->realfraction = 1;
+       if (model && model->TraceBox)
+               model->TraceBox(model, 0, trace, start, mins, maxs, end, hitsupercontents);
+       trace->fraction = bound(0, trace->fraction, 1);
+       trace->realfraction = bound(0, trace->realfraction, 1);
+       VectorLerp(start, trace->fraction, end, trace->endpos);
+}
+
+void Collision_CombineTraces(trace_t *cliptrace, const trace_t *trace, void *touch, qboolean isbmodel)
+{
+       // take the 'best' answers from the new trace and combine with existing data
+       if (trace->allsolid)
+               cliptrace->allsolid = true;
+       if (trace->startsolid)
+       {
+               if (isbmodel)
+                       cliptrace->bmodelstartsolid = true;
+               cliptrace->startsolid = true;
+               if (cliptrace->realfraction == 1)
+                       cliptrace->ent = touch;
+       }
+       // don't set this except on the world, because it can easily confuse
+       // monsters underwater if there's a bmodel involved in the trace
+       // (inopen && inwater is how they check water visibility)
+       //if (trace->inopen)
+       //      cliptrace->inopen = true;
+       if (trace->inwater)
+               cliptrace->inwater = true;
+       if (trace->realfraction < cliptrace->realfraction)
+       {
+               cliptrace->fraction = trace->fraction;
+               cliptrace->realfraction = trace->realfraction;
+               VectorCopy(trace->endpos, cliptrace->endpos);
+               cliptrace->plane = trace->plane;
+               cliptrace->ent = touch;
+               cliptrace->hitsupercontents = trace->hitsupercontents;
+               cliptrace->hitq3surfaceflags = trace->hitq3surfaceflags;
+               cliptrace->hittexture = trace->hittexture;
+       }
+       cliptrace->startsupercontents |= trace->startsupercontents;
+}