Consider two geometrical objects in 3D:
A few years ago I made an efficient algorithm to test intersection between a cone and aabb for some rendering code. I recently needed this test for something I'm working on now, so I revisited it and made it even more efficient. I have spent way more time than I care to admit working on this problem and so I decided to spare you the misery and post the code. As such, this is AFAIK a completely unique solution and won't be found in a textbook (i.e. David Eberly's solution).
BIG EDIT: My previous algorithm handled most situations well enough that I didn't notice any problems - that is until I got a block of time large enough to test all situations rigorously. It had a small margin of error in one case, and a fairly large error margin in one other one when testing against the brute-force 6-planes approach. The segment-box test I was doing at the end couldn't account for all the possible weird situations where the expanding cone would penetrate the box. It looked good on my 2d and poorly-drawn-3d test cases, but failed in practice. My new idea is slightly more expensive, but it's bullet-proof.
The steps that the new version goes through are as follows:
1.) Early out if the apex is in the bounding box.
2.) Identify the faces that the cone can possibly touch (Max of 3) and add their vertices to an array.
3.) Project all of the vertices in the array into "cone-space".
4.) Early out if the projected vertices are inside the cone
5.) Do a circle-polygon-test against the vertex array and the radius of the cone to catch edge intersections with the polygon on border of the cone
bool Intersect(const Cone& pCone) const {
Vector3 pFaceVerts[12];
U32 uVertCount;
int piClipSigns[3];
U32 uClipCount = GetClipInfo(pCone.GetApex(), piClipSigns);
switch (uClipCount) {
// If the clip count is zero, the apex is fully contained in the box
xcase 0: {
return true;
}
// 1) Clips single face, 4 vertices, guaranteed to not touch any other faces
xcase 1: {
int iFacet = piClipSigns[0] != 0 ? 0 : (piClipSigns[1] != 0 ? 1 : 2);
GetFacetVertices(iFacet, piClipSigns[iFacet], pFaceVerts);
uVertCount = 4;
}
// 2) Clips an edge joining two candidate faces, 6 vertices
// 3) Clips a vertex joining three candidate faces, 7 vertices
xcase 2:
acase 3: {
uVertCount = 0;
for (U32 iFacet = 0; iFacet < 3; iFacet++) {
if (piClipSigns[iFacet] != 0) {
GetFacetVertices(iFacet, piClipSigns[iFacet], pFaceVerts + uVertCount);
uVertCount += 4;
}
}
FixVertices(pFaceVerts, uVertCount);
}
}
// Project vertices into cone-space
F32 fConeRadiusSquared = Square(pCone.GetRadius());
F32 pfLengthAlongAxis[6];
bool bOutside = true;
for (U32 i = 0; i < uVertCount; i++) {
pfLengthAlongAxis[i] = Dot(pCone.GetAxis(), pFaceVerts[i] - pCone.GetApex());
bOutside &= Clamp1(pfLengthAlongAxis[i], LargeEpsilon, pCone.GetHeight() - LargeEpsilon);
}
// Outside the cone axis length-wise
if (bOutside) {
return false;
}
for (U32 i = 0; i < uVertCount; i++) {
Vector3 vPosOnAxis = pCone.GetApex() + pCone.GetAxis() * pfLengthAlongAxis[i];
Vector3 vDirFromAxis = pFaceVerts[i] - vPosOnAxis;
F32 fScale = (pCone.GetHeight() / pfLengthAlongAxis[i]);
F32 x = fScale * Dot(vDirFromAxis, pCone.GetBaseRight());
F32 y = fScale * Dot(vDirFromAxis, pCone.GetBaseUp());
// Intersects if any projected points are inside the cone
if (Square(x) + Square(y) <= fConeRadiusSquared) {
return true;
}
pFaceVerts[i] = Vector2(x, y);
}
// Finally do a polygon circle intersection with circle center at origin
return PolygonCircleIntersect(pFaceVerts, uVertCount, pCone.GetRadius());
}
GetClipInfo:
inline U32 GetClipInfo(const Vector3& P, int piClipSigns[3]) const {
U32 N = 0;
for (U32 i = 0; i < 3; i++) {
if (P[i] < m_vMin[i]) {
piClipSigns[i] = -1;
N++;
} else if (P[i] > m_vMax[i]) {
piClipSigns[i] = +1;
N++;
} else {
piClipSigns[i] = 0;
}
}
return N;
}
GetFacetVertices and FixVertices are slightly hacky at the moment, but they get the vertices on the face and fixup the vertices to be convex and ccw-ordered, respectively.
An alternative would be just to project all the vertices into cone-space without any fancy logic, but I need mine to be as fast as possible so I broke it down into several cases.
I tried various other approaches, notably a separating axis test between the cone axis and the box and used the largest separating axis to get the closest face to test with PolygonCircleIntersect, but I identified a failure case and so threw it out.