void CM_HandlePatchCollision(struct traceWork_s *tw, trace_t &trace, const vec3_t tStart, const vec3_t tEnd, CCMPatch *patch, int checkcount) { int numBrushes, i; cbrush_t *brush; // Get the collision data brush = patch->GetCollisionData(); numBrushes = patch->GetNumBrushes(); for(i = 0; i < numBrushes; i++, brush++) { if(brush->checkcount == checkcount) { return; } // Generic collision of terxel bounds to line segment bounds if(!CM_GenericBoxCollide(brush->bounds, tw->localBounds)) { continue; } brush->checkcount = checkcount; //CM_TraceThroughBrush(tw, trace, brush, false ); CM_TraceThroughBrush(tw, brush); if (trace.fraction <= 0.0) { break; } } }
/* ================ CM_ProximityToBrush ================ */ static void CM_ProximityToBrush( traceWork_t *tw, cbrush_t *brush ) { int i; cbrushedge_t *edge; float dist, minDist = 1e+10f; float s, t; float sAtMin = 0.0f; float radius = 0.0f, fraction; traceWork_t tw2; // cheapish purely linear trace to test for intersection Com_Memset( &tw2, 0, sizeof( tw2 ) ); tw2.trace.fraction = 1.0f; tw2.type = TT_CAPSULE; tw2.sphere.radius = 0.0f; VectorClear( tw2.sphere.offset ); VectorCopy( tw->start, tw2.start ); VectorCopy( tw->end, tw2.end ); CM_TraceThroughBrush( &tw2, brush ); if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid ) { for( i = 0; i < brush->numEdges; i++ ) { edge = &brush->edges[ i ]; dist = DistanceBetweenLineSegmentsSquared( tw->start, tw->end, edge->p0, edge->p1, &s, &t ); if( dist < minDist ) { minDist = dist; sAtMin = s; } } if( tw->type == TT_BISPHERE ) { radius = tw->biSphere.startRadius + ( sAtMin * ( tw->biSphere.endRadius - tw->biSphere.startRadius ) ); } else if( tw->type == TT_CAPSULE ) { radius = tw->sphere.radius; } else if( tw->type == TT_AABB ) { //FIXME } fraction = minDist / ( radius * radius ); if( fraction < tw->trace.lateralFraction ) tw->trace.lateralFraction = fraction; } else tw->trace.lateralFraction = 0.0f; }
/* ================ CM_TraceThroughLeaf ================ */ static void CM_TraceThroughLeaf(traceWork_t *tw, cLeaf_t *leaf) { int k; cbrush_t *brush; float fraction; // trace line against all brushes in the leaf for (k = 0 ; k < leaf->numLeafBrushes ; k++) { // brushnum = cm.leafbrushes[leaf->firstLeafBrush + k]; brush = &cm.brushes[cm.leafbrushes[leaf->firstLeafBrush + k]]; if (brush->checkcount == cm.checkcount) { continue; // already checked this brush in another leaf } brush->checkcount = cm.checkcount; if (!(brush->contents & tw->contents)) { continue; } if (cm_optimize->integer) { if (!CM_TraceThroughBounds(tw, brush->bounds[0], brush->bounds[1])) { continue; } } fraction = tw->trace.fraction; CM_TraceThroughBrush(tw, brush); if (!tw->trace.fraction) { return; } if (tw->trace.fraction < fraction) { CM_CalcTraceBounds(tw, qtrue); } } // trace line against all patches in the leaf if (!cm_noCurves->integer) { cPatch_t *patch; for (k = 0 ; k < leaf->numLeafSurfaces ; k++) { patch = cm.surfaces[cm.leafsurfaces[leaf->firstLeafSurface + k]]; if (!patch) { continue; } if (patch->checkcount == cm.checkcount) { continue; // already checked this patch in another leaf } patch->checkcount = cm.checkcount; if (!(patch->contents & tw->contents)) { continue; } if (cm_optimize->integer) { if (!CM_TraceThroughBounds(tw, patch->pc->bounds[0], patch->pc->bounds[1])) { continue; } } fraction = tw->trace.fraction; CM_TraceThroughPatch(tw, patch); if (!tw->trace.fraction) { return; } if (tw->trace.fraction < fraction) { CM_CalcTraceBounds(tw, qtrue); } } } }
static void CM_TraceThroughLeaf( traceWork_t* tw, const cLeaf_t* leaf ) { int k; int brushnum; cbrush_t *b; cPatch_t *patch; // trace line against all brushes in the leaf for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; b = &cm.brushes[brushnum]; if ( b->checkcount == cm.checkcount ) { continue; // already checked this brush in another leaf } b->checkcount = cm.checkcount; if ( !(b->contents & tw->contents) ) { continue; } if (!CM_BoundsIntersect( tw->bounds[0], tw->bounds[1], b->bounds[0], b->bounds[1] )) continue; CM_TraceThroughBrush( tw, b ); if ( !tw->trace.fraction ) { return; } } // trace line against all patches in the leaf #ifdef BSPC if (1) { #else if ( !cm_noCurves->integer ) { #endif for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; if ( !patch ) { continue; } if ( patch->checkcount == cm.checkcount ) { continue; // already checked this patch in another leaf } patch->checkcount = cm.checkcount; if ( !(patch->contents & tw->contents) ) { continue; } CM_TraceThroughPatch( tw, patch ); if ( !tw->trace.fraction ) { return; } } } } #define RADIUS_EPSILON 1.0f /* ================ CM_TraceThroughSphere get the first intersection of the ray with the sphere ================ */ void CM_TraceThroughSphere( traceWork_t *tw, vec3_t origin, float radius, vec3_t start, vec3_t end ) { float l1, l2, length, scale, fraction; float a, b, c, d, sqrtd; vec3_t v1, dir, intersection; // if inside the sphere VectorSubtract(start, origin, dir); l1 = VectorLengthSquared(dir); if (l1 < Square(radius)) { tw->trace.fraction = 0; tw->trace.startsolid = qtrue; // test for allsolid VectorSubtract(end, origin, dir); l1 = VectorLengthSquared(dir); if (l1 < Square(radius)) { tw->trace.allsolid = qtrue; } return; } // VectorSubtract(end, start, dir); length = VectorNormalize(dir); // l1 = CM_DistanceFromLineSquared(origin, start, end, dir); VectorSubtract(end, origin, v1); l2 = VectorLengthSquared(v1); // if no intersection with the sphere and the end point is at least an epsilon away if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { return; } // // | origin - (start + t * dir) | = radius // a = dir[0]^2 + dir[1]^2 + dir[2]^2; // b = 2 * (dir[0] * (start[0] - origin[0]) + dir[1] * (start[1] - origin[1]) + dir[2] * (start[2] - origin[2])); // c = (start[0] - origin[0])^2 + (start[1] - origin[1])^2 + (start[2] - origin[2])^2 - radius^2; // VectorSubtract(start, origin, v1); // dir is normalized so a = 1 a = 1.0f;//dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]; b = 2.0f * (dir[0] * v1[0] + dir[1] * v1[1] + dir[2] * v1[2]); c = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); d = b * b - 4.0f * c;// * a; if (d > 0) { sqrtd = SquareRootFloat(d); // = (- b + sqrtd) * 0.5f; // / (2.0f * a); fraction = (- b - sqrtd) * 0.5f; // / (2.0f * a); // if (fraction < 0) { fraction = 0; } else { fraction /= length; } if ( fraction < tw->trace.fraction ) { tw->trace.fraction = fraction; VectorSubtract(end, start, dir); VectorMA(start, fraction, dir, intersection); VectorSubtract(intersection, origin, dir); #ifdef CAPSULE_DEBUG l2 = VectorLength(dir); if (l2 < radius) { int bah = 1; } #endif scale = 1 / (radius+RADIUS_EPSILON); VectorScale(dir, scale, dir); VectorCopy(dir, tw->trace.plane.normal); VectorAdd( tw->modelOrigin, intersection, intersection); tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection); tw->trace.contents = CONTENTS_BODY; } } else if (d == 0) { //t1 = (- b ) / 2; // slide along the sphere } // no intersection at all }
/* ================ CM_TraceToLeaf ================ */ void CM_TraceToLeaf( traceWork_t *tw, cLeaf_t *leaf, clipMap_t *local ) { int k; int brushnum; cbrush_t *b; cPatch_t *patch; // trace line against all brushes in the leaf for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { brushnum = local->leafbrushes[leaf->firstLeafBrush+k]; b = &local->brushes[brushnum]; if ( b->checkcount == local->checkcount ) { continue; // already checked this brush in another leaf } b->checkcount = local->checkcount; if ( !(b->contents & tw->contents) ) { continue; } #ifndef BSPC #ifndef _XBOX // Removing terrain from Xbox if ( com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) ) { // Invalidate the checkcount for terrain as the terrain brush has to be processed // many times. b->checkcount--; CM_TraceThroughTerrain( tw, tw->trace, b ); // If inside a terrain brush don't bother with regular brush collision continue; } #endif #endif //if (b->contents & CONTENTS_PLAYERCLIP) continue; CM_TraceThroughBrush( tw, b ); if ( !tw->trace.fraction ) { return; } } // trace line against all patches in the leaf #ifdef BSPC if (1) { #else if ( !cm_noCurves->integer ) { #endif for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { #ifdef _XBOX int index = CM_GetSurfaceIndex(leaf->firstLeafSurface + k); patch = cmg.surfaces[ index ]; #else patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ]; #endif if ( !patch ) { continue; } if ( patch->checkcount == local->checkcount ) { continue; // already checked this patch in another leaf } patch->checkcount = local->checkcount; if ( !(patch->contents & tw->contents) ) { continue; } CM_TraceThroughPatch( tw, patch ); if ( !tw->trace.fraction ) { return; } } } } //========================================================================================= /* ================== CM_TraceThroughTree Traverse all the contacted leafs from the start to the end position. If the trace is a point, they will be exactly in order, but for larger trace volumes it is possible to hit something in a later leaf with a smaller intercept fraction. ================== */ void CM_TraceThroughTree( traceWork_t *tw, clipMap_t *local, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) { cNode_t *node; cplane_t *plane; float t1, t2, offset; float frac, frac2; float idist; vec3_t mid; int side; float midf; #ifdef _XBOX if(!tr.world) { return; } #endif if (tw->trace.fraction <= p1f) { return; // already hit something nearer } // if < 0, we are in a leaf node if (num < 0) { CM_TraceToLeaf( tw, &local->leafs[-1-num], local ); return; } // // find the point distances to the seperating plane // and the offset for the size of the box // node = local->nodes + num; #ifdef _XBOX plane = cmg.planes + tr.world->nodes[num].planeNum; #else plane = node->plane; #endif #if 0 // uncomment this to test against every leaf in the world for debugging CM_TraceThroughTree( tw, local, node->children[0], p1f, p2f, p1, p2 ); CM_TraceThroughTree( tw, local, node->children[1], p1f, p2f, p1, p2 ); return; #endif // adjust the plane distance apropriately for mins/maxs if ( plane->type < 3 ) { t1 = p1[plane->type] - plane->dist; t2 = p2[plane->type] - plane->dist; offset = tw->extents[plane->type]; } else { t1 = DotProduct (plane->normal, p1) - plane->dist; t2 = DotProduct (plane->normal, p2) - plane->dist; if ( tw->isPoint ) { offset = 0; } else { // an axial brush right behind a slanted bsp plane // will poke through when expanded, so adjust // by sqrt(3) offset = fabs(tw->extents[0]*plane->normal[0]) + fabs(tw->extents[1]*plane->normal[1]) + fabs(tw->extents[2]*plane->normal[2]); offset *= 2; #if 0 CM_TraceThroughTree( tw, local, node->children[0], p1f, p2f, p1, p2 ); CM_TraceThroughTree( tw, local, node->children[1], p1f, p2f, p1, p2 ); return; #endif offset = tw->maxOffset; offset = 2048; } } // see which sides we need to consider if ( t1 >= offset + 1 && t2 >= offset + 1 ) { CM_TraceThroughTree( tw, local, node->children[0], p1f, p2f, p1, p2 ); return; } if ( t1 < -offset - 1 && t2 < -offset - 1 ) { CM_TraceThroughTree( tw, local, node->children[1], p1f, p2f, p1, p2 ); return; } // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side if ( t1 < t2 ) { idist = 1.0/(t1-t2); side = 1; frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist; } else if (t1 > t2) { idist = 1.0/(t1-t2); side = 0; frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist; frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; } else { side = 0; frac = 1; frac2 = 0; } // move up to the node if ( frac < 0 ) { frac = 0; } if ( frac > 1 ) { frac = 1; } midf = p1f + (p2f - p1f)*frac; mid[0] = p1[0] + frac*(p2[0] - p1[0]); mid[1] = p1[1] + frac*(p2[1] - p1[1]); mid[2] = p1[2] + frac*(p2[2] - p1[2]); CM_TraceThroughTree( tw, local, node->children[side], p1f, midf, p1, mid ); // go past the node if ( frac2 < 0 ) { frac2 = 0; } if ( frac2 > 1 ) { frac2 = 1; } midf = p1f + (p2f - p1f)*frac2; mid[0] = p1[0] + frac2*(p2[0] - p1[0]); mid[1] = p1[1] + frac2*(p2[1] - p1[1]); mid[2] = p1[2] + frac2*(p2[2] - p1[2]); CM_TraceThroughTree( tw, local, node->children[side^1], midf, p2f, mid, p2 ); }
void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, cbrush_t *brush ) { CCMLandScape *landscape; vec3_t tBegin, tEnd, tDistance, tStep; vec3_t baseStart; vec3_t baseEnd; int count; int i; float fraction; // At this point we know we may be colliding with a terrain brush (and we know we have a valid terrain structure) landscape = cmg.landScape; if (!landscape) { assert(landscape); Com_Error(ERR_FATAL,"Brush had surfaceparm terrain, but there is no Terrain entity on this map!"); } // Check for absolutely no connection if(!CM_GenericBoxCollide(tw->bounds, landscape->GetBounds())) { return; } // Now we know that at least some part of the trace needs to collide with the terrain // The regular brush collision is handled elsewhere, so advance the ray to an edge in the terrain brush CM_TraceThroughBrush( tw, trace, brush, true ); // Remember the base entering and leaving fractions tw->baseEnterFrac = tw->enterFrac; tw->baseLeaveFrac = tw->leaveFrac; // Reset to full spread within the brush tw->enterFrac = -1.0f; tw->leaveFrac = 1.0f; // Work out the corners of the AABB when the trace first hits the terrain brush and when it leaves VectorAdvance(tw->start, tw->baseEnterFrac, tw->end, tBegin); VectorAdvance(tw->start, tw->baseLeaveFrac, tw->end, tEnd); VectorSubtract(tEnd, tBegin, tDistance); // Calculate number of iterations to process count = ceilf(VectorLength(tDistance) / (landscape->GetPatchScalarSize() * TERRAIN_STEP_MAGIC)); count = 1; fraction = trace.fraction; VectorScale(tDistance, 1.0f / count, tStep); // Save the base start and end vectors VectorCopy ( tw->start, baseStart ); VectorCopy ( tw->end, baseEnd ); // Use the terrain vectors. Start both at the beginning since the // step will be added to the end as the first step of the loop VectorCopy ( tBegin, tw->start ); VectorCopy ( tBegin, tw->end ); // Step thru terrain patches moving on about 1 patch at a time for ( i = 0; i < count; i ++ ) { // Add the step to the end VectorAdd(tw->end, tStep, tw->end); CM_CalcExtents(tBegin, tw->end, tw, tw->localBounds); landscape->PatchCollide(tw, trace, tw->start, tw->end, brush->checkcount); // If collision with something closer than water then just stop here if ( trace.fraction < fraction ) { // Convert the fraction of this sub tract into the full trace's fraction trace.fraction = i * (1.0f / count) + (1.0f / count) * trace.fraction; break; } // Move the end to the start so the next trace starts // where this one left off VectorCopy(tw->end, tw->start); } // Put the original start and end back VectorCopy ( baseStart, tw->start ); VectorCopy ( baseEnd, tw->end ); // Convert to global fraction only if something was hit along the way if ( trace.fraction != 1.0 ) { trace.fraction = tw->baseEnterFrac + ((tw->baseLeaveFrac - tw->baseEnterFrac) * trace.fraction); trace.contents = brush->contents; } // Collide with any water if ( tw->contents & CONTENTS_WATER ) { fraction = landscape->WaterCollide(tw->start, tw->end, trace.fraction); if( fraction < trace.fraction ) { VectorSet(trace.plane.normal, 0.0f, 0.0f, 1.0f); trace.contents = landscape->GetWaterContents(); trace.fraction = fraction; trace.surfaceFlags = landscape->GetWaterSurfaceFlags(); } } }
/* ================ CM_TraceToLeaf ================ */ void CM_TraceToLeaf( traceWork_t *tw, trace_t &trace, cLeaf_t *leaf, clipMap_t *local ) { int k; int brushnum; cbrush_t *b; cPatch_t *patch; // trace line against all brushes in the leaf for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { brushnum = local->leafbrushes[leaf->firstLeafBrush + k]; b = &local->brushes[brushnum]; if ( b->checkcount == local->checkcount ) { continue; // already checked this brush in another leaf } b->checkcount = local->checkcount; if ( !(b->contents & tw->contents) ) { continue; } CM_TraceThroughBrush( tw, trace, b, false); if ( !trace.fraction ) { return; } } // trace line against all patches in the leaf #ifdef BSPC if (1) { #else if ( !cm_noCurves->integer ) { #endif for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ]; if ( !patch ) { continue; } if ( patch->checkcount == local->checkcount ) { continue; // already checked this patch in another leaf } patch->checkcount = local->checkcount; if ( !(patch->contents & tw->contents) ) { continue; } CM_TraceThroughPatch( tw, trace, patch ); if ( !trace.fraction ) { return; } } } } /* ================== CM_TraceThroughTree Traverse all the contacted leafs from the start to the end position. If the trace is a point, they will be exactly in order, but for larger trace volumes it is possible to hit something in a later leaf with a smaller intercept fraction. ================== */ void CM_TraceThroughTree( traceWork_t *tw, trace_t &trace, clipMap_t *local, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) { cNode_t *node; cplane_t *plane; float t1, t2, offset; float frac, frac2; float idist; vec3_t mid; int side; float midf; if (trace.fraction <= p1f) { return; // already hit something nearer } // if < 0, we are in a leaf node if (num < 0) { CM_TraceThroughLeaf( tw, trace, local, &local->leafs[-1-num] ); return; } // // find the point distances to the seperating plane // and the offset for the size of the box // node = local->nodes + num; plane = node->plane; // adjust the plane distance appropriately for mins/maxs if ( plane->type < 3 ) { t1 = p1[plane->type] - plane->dist; t2 = p2[plane->type] - plane->dist; offset = tw->extents[plane->type]; } else { t1 = DotProduct (plane->normal, p1) - plane->dist; t2 = DotProduct (plane->normal, p2) - plane->dist; if ( tw->isPoint ) { offset = 0; } else { #if 0 // bk010201 - DEAD // an axial brush right behind a slanted bsp plane // will poke through when expanded, so adjust // by sqrt(3) offset = fabs(tw->extents[0]*plane->normal[0]) + fabs(tw->extents[1]*plane->normal[1]) + fabs(tw->extents[2]*plane->normal[2]); offset *= 2; offset = tw->maxOffset; #endif // this is silly offset = 2048; } } // see which sides we need to consider if ( t1 >= offset + 1 && t2 >= offset + 1 ) { CM_TraceThroughTree( tw, trace, local, node->children[0], p1f, p2f, p1, p2 ); return; } if ( t1 < -offset - 1 && t2 < -offset - 1 ) { CM_TraceThroughTree( tw, trace, local, node->children[1], p1f, p2f, p1, p2 ); return; } // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side if ( t1 < t2 ) { idist = 1.0/(t1-t2); side = 1; frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist; } else if (t1 > t2) { idist = 1.0/(t1-t2); side = 0; frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist; frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; } else { side = 0; frac = 1; frac2 = 0; } // move up to the node if ( frac < 0 ) { frac = 0; } if ( frac > 1 ) { frac = 1; } midf = p1f + (p2f - p1f)*frac; mid[0] = p1[0] + frac*(p2[0] - p1[0]); mid[1] = p1[1] + frac*(p2[1] - p1[1]); mid[2] = p1[2] + frac*(p2[2] - p1[2]); CM_TraceThroughTree( tw, trace, local, node->children[side], p1f, midf, p1, mid ); // go past the node if ( frac2 < 0 ) { frac2 = 0; } if ( frac2 > 1 ) { frac2 = 1; } midf = p1f + (p2f - p1f)*frac2; mid[0] = p1[0] + frac2*(p2[0] - p1[0]); mid[1] = p1[1] + frac2*(p2[1] - p1[1]); mid[2] = p1[2] + frac2*(p2[2] - p1[2]); CM_TraceThroughTree( tw, trace, local, node->children[side^1], midf, p2f, mid, p2 ); }