+
+void Collision_TraceLinePolygonFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, int numpoints, const float *points, int supercontents)
+{
+ if (numpoints > 256)
+ {
+ Con_Print("Polygon with more than 256 points not supported yet (fixme!)\n");
+ return;
+ }
+ polyf_brush.numpoints = numpoints;
+ 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);
+ polyf_brush.planes = polyf_planes;
+ polyf_brush.supercontents = supercontents;
+ Collision_CalcPlanesForPolygonBrushFloat(&polyf_brush);
+ //Collision_PrintBrushAsQHull(&polyf_brush, "polyf_brush");
+ Collision_TraceLineBrushFloat(trace, linestart, lineend, &polyf_brush, &polyf_brush);
+}
+
+void Collision_TraceLineTriangleMeshFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, int numtriangles, const int *element3i, const float *vertex3f, int supercontents, const vec3_t segmentmins, const vec3_t segmentmaxs)
+{
+ int i;
+#if 1
+ // FIXME: snap vertices?
+ for (i = 0;i < numtriangles;i++, element3i += 3)
+ Collision_TraceLineTriangleFloat(trace, linestart, lineend, vertex3f + element3i[0] * 3, vertex3f + element3i[1] * 3, vertex3f + element3i[2] * 3);
+#else
+ polyf_brush.numpoints = 3;
+ polyf_brush.numplanes = 5;
+ polyf_brush.points = polyf_points;
+ polyf_brush.planes = polyf_planes;
+ polyf_brush.supercontents = supercontents;
+ 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])) - 1;
+ facemins[1] = min(polyf_points[0].v[1], min(polyf_points[1].v[1], polyf_points[2].v[1])) - 1;
+ facemins[2] = min(polyf_points[0].v[2], min(polyf_points[1].v[2], polyf_points[2].v[2])) - 1;
+ facemaxs[0] = max(polyf_points[0].v[0], max(polyf_points[1].v[0], polyf_points[2].v[0])) + 1;
+ facemaxs[1] = max(polyf_points[0].v[1], max(polyf_points[1].v[1], polyf_points[2].v[1])) + 1;
+ facemaxs[2] = max(polyf_points[0].v[2], max(polyf_points[1].v[2], polyf_points[2].v[2])) + 1;
+ if (BoxesOverlap(segmentmins, segmentmaxs, facemins, facemaxs))
+ {
+ Collision_CalcPlanesForPolygonBrushFloat(&polyf_brush);
+ //Collision_PrintBrushAsQHull(&polyf_brush, "polyf_brush");
+ Collision_TraceLineBrushFloat(trace, linestart, lineend, &polyf_brush, &polyf_brush);
+ }
+ }
+#endif
+}
+
+
+static colpointf_t polyf_pointsstart[256], polyf_pointsend[256];
+static colplanef_t polyf_planesstart[256 + 2], polyf_planesend[256 + 2];
+static colbrushf_t polyf_brushstart, polyf_brushend;
+
+void Collision_TraceBrushPolygonTransformFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int numpoints, const float *points, const matrix4x4_t *polygonmatrixstart, const matrix4x4_t *polygonmatrixend, int supercontents)
+{
+ int i;
+ if (numpoints > 256)
+ {
+ Con_Print("Polygon with more than 256 points not supported yet (fixme!)\n");
+ return;
+ }
+ polyf_brushstart.numpoints = numpoints;
+ polyf_brushstart.numplanes = numpoints + 2;
+ polyf_brushstart.points = polyf_pointsstart;//(colpointf_t *)points;
+ polyf_brushstart.planes = polyf_planesstart;
+ polyf_brushstart.supercontents = supercontents;
+ for (i = 0;i < numpoints;i++)
+ Matrix4x4_Transform(polygonmatrixstart, points + i * 3, polyf_brushstart.points[i].v);
+ polyf_brushend.numpoints = numpoints;
+ polyf_brushend.numplanes = numpoints + 2;
+ polyf_brushend.points = polyf_pointsend;//(colpointf_t *)points;
+ polyf_brushend.planes = polyf_planesend;
+ polyf_brushend.supercontents = supercontents;
+ for (i = 0;i < numpoints;i++)
+ Matrix4x4_Transform(polygonmatrixend, points + i * 3, polyf_brushend.points[i].v);
+ Collision_SnapCopyPoints(numpoints, polyf_pointsstart, polyf_pointsstart, COLLISION_SNAPSCALE, COLLISION_SNAP);
+ Collision_SnapCopyPoints(numpoints, polyf_pointsend, polyf_pointsend, COLLISION_SNAPSCALE, COLLISION_SNAP);
+ Collision_CalcPlanesForPolygonBrushFloat(&polyf_brushstart);
+ Collision_CalcPlanesForPolygonBrushFloat(&polyf_brushend);
+
+ //Collision_PrintBrushAsQHull(&polyf_brushstart, "polyf_brushstart");
+ //Collision_PrintBrushAsQHull(&polyf_brushend, "polyf_brushend");
+
+ Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &polyf_brushstart, &polyf_brushend);
+}
+
+
+
+#define MAX_BRUSHFORBOX 16
+static int brushforbox_index = 0;
+static colpointf_t brushforbox_point[MAX_BRUSHFORBOX*8];
+static colplanef_t brushforbox_plane[MAX_BRUSHFORBOX*6];
+static colbrushf_t brushforbox_brush[MAX_BRUSHFORBOX];
+static colbrushf_t brushforpoint_brush[MAX_BRUSHFORBOX];
+
+void Collision_InitBrushForBox(void)
+{
+ int i;
+ for (i = 0;i < MAX_BRUSHFORBOX;i++)
+ {
+ brushforbox_brush[i].supercontents = SUPERCONTENTS_SOLID;
+ brushforbox_brush[i].numpoints = 8;
+ brushforbox_brush[i].numplanes = 6;
+ brushforbox_brush[i].points = brushforbox_point + i * 8;
+ brushforbox_brush[i].planes = brushforbox_plane + i * 6;
+ brushforpoint_brush[i].supercontents = SUPERCONTENTS_SOLID;
+ brushforpoint_brush[i].numpoints = 1;
+ brushforpoint_brush[i].numplanes = 0;
+ brushforpoint_brush[i].points = brushforbox_point + i * 8;
+ brushforpoint_brush[i].planes = brushforbox_plane + i * 6;
+ }
+}
+
+colbrushf_t *Collision_BrushForBox(const matrix4x4_t *matrix, const vec3_t mins, const vec3_t maxs)
+{
+ int i, j;
+ vec3_t v;
+ colbrushf_t *brush;
+ if (brushforbox_brush[0].numpoints == 0)
+ Collision_InitBrushForBox();
+ if (VectorCompare(mins, maxs))
+ {
+ // point brush
+ brush = brushforpoint_brush + ((brushforbox_index++) % MAX_BRUSHFORBOX);
+ VectorCopy(mins, brush->points->v);
+ }
+ else
+ {
+ brush = brushforbox_brush + ((brushforbox_index++) % MAX_BRUSHFORBOX);
+ // FIXME: optimize
+ for (i = 0;i < 8;i++)
+ {
+ v[0] = i & 1 ? maxs[0] : mins[0];
+ v[1] = i & 2 ? maxs[1] : mins[1];
+ v[2] = i & 4 ? maxs[2] : mins[2];
+ Matrix4x4_Transform(matrix, v, brush->points[i].v);
+ }
+ // FIXME: optimize!
+ for (i = 0;i < 6;i++)
+ {
+ VectorClear(v);
+ v[i >> 1] = i & 1 ? 1 : -1;
+ Matrix4x4_Transform3x3(matrix, v, brush->planes[i].normal);
+ VectorNormalize(brush->planes[i].normal);
+ }
+ }
+ for (j = 0;j < brush->numplanes;j++)
+ brush->planes[j].dist = furthestplanedist_float(brush->planes[j].normal, brush->points, brush->numpoints);
+ 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;
+ brush->mins[2] -= 1;
+ brush->maxs[0] += 1;
+ brush->maxs[1] += 1;
+ brush->maxs[2] += 1;
+ Collision_ValidateBrush(brush);
+ return brush;
+}
+
+void Collision_ClipTrace_BrushBox(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask)
+{
+ colbrushf_t *boxbrush, *thisbrush_start, *thisbrush_end;
+ matrix4x4_t identitymatrix;
+ vec3_t startmins, startmaxs, endmins, endmaxs;
+
+ // create brushes for the collision
+ VectorAdd(start, mins, startmins);
+ VectorAdd(start, maxs, startmaxs);
+ VectorAdd(end, mins, endmins);
+ VectorAdd(end, maxs, endmaxs);
+ Matrix4x4_CreateIdentity(&identitymatrix);
+ boxbrush = Collision_BrushForBox(&identitymatrix, cmins, cmaxs);
+ thisbrush_start = Collision_BrushForBox(&identitymatrix, startmins, startmaxs);
+ thisbrush_end = Collision_BrushForBox(&identitymatrix, endmins, endmaxs);
+
+ memset(trace, 0, sizeof(trace_t));
+ trace->hitsupercontentsmask = hitsupercontentsmask;
+ trace->fraction = 1;
+ trace->realfraction = 1;
+ trace->allsolid = true;
+ Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, boxbrush, boxbrush);
+}
+
+// LordHavoc: currently unused and not yet tested
+// note: this can be used for tracing a moving sphere vs a stationary sphere,
+// by simply adding the moving sphere's radius to the sphereradius parameter,
+// all the results are correct (impactpoint, impactnormal, and fraction)
+float Collision_ClipTrace_Line_Sphere(double *linestart, double *lineend, double *sphereorigin, double sphereradius, double *impactpoint, double *impactnormal)
+{
+ double dir[3], scale, v[3], deviationdist, impactdist, linelength;
+ // make sure the impactpoint and impactnormal are valid even if there is
+ // no collision
+ impactpoint[0] = lineend[0];
+ impactpoint[1] = lineend[1];
+ impactpoint[2] = lineend[2];
+ impactnormal[0] = 0;
+ impactnormal[1] = 0;
+ impactnormal[2] = 0;
+ // calculate line direction
+ dir[0] = lineend[0] - linestart[0];
+ dir[1] = lineend[1] - linestart[1];
+ dir[2] = lineend[2] - linestart[2];
+ // normalize direction
+ linelength = sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
+ if (linelength)
+ {
+ scale = 1.0 / linelength;
+ dir[0] *= scale;
+ dir[1] *= scale;
+ dir[2] *= scale;
+ }
+ // this dotproduct calculates the distance along the line at which the
+ // sphere origin is (nearest point to the sphere origin on the line)
+ impactdist = dir[0] * (sphereorigin[0] - linestart[0]) + dir[1] * (sphereorigin[1] - linestart[1]) + dir[2] * (sphereorigin[2] - linestart[2]);
+ // calculate point on line at that distance, and subtract the
+ // sphereorigin from it, so we have a vector to measure for the distance
+ // of the line from the sphereorigin (deviation, how off-center it is)
+ v[0] = linestart[0] + impactdist * dir[0] - sphereorigin[0];
+ v[1] = linestart[1] + impactdist * dir[1] - sphereorigin[1];
+ v[2] = linestart[2] + impactdist * dir[2] - sphereorigin[2];
+ deviationdist = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
+ // if outside the radius, it's a miss for sure
+ // (we do this comparison using squared radius to avoid a sqrt)
+ if (deviationdist > sphereradius*sphereradius)
+ return 1; // miss (off to the side)
+ // nudge back to find the correct impact distance
+ impactdist += (sqrt(deviationdist) - sphereradius);
+ if (impactdist >= linelength)
+ return 1; // miss (not close enough)
+ if (impactdist < 0)
+ return 1; // miss (linestart is past or inside sphere)
+ // calculate new impactpoint
+ impactpoint[0] = linestart[0] + impactdist * dir[0];
+ impactpoint[1] = linestart[1] + impactdist * dir[1];
+ impactpoint[2] = linestart[2] + impactdist * dir[2];
+ // calculate impactnormal (surface normal at point of impact)
+ impactnormal[0] = impactpoint[0] - sphereorigin[0];
+ impactnormal[1] = impactpoint[1] - sphereorigin[1];
+ impactnormal[2] = impactpoint[2] - sphereorigin[2];
+ // normalize impactnormal
+ scale = impactnormal[0] * impactnormal[0] + impactnormal[1] * impactnormal[1] + impactnormal[2] * impactnormal[2];
+ if (scale)
+ {
+ scale = 1.0 / sqrt(scale);
+ impactnormal[0] *= scale;
+ impactnormal[1] *= scale;
+ impactnormal[2] *= scale;
+ }
+ // return fraction of movement distance
+ return impactdist / linelength;
+}
+
+void Collision_TraceLineTriangleFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const float *point0, const float *point1, const float *point2)
+{
+ float d1, d2, d, f, fnudged, impact[3], edgenormal[3], faceplanenormal[3], faceplanedist, edge[3];
+
+ // this code is designed for clockwise triangles, conversion to
+ // counterclockwise would require swapping some things around...
+ // it is easier to simply swap the point0 and point2 parameters to this
+ // function when calling it than it is to rewire the internals.
+
+ // calculate the faceplanenormal of the triangle, this represents the front side
+ TriangleNormal(point0, point1, point2, faceplanenormal);
+ // there's no point in processing a degenerate triangle (GIGO - Garbage In, Garbage Out)
+ if (DotProduct(faceplanenormal, faceplanenormal) < 0.0001f)
+ return;
+ // normalize the normal
+ VectorNormalize(faceplanenormal);
+ // calculate the distance
+ faceplanedist = DotProduct(point0, faceplanenormal);
+
+ // calculate the start distance
+ d1 = DotProduct(faceplanenormal, linestart) - faceplanedist;
+ // if start point is on the back side there is no collision
+ // (we don't care about traces going through the triangle the wrong way)
+ if (d1 < 0)
+ return;
+
+ // calculate the end distance
+ d2 = DotProduct(faceplanenormal, lineend) - faceplanedist;
+ // if both are in front, there is no collision
+ if (d2 >= 0)
+ return;
+
+ // from here on we know d1 is >= 0 and d2 is < 0
+ // this means the line starts infront and ends behind, passing through it
+
+ // calculate the recipricol of the distance delta,
+ // so we can use it multiple times cheaply (instead of division)
+ d = 1.0f / (d1 - d2);
+ // calculate the impact fraction by taking the start distance (> 0)
+ // and subtracting the face plane distance (this is the distance of the
+ // triangle along that same normal)
+ // then multiply by the recipricol distance delta
+ f = d1 * d;
+ // skip out if this impact is further away than previous ones
+ if (f > trace->realfraction)
+ return;
+ // calculate the perfect impact point for classification of insidedness
+ impact[0] = linestart[0] + f * (lineend[0] - linestart[0]);
+ impact[1] = linestart[1] + f * (lineend[1] - linestart[1]);
+ impact[2] = linestart[2] + f * (lineend[2] - linestart[2]);
+
+ // calculate the edge normal and reject if impact is outside triangle
+ // (an edge normal faces away from the triangle, to get the desired normal
+ // a crossproduct with the faceplanenormal is used, and because of the way
+ // the insidedness comparison is written it does not need to be normalized)
+
+ VectorSubtract(point2, point0, edge);
+ CrossProduct(edge, faceplanenormal, edgenormal);
+ if (DotProduct(impact, edgenormal) > DotProduct(point0, edgenormal))
+ return;
+
+ VectorSubtract(point0, point1, edge);
+ CrossProduct(edge, faceplanenormal, edgenormal);
+ if (DotProduct(impact, edgenormal) > DotProduct(point1, edgenormal))
+ return;
+
+ VectorSubtract(point1, point2, edge);
+ CrossProduct(edge, faceplanenormal, edgenormal);
+ if (DotProduct(impact, edgenormal) > DotProduct(point2, edgenormal))
+ return;
+
+ // store the new trace fraction
+ trace->realfraction = bound(0, f, 1);
+
+ // calculate a nudged fraction to keep it out of the surface
+ // (the main fraction remains perfect)
+ fnudged = (d1 - collision_impactnudge.value) * d;
+ trace->fraction = bound(0, fnudged, 1);
+
+ // store the new trace endpos
+ // not needed, it's calculated later when the trace is finished
+ //trace->endpos[0] = linestart[0] + fnudged * (lineend[0] - linestart[0]);
+ //trace->endpos[1] = linestart[1] + fnudged * (lineend[1] - linestart[1]);
+ //trace->endpos[2] = linestart[2] + fnudged * (lineend[2] - linestart[2]);
+
+ // store the new trace plane (because collisions only happen from
+ // the front this is always simply the triangle normal, never flipped)
+ VectorCopy(faceplanenormal, trace->plane.normal);
+ trace->plane.dist = faceplanedist;
+}
+
+typedef struct colbspnode_s
+{
+ mplane_t plane;
+ struct colbspnode_s *children[2];
+ // the node is reallocated or split if max is reached
+ int numcolbrushf;
+ int maxcolbrushf;
+ colbrushf_t **colbrushflist;
+ //int numcolbrushd;
+ //int maxcolbrushd;
+ //colbrushd_t **colbrushdlist;
+}
+colbspnode_t;
+
+typedef struct colbsp_s
+{
+ mempool_t *mempool;
+ colbspnode_t *nodes;
+}
+colbsp_t;
+
+colbsp_t *Collision_CreateCollisionBSP(mempool_t *mempool)
+{
+ colbsp_t *bsp;
+ bsp = Mem_Alloc(mempool, sizeof(colbsp_t));
+ bsp->mempool = mempool;
+ bsp->nodes = Mem_Alloc(bsp->mempool, sizeof(colbspnode_t));
+ return bsp;
+}
+
+void Collision_FreeCollisionBSPNode(colbspnode_t *node)
+{
+ if (node->children[0])
+ Collision_FreeCollisionBSPNode(node->children[0]);
+ if (node->children[1])
+ Collision_FreeCollisionBSPNode(node->children[1]);
+ while (--node->numcolbrushf)
+ Mem_Free(node->colbrushflist[node->numcolbrushf]);
+ //while (--node->numcolbrushd)
+ // Mem_Free(node->colbrushdlist[node->numcolbrushd]);
+ Mem_Free(node);
+}
+
+void Collision_FreeCollisionBSP(colbsp_t *bsp)
+{
+ Collision_FreeCollisionBSPNode(bsp->nodes);
+ Mem_Free(bsp);
+}
+
+void Collision_BoundingBoxOfBrushTraceSegment(const colbrushf_t *start, const colbrushf_t *end, vec3_t mins, vec3_t maxs, float startfrac, float endfrac)
+{
+ int i;
+ colpointf_t *ps, *pe;
+ float tempstart[3], tempend[3];
+ VectorLerp(start->points[0].v, startfrac, end->points[0].v, mins);
+ VectorCopy(mins, maxs);
+ for (i = 0, ps = start->points, pe = end->points;i < start->numpoints;i++, ps++, pe++)
+ {
+ VectorLerp(ps->v, startfrac, pe->v, tempstart);
+ VectorLerp(ps->v, endfrac, pe->v, tempend);
+ mins[0] = min(mins[0], min(tempstart[0], tempend[0]));
+ mins[1] = min(mins[1], min(tempstart[1], tempend[1]));
+ mins[2] = min(mins[2], min(tempstart[2], tempend[2]));
+ maxs[0] = min(maxs[0], min(tempstart[0], tempend[0]));
+ maxs[1] = min(maxs[1], min(tempstart[1], tempend[1]));
+ maxs[2] = min(maxs[2], min(tempstart[2], tempend[2]));
+ }
+ mins[0] -= 1;
+ mins[1] -= 1;
+ mins[2] -= 1;
+ maxs[0] += 1;
+ maxs[1] += 1;
+ maxs[2] += 1;
+}
+