Detect if a cube and a cone intersect each other?

后端 未结 4 1969
后悔当初
后悔当初 2020-12-14 18:26

Consider two geometrical objects in 3D:

  • a cube aligned with the axes and defined by the position of its center and its extent (edge length)
  • a cone not
4条回答
  •  星月不相逢
    2020-12-14 18:48

    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.

提交回复
热议问题