#include "quakedef.h" #ifndef SERVERONLY #include "glquake.h" #endif #include "com_mesh.h" #include "com_bih.h" //BIH traces are capable of checking each object only once, thus our collision structures can be fully const. //this also allows traces to be threaded, if we can avoid the temptation to (ab)use globals. //so don't use any globals! struct bihnode_s { //in a bih tree there are two values per node instead of a kd-tree's single midpoint //this allows the two sides to overlap, which prevents the need to chop large objects into multiple leafs //(it also allows gaps in the middle, which can further skip recursion) enum bihtype_e type; union { struct{ int firstchild; int numchildren; } group; #ifdef BIH_USEBVH struct{ int firstchild; vec3_t min, max; float cmin; float cmax; } bvhnode; #endif #ifdef BIH_USEBIH struct{ int firstchild; float cmin[2]; float cmax[2]; } bihnode; #endif struct bihdata_s data; }; }; struct bihbox_s { vec3_t min; vec3_t max; }; struct bihtrace_s { struct bihbox_s bounds; struct bihbox_s size; vec3_t expand; vec3_t up; //capsule's upwards direction vec3_t capsulesize; //radius, up, down qboolean negativedir[3]; enum { shape_ispoint, shape_isbox, shape_iscapsule, } shape; unsigned int hitcontents; vec3_t startpos; //bounds.[min|max] vec3_t totalmove; vec3_t endpos; //bounds.[min|max] trace_t trace; }; static const q2mapsurface_t nullsurface; static qboolean BIH_BoundsIntersect (const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2) { return (mins1[0] <= maxs2[0] && mins1[1] <= maxs2[1] && mins1[2] <= maxs2[2] && maxs1[0] >= mins2[0] && maxs1[1] >= mins2[1] && maxs1[2] >= mins2[2]); } #define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) #define boxdist(dist,plane) \ default: \ case shape_isbox: \ /* FIXME: needs special case for axial */ \ for (j=0 ; j<3 ; j++) \ { \ if (plane->normal[j] < 0) \ ofs[j] = tr->size.max[j]; \ else \ ofs[j] = tr->size.min[j]; \ } \ dist = DotProduct (ofs, plane->normal); \ dist = plane->dist - dist; \ break; #define capsuledist(dist,plane) \ case shape_iscapsule: \ dist = DotProduct(tr->up, plane->normal); \ dist = dist*(tr->capsulesize[(dist<0)?1:2]) - tr->capsulesize[0]; \ dist = plane->dist - dist; \ break; #define pointdist(dist,plane) \ case shape_ispoint: \ dist = plane->dist; \ break; #define calcdist(dist,plane) switch(tr->shape) { \ boxdist(dist,plane) \ capsuledist(dist,plane) \ pointdist(dist,plane) \ } #define DIST_EPSILON (0.03125) /*static void BIH_ClipBoxToPlanes (struct bihtrace_s *fte_restrict tr, vec3_t plmins, vec3_t plmaxs, const mplane_t *plane, int numplanes, const q2csurface_t *surf) { int i, j; const mplane_t *clipplane; float dist; float enterfrac, leavefrac; vec3_t ofs; float d1, d2; qboolean getout, startout; float f; static const mplane_t bboxplanes[6] = //we change the dist, but nothing else { {{1, 0, 0}}, {{0, 1, 0}}, {{0, 0, 1}}, {{-1, 0, 0}}, {{0, -1, 0}}, {{0, 0, -1}}, }; size_t u; float nearfrac=0; enterfrac = -1; leavefrac = 2; clipplane = NULL; getout = false; startout = false; for (i=0 ; istartpos, plane->normal) - dist; d2 = DotProduct (tr->endpos, plane->normal) - dist; if (d2 > 0) getout = true; // endpoint is not in solid if (d1 > 0) startout = true; // if completely in front of face, no intersection if (d1 > 0 && d2 >= d1) return; if (d1 <= 0 && d2 <= 0) continue; // crosses face if (d1 > d2) { // enter f = (d1) / (d1-d2); if (f > enterfrac) { enterfrac = f; nearfrac = (d1-DIST_EPSILON) / (d1-d2); clipplane = plane; } } else { // leave f = (d1) / (d1-d2); if (f < leavefrac) leavefrac = f; } } if (tr->shape) //bevel the brush axially (to match the player's bbox), in case that wasn't already done for (i=0, plane = bboxplanes; isize.min[i]; dist = plmaxs[i] - dist; d1 = tr->startpos[i] - dist; d2 = tr->endpos[i] - dist; } else { //negative normal j = i-3; dist = -tr->size.max[j]; dist = -plmins[j] - dist; d1 = -tr->startpos[j] - dist; d2 = -tr->endpos[j] - dist; } if (d2 > 0) getout = true; // endpoint is not in solid if (d1 > 0) startout = true; // if completely in front of face, no intersection if (d1 > 0 && d2 >= d1) return; if (d1 <= 0 && d2 <= 0) continue; // crosses face if (d1 > d2) { // enter f = (d1) / (d1-d2); if (f > enterfrac) { enterfrac = f; nearfrac = (d1-DIST_EPSILON) / (d1-d2); clipplane = plane; } } else { // leave f = (d1) / (d1-d2); if (f < leavefrac) leavefrac = f; } } if (!startout) { // original point was inside brush tr->trace.startsolid = true; if (!getout) tr->trace.allsolid = true; return; } if (enterfrac <= leavefrac) { if (enterfrac > -1 && enterfrac <= tr->trace.truefraction) { if (enterfrac < 0) enterfrac = 0; tr->trace.fraction = nearfrac; tr->trace.truefraction = enterfrac; if ((u=clipplane-bboxplanes) < countof(bboxplanes)) //hit one of the bbox planes. get the proper plane dist. tr->trace.plane.dist = (u < 3)?plmaxs[u]:-plmins[u-3]; else tr->trace.plane.dist = clipplane->dist; VectorCopy(clipplane->normal, tr->trace.plane.normal); tr->trace.surface = surf; tr->trace.contents = surf->value; } } }*/ static void BIH_ClipToTriangle(struct bihtrace_s *fte_restrict tr, const struct bihdata_s *info) { int i, j; float *p1, *p2, *p3; vec3_t edge1, edge2, edge3; mplane_t planes[5]; const mplane_t *plane; vec3_t tmins, tmaxs; const mplane_t *clipplane; float dist; float enterfrac, leavefrac, nearfrac; vec3_t ofs; float d1, d2; qboolean getout, startout; float f; static const mplane_t bboxplanes[6] = { {{1, 0, 0}}, {{0, 1, 0}}, {{0, 0, 1}}, {{-1, 0, 0}}, {{0, -1, 0}}, {{0, 0, -1}}, }; size_t u; p1 = info->tri.xyz[info->tri.indexes[0]]; p2 = info->tri.xyz[info->tri.indexes[1]]; p3 = info->tri.xyz[info->tri.indexes[2]]; //determine the triangle extents, and skip the triangle if we're completely out of bounds for (j = 0; j < 3; j++) { tmins[j] = p1[j]; if (tmins[j] > p2[j]) tmins[j] = p2[j]; if (tmins[j] > p3[j]) tmins[j] = p3[j]; if (tr->bounds.max[j]+(1/8.f) < tmins[j]) return; tmaxs[j] = p1[j]; if (tmaxs[j] < p2[j]) tmaxs[j] = p2[j]; if (tmaxs[j] < p3[j]) tmaxs[j] = p3[j]; if (tr->bounds.min[j]-(1/8.f) > tmaxs[j]) return; } VectorSubtract(p1, p2, edge1); VectorSubtract(p3, p2, edge2); VectorSubtract(p1, p3, edge3); CrossProduct(edge1, edge2, planes[0].normal); VectorNormalize(planes[0].normal); planes[0].dist = DotProduct(p1, planes[0].normal); VectorNegate(planes[0].normal, planes[1].normal); planes[1].dist = -planes[0].dist + 4; //determine edges //FIXME: use adjacency info CrossProduct(edge1, planes[0].normal, planes[2].normal); VectorNormalize(planes[2].normal); planes[2].dist = DotProduct(p2, planes[2].normal); CrossProduct(planes[0].normal, edge2, planes[3].normal); VectorNormalize(planes[3].normal); planes[3].dist = DotProduct(p3, planes[3].normal); CrossProduct(planes[0].normal, edge3, planes[4].normal); VectorNormalize(planes[4].normal); planes[4].dist = DotProduct(p1, planes[4].normal); nearfrac=0; enterfrac = -1; leavefrac = 2; clipplane = NULL; getout = false; startout = false; for (i=0, plane = planes ; istartpos, plane->normal) - dist; d2 = DotProduct (tr->endpos, plane->normal) - dist; if (d2 > 0) getout = true; // endpoint is not in solid if (d1 > 0) startout = true; // if completely in front of face, no intersection if (d1 > 0 && d2 >= d1) return; if (d1 <= 0 && d2 <= 0) continue; // crosses face if (d1 > d2) { // enter f = (d1) / (d1-d2); if (f > enterfrac) { enterfrac = f; nearfrac = (d1-DIST_EPSILON) / (d1-d2); clipplane = plane; } } else { // leave f = (d1) / (d1-d2); if (f < leavefrac) leavefrac = f; } } if (tr->shape) //bevel the brush axially (to match the player's bbox), in case that wasn't already done for (i=0, plane = bboxplanes; isize.min[i]; dist = tmaxs[i] - dist; d1 = tr->startpos[i] - dist; d2 = tr->endpos[i] - dist; } else { //negative normal j = i-3; dist = -tr->size.max[j]; dist = -tmins[j] - dist; d1 = -tr->startpos[j] - dist; d2 = -tr->endpos[j] - dist; } if (d2 > 0) getout = true; // endpoint is not in solid if (d1 > 0) startout = true; // if completely in front of face, no intersection if (d1 > 0 && d2 >= d1) return; if (d1 <= 0 && d2 <= 0) continue; // crosses face if (d1 > d2) { // enter f = (d1) / (d1-d2); if (f > enterfrac) { enterfrac = f; nearfrac = (d1-DIST_EPSILON) / (d1-d2); clipplane = plane; } } else { // leave f = (d1) / (d1-d2); if (f < leavefrac) leavefrac = f; } } if (!startout) { // original point was inside brush tr->trace.startsolid = true; if (!getout) tr->trace.allsolid = true; return; } if (enterfrac <= leavefrac) { if (enterfrac > -1 && enterfrac <= tr->trace.truefraction) { if (enterfrac < 0) enterfrac = 0; tr->trace.fraction = nearfrac; tr->trace.truefraction = enterfrac; if ((u=clipplane-bboxplanes) < countof(bboxplanes)) //hit one of the bbox planes. get the proper plane dist. tr->trace.plane.dist = (u < 3)?tmaxs[u]:-tmins[u-3]; else tr->trace.plane.dist = clipplane->dist; VectorCopy(clipplane->normal, tr->trace.plane.normal); tr->trace.surface = &nullsurface.c; tr->trace.contents = info->contents; } } } static void BIH_TestToTriangle(struct bihtrace_s *fte_restrict tr, const struct bihdata_s *info) { int j; float *p1, *p2, *p3; vec3_t edge1, edge2, edge3; mplane_t planes[5]; const mplane_t *plane; vec3_t tmins, tmaxs; int i; float dist; vec3_t ofs; float d1; static const mplane_t bboxplanes[6] = //we change the dist, but nothing else { {{1, 0, 0}}, {{0, 1, 0}}, {{0, 0, 1}}, {{-1, 0, 0}}, {{0, -1, 0}}, {{0, 0, -1}}, }; p1 = info->tri.xyz[info->tri.indexes[0]]; p2 = info->tri.xyz[info->tri.indexes[1]]; p3 = info->tri.xyz[info->tri.indexes[2]]; //determine the triangle extents, and skip the triangle if we're completely out of bounds for (j = 0; j < 3; j++) { tmins[j] = p1[j]; if (tmins[j] > p2[j]) tmins[j] = p2[j]; if (tmins[j] > p3[j]) tmins[j] = p3[j]; if (tr->bounds.max[j]+(1/8.f) < tmins[j]) return; tmaxs[j] = p1[j]; if (tmaxs[j] < p2[j]) tmaxs[j] = p2[j]; if (tmaxs[j] < p3[j]) tmaxs[j] = p3[j]; if (tr->bounds.min[j]-(1/8.f) > tmaxs[j]) return; } VectorSubtract(p1, p2, edge1); VectorSubtract(p3, p2, edge2); VectorSubtract(p1, p3, edge3); CrossProduct(edge1, edge2, planes[0].normal); VectorNormalize(planes[0].normal); planes[0].dist = DotProduct(p1, planes[0].normal); VectorNegate(planes[0].normal, planes[1].normal); planes[1].dist = -planes[0].dist + 4; //determine edges //FIXME: use adjacency info CrossProduct(edge1, planes[0].normal, planes[2].normal); VectorNormalize(planes[2].normal); planes[2].dist = DotProduct(p2, planes[2].normal); CrossProduct(planes[0].normal, edge2, planes[3].normal); VectorNormalize(planes[3].normal); planes[3].dist = DotProduct(p3, planes[3].normal); CrossProduct(planes[0].normal, edge3, planes[4].normal); VectorNormalize(planes[4].normal); planes[4].dist = DotProduct(p1, planes[4].normal); for (i=0, plane = planes; istartpos, plane->normal) - dist; if (d1 > 0) return; } if (tr->shape) //bevel the brush axially (to match the player's bbox), in case that wasn't already done for (i=0, plane = bboxplanes; isize.min[i]; dist = tmaxs[i] - dist; d1 = tr->startpos[i] - dist; } else { //negative normal j = i-3; dist = -tr->size.max[j]; dist = -tmins[j] - dist; d1 = -tr->startpos[j] - dist; } // if completely in front of face, no intersection if (d1 > 0) return; } tr->trace.startsolid = tr->trace.allsolid = true; tr->trace.contents |= info->contents; } #if defined(Q2BSPS) || defined(Q3BSPS) static void BIH_ClipBoxToBrush (struct bihtrace_s *fte_restrict tr, const q2cbrush_t *brush) { int i, j; mplane_t *plane, *clipplane; float dist; float enterfrac, leavefrac; vec3_t ofs; float d1, d2; qboolean getout, startout; float f; q2cbrushside_t *side, *leadside; float nearfrac=0; enterfrac = -1; leavefrac = 2; clipplane = NULL; if (!brush->numsides) return; getout = false; startout = false; leadside = NULL; for (i=0 ; inumsides ; i++) { side = brush->brushside+i; plane = side->plane; calcdist(dist, plane) d1 = DotProduct (tr->startpos, plane->normal) - dist; d2 = DotProduct (tr->endpos, plane->normal) - dist; if (d2 > 0) getout = true; // endpoint is not in solid if (d1 > 0) startout = true; // if completely in front of face, no intersection if (d1 > 0 && d2 >= d1) return; if (d1 <= 0 && d2 <= 0) continue; // crosses face if (d1 > d2) { // enter f = (d1) / (d1-d2); if (f > enterfrac) { enterfrac = f; nearfrac = (d1-DIST_EPSILON) / (d1-d2); clipplane = plane; leadside = side; } } else { // leave f = (d1) / (d1-d2); if (f < leavefrac) leavefrac = f; } } if (!startout) { // original point was inside brush tr->trace.startsolid = true; if (!getout) tr->trace.allsolid = true; return; } if (enterfrac <= leavefrac) { if (enterfrac > -1 && enterfrac <= tr->trace.truefraction) { if (enterfrac < 0) enterfrac = 0; tr->trace.fraction = nearfrac; tr->trace.truefraction = enterfrac; tr->trace.plane.dist = clipplane->dist; VectorCopy(clipplane->normal, tr->trace.plane.normal); tr->trace.surface = &(leadside->surface->c); tr->trace.contents = brush->contents; } } } static void BIH_TestBoxInBrush (struct bihtrace_s *fte_restrict tr, q2cbrush_t *brush) { int i, j; mplane_t *plane; float dist; vec3_t ofs; float d1; q2cbrushside_t *side; if (!brush->numsides) return; for (i=0 ; inumsides ; i++) { side = brush->brushside+i; plane = side->plane; calcdist(dist, plane) d1 = DotProduct (tr->startpos, plane->normal) - dist; // if completely in front of face, no intersection if (d1 > 0) return; } // inside this brush tr->trace.startsolid = tr->trace.allsolid = true; tr->trace.contents |= brush->contents; } #endif #ifdef Q3BSPS static void BIH_ClipBoxToPatch (struct bihtrace_s *fte_restrict tr, q2cbrush_t *brush) { int i, j; mplane_t *plane, *clipplane; float enterfrac, leavefrac, nearfrac = 0; vec3_t ofs; float d1, d2; float dist; qboolean startout; float f; q2cbrushside_t *side, *leadside; if (!brush->numsides) return; enterfrac = -1; leavefrac = 2; clipplane = NULL; startout = false; leadside = NULL; for (i=0 ; inumsides ; i++) { side = brush->brushside+i; plane = side->plane; calcdist(dist, plane) d1 = DotProduct (tr->startpos, plane->normal) - dist; d2 = DotProduct (tr->endpos, plane->normal) - dist; // if completely in front of face, no intersection if (d1 > 0 && d2 >= d1) return; if (d1 > 0) startout = true; if (d1 <= 0 && d2 <= 0) continue; // crosses face if (d1 > d2) { // enter f = (d1) / (d1-d2); if (f > enterfrac) { enterfrac = f; nearfrac = (d1-DIST_EPSILON) / (d1-d2); clipplane = plane; leadside = side; } } else { // leave f = (d1) / (d1-d2); if (f < leavefrac) leavefrac = f; } } if (!startout) { tr->trace.startsolid = true; return; // original point is inside the patch } if (nearfrac <= leavefrac) { if (leadside && leadside->surface && enterfrac <= tr->trace.truefraction) { if (enterfrac < 0) enterfrac = 0; tr->trace.truefraction = enterfrac; tr->trace.fraction = nearfrac; tr->trace.plane.dist = clipplane->dist; VectorCopy(clipplane->normal, tr->trace.plane.normal); tr->trace.surface = &leadside->surface->c; tr->trace.contents = brush->contents; } else if (enterfrac < tr->trace.truefraction) leavefrac=0; } } static void BIH_TestBoxInPatch (struct bihtrace_s *fte_restrict tr, q2cbrush_t *brush) { int i, j; mplane_t *plane; vec3_t ofs, ofs2; float dist, thickness; float d1; q2cbrushside_t *side; if (!brush->numsides) return; i = 0; //front plane { side = brush->brushside+i; plane = side->plane; switch(tr->shape) { default: case shape_isbox: for (j=0 ; j<3 ; j++) { if (plane->normal[j] < 0) ofs[j] = tr->size.max[j], ofs2[j] = tr->size.min[j]; else ofs[j] = tr->size.min[j], ofs2[j] = tr->size.max[j]; } dist = DotProduct (ofs, plane->normal); thickness = DotProduct (ofs2, plane->normal)-dist; dist = plane->dist - dist; break; case shape_iscapsule: dist = DotProduct(tr->up, plane->normal); thickness = dist*(tr->capsulesize[(dist<0)?2:1]) + tr->capsulesize[0]*2; dist = dist*(tr->capsulesize[(dist<0)?1:2]) - tr->capsulesize[0]; dist = plane->dist - dist; break; case shape_ispoint: dist = plane->dist; thickness = 0; break; } d1 = DotProduct (tr->startpos, plane->normal) - dist; // if completely in front of face, no intersection if (d1 > 0) return; //point is behind the front plane, so no real intersection. if (thickness < 0.25) thickness = 0.25; //FIXME: patches should probably be infinitely thin, but that makes stuff messy. if (d1 < -thickness) return; } for (i=1 ; inumsides ; i++) { side = brush->brushside+i; plane = side->plane; calcdist(dist, plane) d1 = DotProduct (tr->startpos, plane->normal) - dist; // if completely in front of face, no intersection if (d1 > 0) return; } // inside this patch tr->trace.startsolid = tr->trace.allsolid = true; tr->trace.contents = brush->contents; } #endif static void BIH_RecursiveTrace (struct bihtrace_s *fte_restrict tr, const struct bihnode_s *fte_restrict node, const struct bihbox_s *fte_restrict movesubbounds, const struct bihbox_s *fte_restrict nodebox) { //if the tree were 1d, we wouldn't need to be so careful with the bounds, but if the trace is long then we want to avoid hitting all surfaces within that entire-map-encompassing move aabb switch(node->type) { //leaf #if defined(Q2BSPS) || defined(Q3BSPS) case BIH_BRUSH: if (node->data.contents & tr->hitcontents) { q2cbrush_t *b = node->data.brush; if (BIH_BoundsIntersect(b->absmins, b->absmaxs, movesubbounds->min, movesubbounds->max)) BIH_ClipBoxToBrush (tr, b); } return; #endif #ifdef Q3BSPS case BIH_PATCHBRUSH: if (node->data.contents & tr->hitcontents) { q2cbrush_t *b = node->data.patchbrush; if (BIH_BoundsIntersect(b->absmins, b->absmaxs, movesubbounds->min, movesubbounds->max)) BIH_ClipBoxToPatch (tr, b); } return; case BIH_TRISOUP: /*if (node->data.contents & tr->hitcontents) { q3cmesh_t *cmesh = node->data.cmesh; if (BIH_BoundsIntersect(cmesh->absmins, cmesh->absmaxs, movesubbounds->min, movesubbounds->max)) Mod_Trace_Trisoup_(cmesh->xyz_array, cmesh->indicies, cmesh->numincidies, trace_start, trace_end, trace_mins, trace_maxs, &trace_trace, &cmesh->surface->c); }*/ return; #endif case BIH_TRIANGLE: if (node->data.contents & tr->hitcontents) BIH_ClipToTriangle(tr, &node->data); return; case BIH_GROUP: { int i; for (i = 0; i < node->group.numchildren; i++) BIH_RecursiveTrace(tr, node+node->group.firstchild+i, movesubbounds, nodebox); } return; #ifdef BIH_USEBIH case BIH_X: case BIH_Y: case BIH_Z: { struct bihbox_s bounds; struct bihbox_s newbounds; float distnear, distfar, nearfrac, farfrac, min, max; unsigned int axis = node->type-BIH_X, child, a, s; vec3_t points[2]; if (!tr->totalmove[axis]) { //doesn't move with respect to this axis. don't allow infinities. for (child = 0; child < 2; child++) { //only recurse if we are actually within the child min = node->bihnode.cmin[child] - tr->expand[axis]; max = node->bihnode.cmax[child] + tr->expand[axis]; if (min <= tr->startpos[axis] && tr->startpos[axis] <= max) { bounds = *nodebox; bounds.min[axis] = min; bounds.max[axis] = max; BIH_RecursiveTrace(tr, node+node->bihnode.firstchild+child, movesubbounds, &bounds); } } } else if (tr->negativedir[axis]) { //trace goes from right to left so favour the right. bounds = *nodebox; for (child = 2; child-- > 0;) { bounds.min[axis] = node->bihnode.cmin[child] - tr->expand[axis]; bounds.max[axis] = node->bihnode.cmax[child] + tr->expand[axis]; //expand the bounds according to the player's size if (!BIH_BoundsIntersect(movesubbounds->min, movesubbounds->max, bounds.min, bounds.max)) continue; // if (movesubbounds->max[axis] < bounds.min[axis]) // continue; //(clipped) move bounds is outside this child // if (bounds.max[axis] < movesubbounds->min[axis]) // continue; //(clipped) move bounds is outside this child distnear = bounds.max[axis] - tr->startpos[axis]; nearfrac = distnear/tr->totalmove[axis]; if (nearfrac <= tr->trace.truefraction) { VectorMA(tr->startpos, nearfrac, tr->totalmove, points[0]); //clip the new movebounds (this is more to clip the other axis too) distfar = bounds.min[axis] - tr->startpos[axis]; farfrac = distfar/tr->totalmove[axis]; VectorMA(tr->startpos, farfrac, tr->totalmove, points[1]); //clip the new movebounds (this is more to clip the other axis too) for (a = 0; a < 3; a++) { s = points[0][a] > points[1][a]; newbounds.min[a] = max(movesubbounds->min[a], points[s][a] - tr->expand[a]); newbounds.max[a] = min(movesubbounds->max[a], points[!s][a] + tr->expand[a]); } BIH_RecursiveTrace(tr, node+node->bihnode.firstchild+child, &newbounds, &bounds); } } } else { //trace goes from left to right bounds = *nodebox; for (child = 0; child < 2; child++) { bounds.min[axis] = node->bihnode.cmin[child] - tr->expand[axis]; bounds.max[axis] = node->bihnode.cmax[child] + tr->expand[axis]; //expand the bounds according to the player's size if (!BIH_BoundsIntersect(movesubbounds->min, movesubbounds->max, bounds.min, bounds.max)) continue; // if (movesubbounds->max[axis] < bounds.min[axis]) // continue; //(clipped) move bounds is outside this child // if (bounds.max[axis] < movesubbounds->min[axis]) // continue; //(clipped) move bounds is outside this child distnear = bounds.min[axis] - tr->startpos[axis]; nearfrac = distnear/tr->totalmove[axis]; if (nearfrac <= tr->trace.truefraction) { VectorMA(tr->startpos, nearfrac, tr->totalmove, points[0]); //clip the new movebounds (this is more to clip the other axis too) distfar = bounds.max[axis] - tr->startpos[axis]; farfrac = distfar/tr->totalmove[axis]; VectorMA(tr->startpos, farfrac, tr->totalmove, points[1]); //clip the new movebounds (this is more to clip the other axis too) for (a = 0; a < 3; a++) { s = points[0][a] > points[1][a]; newbounds.min[a] = max(movesubbounds->min[a], points[s][a] - tr->expand[a]); newbounds.max[a] = min(movesubbounds->max[a], points[!s][a] + tr->expand[a]); } BIH_RecursiveTrace(tr, node+node->bihnode.firstchild+child, &newbounds, &bounds); } } } } return; #endif #ifdef BIH_USEBVH case BVH_X: case BVH_Y: case BVH_Z: { struct bihbox_s bounds; struct bihbox_s newbounds; float distnear, distfar, nearfrac, farfrac, min, max; unsigned int axis = node->type-BVH_X, child, a, s; vec3_t points[2]; if (!tr->totalmove[axis]) { //doesn't move with respect to this axis. don't allow infinities. for (child = 0; child < 2; child++) { //only recurse if we are actually within the child if (child == 0) { min = node->bvhnode.min[axis] - tr->expand[axis]; max = node->bvhnode.cmax + tr->expand[axis]; } else { min = node->bvhnode.cmin - tr->expand[axis]; max = node->bvhnode.max[axis] + tr->expand[axis]; } if (min <= tr->startpos[axis] && tr->startpos[axis] <= max) { VectorCopy(node->bvhnode.min, bounds.min); VectorCopy(node->bvhnode.max, bounds.max); bounds.min[axis] = min; bounds.max[axis] = max; CM_RecursiveBIHTrace(tr, node+node->bvhnode.firstchild+child, movesubbounds, &bounds); } } } else if (tr->negativedir[axis]) { //trace goes from right to left so favour the right. VectorCopy(node->bvhnode.min, bounds.min); VectorCopy(node->bvhnode.max, bounds.max); for (child = 2; child-- > 0;) { if (child == 0) { bounds.min[axis] = node->bvhnode.min[axis] - tr->expand[axis]; bounds.max[axis] = node->bvhnode.cmax + tr->expand[axis]; //expand the bounds according to the player's size } else { bounds.min[axis] = node->bvhnode.cmin - tr->expand[axis]; bounds.max[axis] = node->bvhnode.max[axis] + tr->expand[axis]; //expand the bounds according to the player's size } if (!BIH_BoundsIntersect(movesubbounds->min, movesubbounds->max, bounds.min, bounds.max)) continue; // if (movesubbounds->max[axis] < bounds.min[axis]) // continue; //(clipped) move bounds is outside this child // if (bounds.max[axis] < movesubbounds->min[axis]) // continue; //(clipped) move bounds is outside this child distnear = bounds.max[axis] - tr->startpos[axis]; nearfrac = (distnear+DIST_EPSILON)/tr->totalmove[axis]; if (nearfrac <= trace_truefraction) { VectorMA(tr->startpos, nearfrac, tr->totalmove, points[0]); //clip the new movebounds (this is more to clip the other axis too) distfar = bounds.min[axis] - tr->startpos[axis]; farfrac = (distfar-DIST_EPSILON)/tr->totalmove[axis]; VectorMA(tr->startpos, farfrac, tr->totalmove, points[1]); //clip the new movebounds (this is more to clip the other axis too) for (a = 0; a < 3; a++) { s = points[0][a] > points[1][a]; newbounds.min[a] = max(movesubbounds->min[a], points[s][a] - tr->expand[a]); newbounds.max[a] = min(movesubbounds->max[a], points[!s][a] + tr->expand[a]); } CM_RecursiveBIHTrace(tr, node+node->bvhnode.firstchild+child, &newbounds, &bounds); } } } else { //trace goes from left to right VectorCopy(node->bvhnode.min, bounds.min); VectorCopy(node->bvhnode.max, bounds.max); for (child = 0; child < 2; child++) { if (child == 0) { bounds.min[axis] = node->bvhnode.min[axis] - tr->expand[axis]; bounds.max[axis] = node->bvhnode.cmax + tr->expand[axis]; //expand the bounds according to the player's size } else { bounds.min[axis] = node->bvhnode.cmin - tr->expand[axis]; bounds.max[axis] = node->bvhnode.max[axis] + tr->expand[axis]; //expand the bounds according to the player's size } if (!BIH_BoundsIntersect(movesubbounds->min, movesubbounds->max, bounds.min, bounds.max)) continue; // if (movesubbounds->max[axis] < bounds.min[axis]) // continue; //(clipped) move bounds is outside this child // if (bounds.max[axis] < movesubbounds->min[axis]) // continue; //(clipped) move bounds is outside this child distnear = bounds.min[axis] - tr->startpos[axis]; nearfrac = (distnear-DIST_EPSILON)/tr->totalmove[axis]; if (nearfrac <= trace_truefraction) { VectorMA(tr->startpos, nearfrac, tr->totalmove, points[0]); //clip the new movebounds (this is more to clip the other axis too) distfar = bounds.max[axis] - tr->startpos[axis]; farfrac = (distfar+DIST_EPSILON)/tr->totalmove[axis]; VectorMA(tr->startpos, farfrac, tr->totalmove, points[1]); //clip the new movebounds (this is more to clip the other axis too) for (a = 0; a < 3; a++) { s = points[0][a] > points[1][a]; newbounds.min[a] = max(movesubbounds->min[a], points[s][a] - tr->expand[a]); newbounds.max[a] = min(movesubbounds->max[a], points[!s][a] + tr->expand[a]); } CM_RecursiveBIHTrace(tr, node+node->bvhnode.firstchild+child, &newbounds, &bounds); } } } } return; #endif } FTE_UNREACHABLE; } //tracebox-with-no-movement, can be a little faster. static void BIH_RecursiveTest (struct bihtrace_s *fte_restrict tr, const struct bihnode_s *fte_restrict node) { //with BIH, its possible for a large child node to have a box larger than its sibling. switch(node->type) { #if defined(Q2BSPS) || defined(Q3BSPS) case BIH_BRUSH: if (node->data.contents & tr->hitcontents) { q2cbrush_t *b = node->data.brush; // if (BIH_BoundsIntersect(tr->bounds.min, tr->bounds.max, b->absmins, b->absmaxs)) BIH_TestBoxInBrush (tr, b); } return; #endif #ifdef Q3BSPS case BIH_PATCHBRUSH: if (node->data.contents & tr->hitcontents) { q2cbrush_t *b = node->data.patchbrush; // if (BIH_BoundsIntersect(tr->bounds.min, tr->bounds.max, b->absmins, b->absmaxs)) BIH_TestBoxInPatch (tr, b); } return; case BIH_TRISOUP: /*if (node->data.contents & tr->hitcontents) // if (BIH_BoundsIntersect(cmesh->absmins, cmesh->absmaxs, tr->bounds.min, tr->bounds.max)) { q3cmesh_t *cmesh = node->data.cmesh; Mod_Trace_Trisoup_(cmesh->xyz_array, cmesh->indicies, cmesh->numincidies, trace_start, trace_end, trace_mins, trace_maxs, &trace_trace, &cmesh->surface->c); }*/ return; #endif case BIH_TRIANGLE: if (node->data.contents & tr->hitcontents) BIH_TestToTriangle(tr, &node->data); return; case BIH_GROUP: { int i; for (i = 0; i < node->group.numchildren; i++) { BIH_RecursiveTest(tr, node+node->group.firstchild+i); if (tr->trace.allsolid) break; } } return; #ifdef BIH_USEBIH case BIH_X: case BIH_Y: case BIH_Z: { //node (x y or z) float min; float max; int axis = node->type - BIH_X; min = node->bihnode.cmin[0] - tr->expand[axis]; max = node->bihnode.cmax[0] + tr->expand[axis]; //expand the bounds according to the player's size //the point can potentially be within both children, or neither. //it doesn't really matter which order we walk the tree, just be sure to do it efficiently. if (min <= tr->startpos[axis] && tr->startpos[axis] <= max) { BIH_RecursiveTest(tr, node+node->bihnode.firstchild+0); if (tr->trace.allsolid) return; } min = node->bihnode.cmin[1] - tr->expand[axis]; max = node->bihnode.cmax[1] + tr->expand[axis]; if (min <= tr->startpos[axis] && tr->startpos[axis] <= max) return BIH_RecursiveTest(tr, node+node->bihnode.firstchild+1); } return; #endif #ifdef BIH_USEBVH case BVH_X: case BVH_Y: case BVH_Z: { //node (x y or z) float min; float max; int axis = node->type - BVH_X; min = node->bvhnode.min[axis] - tr->expand[axis]; max = node->bvhnode.cmax + tr->expand[axis]; //expand the bounds according to the player's size //the point can potentially be within both children, or neither. //it doesn't really matter which order we walk the tree, just be sure to do it efficiently. if (min <= tr->startpos[axis] && tr->startpos[axis] <= max) { CM_RecursiveBIHTest(tr, node+node->bvhnode.firstchild+0); if (trace_trace.allsolid) return; } min = node->bvhnode.cmin - tr->expand[axis]; max = node->bvhnode.max[axis] + tr->expand[axis]; if (min <= tr->startpos[axis] && tr->startpos[axis] <= max) return CM_RecursiveBIHTest(tr, node+node->bvhnode.firstchild+1); } return; #endif } FTE_UNREACHABLE; } static qboolean BIH_Trace(model_t *model, int forcehullnum, const framestate_t *framestate, const vec3_t axis[3], const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, qboolean capsule, unsigned int contents, trace_t *out_trace) { int i; vec3_t point; struct bihtrace_s tr; if (axis) { //rotate everything VectorSet(tr.startpos, DotProduct(start, axis[0]), DotProduct(start, axis[1]), DotProduct(start, axis[2])); VectorSet(tr.endpos, DotProduct(end, axis[0]), DotProduct(end, axis[1]), DotProduct(end, axis[2])); VectorSet(tr.up, axis[0][2], -axis[1][2], axis[2][2]); } else { //axial bboxes. woo. VectorCopy(start, tr.startpos); VectorCopy(end, tr.endpos); VectorSet(tr.up, 0, 0, 1); } // fill in a default trace memset (&tr.trace, 0, sizeof(tr.trace)); tr.trace.fraction = tr.trace.truefraction = 1; tr.trace.surface = &(nullsurface.c); if (model) // map is loaded... { tr.hitcontents = contents; VectorCopy (mins, tr.size.min); VectorCopy (maxs, tr.size.max); if (1) //center the point of the trace in the middle... { VectorAdd(tr.size.max, tr.size.min, point); VectorScale(point, 0.5, point); VectorAdd(tr.startpos, point, tr.startpos); VectorAdd(tr.endpos, point, tr.endpos); VectorSubtract(tr.size.min, point, tr.size.min); VectorSubtract(tr.size.max, point, tr.size.max); } // build a bounding box of the entire move (for patches) ClearBounds (tr.bounds.min, tr.bounds.max); //determine the type of trace that we're going to use, and the max extents if (tr.size.min[0] == 0 && tr.size.min[1] == 0 && tr.size.min[2] == 0 && tr.size.max[0] == 0 && tr.size.max[1] == 0 && tr.size.max[2] == 0) { tr.shape = shape_ispoint; VectorSet (tr.expand, 1/32.0, 1/32.0, 1/32.0); //acedemic AddPointToBounds (tr.startpos, tr.bounds.min, tr.bounds.max); AddPointToBounds (tr.endpos, tr.bounds.min, tr.bounds.max); } else if (capsule) { float ext; tr.shape = shape_iscapsule; //determine the capsule sizes tr.capsulesize[0] = ((tr.size.max[0]-tr.size.min[0]) + (tr.size.max[1]-tr.size.min[1]))/4.0; tr.capsulesize[1] = tr.size.max[2]; tr.capsulesize[2] = tr.size.min[2]; //make sure the mins_z/maxs_z isn't screwed. // if (tr.capsulesize[1]-tr.capsulesize[2] < tr.capsulesize[0]) // tr.capsulesize[1] = tr.capsulesize[0]+tr.capsulesize[2]; ext = (tr.capsulesize[1] > -tr.capsulesize[2])?tr.capsulesize[1]:-tr.capsulesize[2]; tr.capsulesize[1] -= tr.capsulesize[0]; tr.capsulesize[2] += tr.capsulesize[0]; tr.expand[0] = ext+1; tr.expand[1] = ext+1; tr.expand[2] = ext+1; //determine the total range VectorSubtract (tr.startpos, tr.expand, point); AddPointToBounds (point, tr.bounds.min, tr.bounds.max); VectorAdd (tr.startpos, tr.expand, point); AddPointToBounds (point, tr.bounds.min, tr.bounds.max); VectorSubtract (tr.endpos, tr.expand, point); AddPointToBounds (point, tr.bounds.min, tr.bounds.max); VectorAdd (tr.endpos, tr.expand, point); AddPointToBounds (point, tr.bounds.min, tr.bounds.max); } else { VectorAdd (tr.startpos, tr.size.min, point); AddPointToBounds (point, tr.bounds.min, tr.bounds.max); VectorAdd (tr.startpos, tr.size.max, point); AddPointToBounds (point, tr.bounds.min, tr.bounds.max); VectorAdd (tr.endpos, tr.size.min, point); AddPointToBounds (point, tr.bounds.min, tr.bounds.max); VectorAdd (tr.endpos, tr.size.max, point); AddPointToBounds (point, tr.bounds.min, tr.bounds.max); tr.shape = shape_isbox; tr.expand[0] = ((-tr.size.min[0] > tr.size.max[0]) ? -tr.size.min[0] : tr.size.max[0])+1; tr.expand[1] = ((-tr.size.min[1] > tr.size.max[1]) ? -tr.size.min[1] : tr.size.max[1])+1; tr.expand[2] = ((-tr.size.min[2] > tr.size.max[2]) ? -tr.size.min[2] : tr.size.max[2])+1; } tr.bounds.min[0] -= 1.0; tr.bounds.min[1] -= 1.0; tr.bounds.min[2] -= 1.0; tr.bounds.max[0] += 1.0; tr.bounds.max[1] += 1.0; tr.bounds.max[2] += 1.0; for (i = 0; i < 3; i++) tr.negativedir[i] = (tr.endpos[i] - tr.startpos[i]) < 0; VectorSubtract(tr.endpos, tr.startpos, tr.totalmove); if (tr.startpos[0] == tr.endpos[0] && tr.startpos[1] == tr.endpos[1] && tr.startpos[2] == tr.endpos[2]) BIH_RecursiveTest(&tr, model->cnodes); else { struct bihbox_s worldsize; VectorCopy(model->mins, worldsize.min); VectorCopy(model->maxs, worldsize.max); BIH_RecursiveTrace(&tr, model->cnodes, &tr.bounds, &worldsize); } if (tr.trace.fraction<0) tr.trace.fraction=0; } *out_trace = tr.trace; #ifdef TERRAIN if (model->terrain) { //terrain is weird. trace_t hmt; Heightmap_Trace(model, forcehullnum, framestate, NULL, tr.startpos, tr.endpos, mins, maxs, capsule, contents, &hmt); if (hmt.fraction < out_trace->fraction) *out_trace = hmt; } #endif if (out_trace->fraction == 1) { VectorCopy (end, out_trace->endpos); } else { VectorInterpolate(start, out_trace->fraction, end, out_trace->endpos); //too lazy to compute the endpos for each impact if (axis) { vec3_t iaxis[3]; vec3_t norm; Matrix3x3_RM_Invert_Simple((const void *)axis, iaxis); VectorCopy(out_trace->plane.normal, norm); out_trace->plane.normal[0] = DotProduct(norm, iaxis[0]); out_trace->plane.normal[1] = DotProduct(norm, iaxis[1]); out_trace->plane.normal[2] = DotProduct(norm, iaxis[2]); /*just interpolate it, its easier than inverse matrix rotations*/ VectorInterpolate(start, out_trace->fraction, end, out_trace->endpos); } } return out_trace->fraction != 1; } //simplest form. no movement, no size. unsigned int BIH_TestContents (const struct bihnode_s *fte_restrict node, const vec3_t p) { restart: switch(node->type) { //leaf #if defined(Q2BSPS) || defined(Q3BSPS) case BIH_BRUSH: { q2cbrush_t *b = node->data.brush; q2cbrushside_t *brushside = b->brushside; size_t j; if (!BIH_BoundsIntersect(p, p, b->absmins, b->absmaxs)) return 0; for ( j = 0; j < b->numsides; j++, brushside++ ) { if ( PlaneDiff (p, brushside->plane) > 0 ) return 0; } return b->contents; //inside all planes } #endif #ifdef Q3BSPS case BIH_PATCHBRUSH: { //patches have no contents... return 0; } case BIH_TRISOUP: { //trisoup has no contents... depending upon epsilons would be crazy. return 0; } #endif case BIH_TRIANGLE: return 0; case BIH_GROUP: { int i; unsigned int contents = 0; for (i = 0; i < node->group.numchildren; i++) contents |= BIH_TestContents(node+node->group.firstchild+i, p); return contents; } #ifdef BIH_USEBIH case BIH_X: case BIH_Y: case BIH_Z: { //node (x y or z) unsigned int axis = node->type - BIH_X; //the point can potentially be within both children, or neither. //it doesn't really matter which order we walk the tree, just be sure to do it efficiently. if (node->bihnode.cmin[0] <= p[axis] && p[axis] <= node->bihnode.cmax[0]) { if (node->bihnode.cmin[1] <= p[axis] && p[axis] <= node->bihnode.cmax[1]) { //need to walk both return BIH_TestContents(node+node->bihnode.firstchild+0, p) | BIH_TestContents(node+node->bihnode.firstchild+1, p); } //only need the left side. node = node+node->bihnode.firstchild+0; goto restart; } else { if (node->bihnode.cmin[1] <= p[axis] && p[axis] <= node->bihnode.cmax[1]) ; else return 0; //walk neither. //only need to walk the right node = node+node->bihnode.firstchild+1; goto restart; } } #endif #ifdef BIH_USEBVH case BVH_X: case BVH_Y: case BVH_Z: { //node (x y or z) unsigned int contents; unsigned int axis = node->type - BVH_X; //the point can potentially be within both children, or neither. //it doesn't really matter which order we walk the tree, just be sure to do it efficiently. if (node->bvhnode.min[axis] <= p[axis] && p[axis] <= node->bvhnode.cmax) contents = BIH_TestContents(node+node->bvhnode.firstchild+0, p); else contents = 0; if (node->bvhnode.cmin <= p[axis] && p[axis] <= node->bvhnode.max[axis]) contents |= BIH_TestContents(node+node->bvhnode.firstchild+1, p); return contents; } #endif } FTE_UNREACHABLE; } static unsigned int BIH_PointContents(struct model_s *mod, const vec3_t axis[3], const vec3_t p) { unsigned int contents; vec3_t n; if (axis) { VectorSet(n, DotProduct(p, axis[0]), DotProduct(p, axis[1]), DotProduct(p, axis[2])); p = n; } contents = BIH_TestContents (mod->cnodes, p); #ifdef TERRAIN if (mod->terrain) contents |= Heightmap_PointContents(mod, NULL, p); #endif return contents; } static unsigned int BIH_NativeContents(struct model_s *mod, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t p, const vec3_t mins, const vec3_t maxs) { //we don't support boxcontents... sorry. unsigned int contents; vec3_t n; if (axis) { VectorSet(n, DotProduct(p, axis[0]), DotProduct(p, axis[1]), DotProduct(p, axis[2])); p = n; } contents = BIH_TestContents (mod->cnodes, p); #ifdef TERRAIN if (mod->terrain) contents |= Heightmap_PointContents(mod, NULL, p); #endif return contents; } #if defined(BIH_USEBIH) || defined(BIH_USEBVH) static int QDECL BIH_Sort_X (const void *va, const void *vb) { const struct bihleaf_s *a = va, *b = vb; float am = a->maxs[0]+a->mins[0]; float bm = b->maxs[0]+b->mins[0]; if (am == bm) return 0; return am > bm; } static int QDECL BIH_Sort_Y (const void *va, const void *vb) { const struct bihleaf_s *a = va, *b = vb; float am = a->maxs[1]+a->mins[1]; float bm = b->maxs[1]+b->mins[1]; if (am == bm) return 0; return am > bm; } static int QDECL BIH_Sort_Z (const void *va, const void *vb) { const struct bihleaf_s *a = va, *b = vb; float am = a->maxs[2]+a->mins[2]; float bm = b->maxs[2]+b->mins[2]; if (am == bm) return 0; return am > bm; } #endif static struct bihbox_s BIH_BuildNode (struct bihnode_s *node, struct bihnode_s **freenodes, struct bihleaf_s *leafs, size_t numleafs) { struct bihbox_s bounds; if (numleafs == 1) //the leaf just gives the brush pointer. { size_t i; VectorCopy(leafs[0].mins, bounds.min); VectorCopy(leafs[0].maxs, bounds.max); node->type = leafs[0].type; node->data = leafs[0].data; //expand by 1qu, to avoid precision issues. for (i = 0; i < 3; i++) { bounds.min[i] -= 1; bounds.max[i] += 1; } } #ifdef BIH_USEBIH else if (numleafs >= 8) //the leaf just gives the brush pointer. { size_t i, j; size_t numleft = numleafs / 2; //this ends up splitting at the median point. size_t numright = numleafs - numleft; struct bihbox_s left, right; struct bihnode_s *cnodes; static int (QDECL *sorts[3]) (const void *va, const void *vb) = {BIH_Sort_X, BIH_Sort_Y, BIH_Sort_Z}; VectorCopy(leafs[0].mins, bounds.min); VectorCopy(leafs[0].maxs, bounds.max); for (i = 1; i < numleafs; i++) { for(j = 0; j < 3; j++) { if (bounds.min[j] > leafs[i].mins[j]) bounds.min[j] = leafs[i].mins[j]; if (bounds.max[j] < leafs[i].maxs[j]) bounds.max[j] = leafs[i].maxs[j]; } } #if 1 { //balanced by counts vec3_t mid; int onleft[3], onright[3], weight[3]; VectorAvg(bounds.max, bounds.min, mid); VectorClear(onleft); VectorClear(onright); for (i = 0; i < numleafs; i++) { for (j = 0; j < 3; j++) { //ignore leafs that split the node. if (leafs[i].maxs[j] < mid[j]) onleft[j]++; if (mid[j] > leafs[i].mins[j]) onright[j]++; } } for (j = 0; j < 3; j++) weight[j] = onleft[j]+onright[j] - abs(onleft[j]-onright[j]); //pick the most balanced. if (weight[0] > weight[1] && weight[0] > weight[2]) node->type = BIH_X; else if (weight[1] > weight[2]) node->type = BIH_Y; else node->type = BIH_Z; } #else { //balanced by volume vec3_t size; VectorSubtract(bounds.max, bounds.min, size); if (size[0] > size[1] && size[0] > size[2]) node->type = BIH_X; else if (size[1] > size[2]) node->type = BIH_Y; else node->type = BIH_Z;*/ } #endif qsort(leafs, numleafs, sizeof(*leafs), sorts[node->type-BIH_X]); cnodes = *freenodes; *freenodes += 2; node->bihnode.firstchild = cnodes - node; left = BIH_BuildNode (cnodes+0, freenodes, leafs, numleft); right = BIH_BuildNode (cnodes+1, freenodes, &leafs[numleft], numright); node->bihnode.cmin[0] = left.min[node->type-BIH_X]; node->bihnode.cmax[0] = left.max[node->type-BIH_X]; node->bihnode.cmin[1] = right.min[node->type-BIH_X]; node->bihnode.cmax[1] = right.max[node->type-BIH_X]; bounds = left; AddPointToBounds(right.min, bounds.min, bounds.max); AddPointToBounds(right.max, bounds.min, bounds.max); } #endif #ifdef BIH_USEBVH else if (numleafs >= 8) //the leaf just gives the brush pointer. { size_t i, j; size_t numleft = numleafs / 2; //this ends up splitting at the median point. size_t numright = numleafs - numleft; struct bihbox_s left, right; struct bihnode_s *cnodes; static int (QDECL *sorts[3]) (const void *va, const void *vb) = {CM_SortBIH_X, CM_SortBIH_Y, CM_SortBIH_Z}; VectorCopy(leafs[0].mins, bounds.min); VectorCopy(leafs[0].maxs, bounds.max); for (i = 1; i < numleafs; i++) { for(j = 0; j < 3; j++) { if (bounds.min[j] > leafs[i].mins[j]) bounds.min[j] = leafs[i].mins[j]; if (bounds.max[j] < leafs[i].maxs[j]) bounds.max[j] = leafs[i].maxs[j]; } } #if 1 { //balanced by counts vec3_t mid; int onleft[3], onright[3], weight[3]; VectorAvg(bounds.max, bounds.min, mid); VectorClear(onleft); VectorClear(onright); for (i = 0; i < numleafs; i++) { for (j = 0; j < 3; j++) { //ignore leafs that split the node. if (leafs[i].maxs[j] < mid[j]) onleft[j]++; if (mid[j] > leafs[i].mins[j]) onright[j]++; } } for (j = 0; j < 3; j++) weight[j] = onleft[j]+onright[j] - abs(onleft[j]-onright[j]); //pick the most balanced. if (weight[0] > weight[1] && weight[0] > weight[2]) node->type = BVH_X; else if (weight[1] > weight[2]) node->type = BVH_Y; else node->type = BVH_Z; } #else { //balanced by volume vec3_t size; VectorSubtract(bounds.max, bounds.min, size); if (size[0] > size[1] && size[0] > size[2]) node->type = BVH_X; else if (size[1] > size[2]) node->type = BVH_Y; else node->type = BVH_Z;*/ } #endif qsort(leafs, numleafs, sizeof(*leafs), sorts[node->type-BVH_X]); cnodes = *freenodes; *freenodes += 2; node->bvhnode.firstchild = cnodes - node; left = BIH_BuildNode (cnodes+0, freenodes, leafs, numleft); right = BIH_BuildNode (cnodes+1, freenodes, &leafs[numleft], numright); node->bvhnode.min[0] = min(left.min[0], right.min[0]); node->bvhnode.min[1] = min(left.min[1], right.min[1]); node->bvhnode.min[2] = min(left.min[2], right.min[2]); node->bvhnode.cmax = left.max[node->type-BVH_X]; node->bvhnode.cmin = right.min[node->type-BVH_X]; node->bvhnode.max[0] = max(left.max[0], right.max[0]); node->bvhnode.max[1] = max(left.max[1], right.max[1]); node->bvhnode.max[2] = max(left.max[2], right.max[2]); bounds = left; AddPointToBounds(right.min, bounds.min, bounds.max); AddPointToBounds(right.max, bounds.min, bounds.max); } #endif else { struct bihnode_s *cnodes; struct bihbox_s cb; size_t i; node->type = BIH_GROUP; cnodes = *freenodes; *freenodes += numleafs; node->group.firstchild = cnodes - node; node->group.numchildren = numleafs; bounds = BIH_BuildNode(cnodes+0, freenodes, leafs+0, 1); for (i = 1; i < numleafs; i++) { cb = BIH_BuildNode(cnodes+i, freenodes, leafs+i, 1); AddPointToBounds(cb.min, bounds.min, bounds.max); AddPointToBounds(cb.max, bounds.min, bounds.max); } } return bounds; } void BIH_Build (model_t *mod, struct bihleaf_s *leafs, size_t numleafs) { size_t numnodes; struct bihnode_s *nodes, *tmpnodes; if (!numleafs) { //if we don't actually have anything solid, we still need SOMETHING so we don't crash. //we can just use an empty group node for that. nodes = ZG_Malloc(&mod->memgroup, sizeof(*nodes)); nodes->type = BIH_GROUP; nodes->group.numchildren = 0; } else { numnodes = numleafs*2-1; nodes = ZG_Malloc(&mod->memgroup, sizeof(*nodes)*numnodes); tmpnodes = nodes+1; BIH_BuildNode(nodes, &tmpnodes, leafs, numleafs); if (tmpnodes > nodes+numnodes) Sys_Error("CM_BuildBIH: generated wrong number of nodes"); } mod->cnodes = nodes; mod->funcs.NativeTrace = BIH_Trace; mod->funcs.PointContents = BIH_PointContents; mod->funcs.NativeContents = BIH_NativeContents; } void BIH_BuildAlias (model_t *mod, galiasinfo_t *meshes) { size_t numleafs, i; struct bihleaf_s *leafs, *leaf; galiasinfo_t *submesh; numleafs = 0; for (submesh = meshes; submesh; submesh = submesh->nextsurf) numleafs+=submesh->numindexes/3; leaf = leafs = BZ_Malloc(sizeof(*leafs)*numleafs); for (submesh = meshes; submesh; submesh = submesh->nextsurf) { for (i = 0; i < submesh->numindexes; i+=3) { vec_t *v1,*v2,*v3; leaf->type = BIH_TRIANGLE; leaf->data.contents = submesh->contents; leaf->data.tri.indexes = submesh->ofs_indexes+i; leaf->data.tri.xyz = submesh->ofs_skel_xyz; v1 = leaf->data.tri.xyz[leaf->data.tri.indexes[0]]; v2 = leaf->data.tri.xyz[leaf->data.tri.indexes[1]]; v3 = leaf->data.tri.xyz[leaf->data.tri.indexes[2]]; VectorCopy(v1, leaf->mins); VectorCopy(v1, leaf->maxs); AddPointToBounds(v2, leaf->mins, leaf->maxs); AddPointToBounds(v3, leaf->mins, leaf->maxs); leaf++; } } BIH_Build(mod, leafs, leaf-leafs); }