TraceResult CheckNode( int nodeIndex, float startFraction, float endFraction, const Vec3& start, const Vec3& end, const Vec3& extents, const TraceBounds& boundsAabb, TraceResult result, const Bsp::CollisionBsp& bsp) { if (result.pathFraction <= startFraction) { // already hit something nearer return result; } if (nodeIndex < 0) { // this is a leaf const auto& leaf = bsp.leaves[-(nodeIndex + 1)]; for (int i = 0; i < leaf.leafBrushCount; i++) { const auto& brush = bsp.brushes[bsp.leafBrushes[leaf.firstLeafBrushIndex + i].brushIndex]; // Don't even bother if there are no brush sides. if (brush.brush.sideCount <= 0) { continue; } // Only test solid brushes // 1 == CONTENTS_SOLID if (!(bsp.textures[brush.brush.textureIndex].contentFlags & 1)) { continue; } // Early exit if the AABB doesn't collide. if (AabbDontIntersect( boundsAabb.aabbMin, boundsAabb.aabbMax, brush.aabbMin, brush.aabbMax)) { continue; } result = CheckBrush(bsp, brush.brush, boundsAabb.bounds, result); } // don't have to do anything else for leaves return result; } // this is a node const auto& node = bsp.nodes[nodeIndex]; const auto& plane = bsp.planes[node.planeIndex]; float startDistance = DotF(start, plane.normal) - plane.distance; float endDistance = DotF(end, plane.normal) - plane.distance; // Offset used for non-ray tests. const auto& bounds = boundsAabb.bounds; float offset = bounds.sphereRadius; // extents are zero for ray or sphere tests. offset += std::abs(extents.data[0] * plane.normal.data[0]) + std::abs(extents.data[1] * plane.normal.data[1]) + std::abs(extents.data[2] * plane.normal.data[2]); if (startDistance >= offset && endDistance >= offset) { // both points are in front of the plane // so check the front child return CheckNode( node.childIndex[0], startFraction, endFraction, start, end, extents, boundsAabb, result, bsp); } if (startDistance < -offset && endDistance < -offset) { // both points are behind the plane // so check the back child return CheckNode( node.childIndex[1], startFraction, endFraction, start, end, extents, boundsAabb, result, bsp); } // the line spans the splitting plane // Default values assume startDistance == endDistance. int side = 0; float fraction1 = 1.0f; float fraction2 = 0.0f; // split the segment into two if (startDistance < endDistance) { // back side = 1; float inverseDistance = 1.0f / (startDistance - endDistance); fraction1 = (startDistance - offset + EPSILON) * inverseDistance; fraction2 = (startDistance + offset + EPSILON) * inverseDistance; } if (endDistance < startDistance) { // front float inverseDistance = 1.0f / (startDistance - endDistance); fraction1 = (startDistance + offset + EPSILON) * inverseDistance; fraction2 = (startDistance - offset - EPSILON) * inverseDistance; } // make sure the numbers are valid fraction1 = Clamp0To1(fraction1); fraction2 = Clamp0To1(fraction2); // calculate the middle point for the first side { auto middleFraction = startFraction + (endFraction - startFraction) * fraction1; auto middle = Lerp(start, end, fraction1); // check the first side result = CheckNode( node.childIndex[side], startFraction, middleFraction, start, middle, extents, boundsAabb, result, bsp); } // calculate the middle point for the second side { auto middleFraction = startFraction + (endFraction - startFraction) * fraction2; auto middle = Lerp(start, end, fraction2); // check the second side result = CheckNode( node.childIndex[!side], middleFraction, endFraction, middle, end, extents, boundsAabb, result, bsp); } return result; }
void Collision::CheckNode( int nodeIndex, float startFraction, float endFraction, vec3f start, vec3f end) { if (nodeIndex < 0) { // this is a leaf Q3BspLeaf *leaf = &mQ3Map->m_pLeafs[-(nodeIndex + 1)]; for (int i = 0; i < leaf->n_leafbrushes; i++) { Q3BspBrush *brush = &mQ3Map->m_pBrushes[mQ3Map->m_pLeafBrushes[leaf->leafbrush + i]]; if (brush->n_brushsides > 0 && (mQ3Map->m_pTextures[brush->texture].contents & 1) ) { CheckBrush( brush ); } } // don't have to do anything else for leaves return; } // this is a node Q3BspNode *node = &mQ3Map->m_pNodes[nodeIndex]; Q3BspPlane *plane = &mQ3Map->m_pPlanes[node->plane]; vec3f normal = vec3f(plane->normal); vec3f startDX = vec3f(start); vec3f endDX = vec3f(end); float startDistance = vec3dot( &startDX, &normal ) - plane->dist; float endDistance = vec3dot( &endDX, &normal ) - plane->dist; float offset; if (mTraceType == TT_RAY) { offset = 0.0f; } else if (mTraceType == TT_SPHERE) { offset = mTraceRadius; } else { offset = 0.0f; } if (startDistance >= offset && endDistance >= offset) { // both points are in front of the plane // so check the front child CheckNode( node->children[0], startFraction, endFraction, start, end ); } else if (startDistance < -offset && endDistance < -offset) { // both points are behind the plane // so check the back child CheckNode( node->children[1], startFraction, endFraction, start, end ); } else { // the line spans the splitting plane int side; float fraction1, fraction2, middleFraction; vec3f middle; // split the segment into two if (startDistance < endDistance) { side = 1; // back float inverseDistance = 1.0f / (startDistance - endDistance); fraction1 = (startDistance - offset + EPSILON) * inverseDistance; fraction2 = (startDistance + offset + EPSILON) * inverseDistance; } else if (endDistance < startDistance) { side = 0; // front float inverseDistance = 1.0f / (startDistance - endDistance); fraction1 = (startDistance + offset + EPSILON) * inverseDistance; fraction2 = (startDistance - offset - EPSILON) * inverseDistance; } else { side = 0; // front fraction1 = 1.0f; fraction2 = 0.0f; } // make sure the numbers are valid if (fraction1 < 0.0f) fraction1 = 0.0f; else if (fraction1 > 1.0f) fraction1 = 1.0f; if (fraction2 < 0.0f) fraction2 = 0.0f; else if (fraction2 > 1.0f) fraction2 = 1.0f; // calculate the middle point for the first side middleFraction = startFraction + (endFraction - startFraction) * fraction1; /*for (int i = 0; i < 3; i++) middle[i] = start[i] + fraction1 * (end[i] - start[i]);*/ middle = start + fraction1 * (end - start); // check the first side CheckNode( node->children[side], startFraction, middleFraction, start, middle ); // calculate the middle point for the second side middleFraction = startFraction + (endFraction - startFraction) * fraction2; /*for (int i = 0; i < 3; i++) middle[i] = start[i] + fraction2 * (end[i] - start[i]);*/ middle = start + fraction2 * (end - start); // check the second side CheckNode( node->children[!side], middleFraction, endFraction, middle, end ); } }
void CQuake3BSP::CheckNode(int nodeIndex, float startRatio, float endRatio, CVector3 vStart, CVector3 vEnd) { // Check if the next node is a leaf if(nodeIndex < 0) { // If this node in the BSP is a leaf, we need to negate and add 1 to offset // the real node index into the m_pLeafs[] array. You could also do [~nodeIndex]. tBSPLeaf *pLeaf = &m_pLeafs[-(nodeIndex + 1)]; // We have a leaf, so let's go through all of the brushes for that leaf for(int i = 0; i < pLeaf->numOfLeafBrushes; i++) { // Get the current brush that we going to check tBSPBrush *pBrush = &m_pBrushes[m_pLeafBrushes[pLeaf->leafBrush + i]]; // Check if we have brush sides and the current brush is solid and collidable if((pBrush->numOfBrushSides > 0) && (m_pTextures[pBrush->textureID].textureType & 1)) { // Now we delve into the dark depths of the real calculations for collision. // We can now check the movement vector against our brush planes. CheckBrush(pBrush, vStart, vEnd); } } // Since we found the brushes, we can go back up and stop recursing at this level return; } // Grad the next node to work with and grab this node's plane data tBSPNode *pNode = &m_pNodes[nodeIndex]; tBSPPlane *pPlane = &m_pPlanes[pNode->plane]; // Here we use the plane equation to find out where our initial start position is // according the the node that we are checking. We then grab the same info for the end pos. float startDistance = Dot(vStart, pPlane->vNormal) - pPlane->d; float endDistance = Dot(vEnd, pPlane->vNormal) - pPlane->d; float offset = 0.0f; // If we are doing sphere collision, include an offset for our collision tests below if(m_traceType == TYPE_SPHERE) offset = m_traceRadius; // Here we check to see if we are working with a BOX or not else if(m_traceType == TYPE_BOX) { // Get the distance our AABB is from the current splitter plane offset = (float)(fabs( m_vExtents.x * pPlane->vNormal.x ) + fabs( m_vExtents.y * pPlane->vNormal.y ) + fabs( m_vExtents.z * pPlane->vNormal.z ) ); } // Here we check to see if the start and end point are both in front of the current node. // If so, we want to check all of the nodes in front of this current splitter plane. if(startDistance >= offset && endDistance >= offset) { // Traverse the BSP tree on all the nodes in front of this current splitter plane CheckNode(pNode->front, startDistance, endDistance, vStart, vEnd); } // If both points are behind the current splitter plane, traverse down the back nodes else if(startDistance < -offset && endDistance < -offset) { // Traverse the BSP tree on all the nodes in back of this current splitter plane CheckNode(pNode->back, startDistance, endDistance, vStart, vEnd); } else { // If we get here, then our ray needs to be split in half to check the nodes // on both sides of the current splitter plane. Thus we create 2 ratios. float Ratio1 = 1.0f, Ratio2 = 0.0f, middleRatio = 0.0f; CVector3 vMiddle; // This stores the middle point for our split ray // Start of the side as the front side to check int side = pNode->front; // Here we check to see if the start point is in back of the plane (negative) if(startDistance < endDistance) { // Since the start position is in back, let's check the back nodes side = pNode->back; // Here we create 2 ratios that hold a distance from the start to the // extent closest to the start (take into account a sphere and epsilon). float inverseDistance = 1.0f / (startDistance - endDistance); Ratio1 = (startDistance - offset - kEpsilon) * inverseDistance; Ratio2 = (startDistance + offset + kEpsilon) * inverseDistance; } // Check if the starting point is greater than the end point (positive) else if(startDistance > endDistance) { // This means that we are going to recurse down the front nodes first. // We do the same thing as above and get 2 ratios for split ray. float inverseDistance = 1.0f / (startDistance - endDistance); Ratio1 = (startDistance + offset + kEpsilon) * inverseDistance; Ratio2 = (startDistance - offset - kEpsilon) * inverseDistance; } // Make sure that we have valid numbers and not some weird float problems. // This ensures that we have a value from 0 to 1 as a good ratio should be :) if (Ratio1 < 0.0f) Ratio1 = 0.0f; else if (Ratio1 > 1.0f) Ratio1 = 1.0f; if (Ratio2 < 0.0f) Ratio2 = 0.0f; else if (Ratio2 > 1.0f) Ratio2 = 1.0f; // Just like we do in the Trace() function, we find the desired middle // point on the ray, but instead of a point we get a middleRatio percentage. middleRatio = startRatio + ((endRatio - startRatio) * Ratio1); vMiddle = vStart + ((vEnd - vStart) * Ratio1); // Now we recurse on the current side with only the first half of the ray CheckNode(side, startRatio, middleRatio, vStart, vMiddle); // Now we need to make a middle point and ratio for the other side of the node middleRatio = startRatio + ((endRatio - startRatio) * Ratio2); vMiddle = vStart + ((vEnd - vStart) * Ratio2); // Depending on which side should go last, traverse the bsp with the // other side of the split ray (movement vector). if(side == pNode->back) CheckNode(pNode->front, middleRatio, endRatio, vMiddle, vEnd); else CheckNode(pNode->back, middleRatio, endRatio, vMiddle, vEnd); } }