1
\$\begingroup\$

I am trying to implement collision detection in 3D between OBB/AABB and sphere. For this i'm using sat/separating axis theorem. For sat to work in 3D i need face normals of obb (sphere to face), each vertex of obb - sphereCenter (sphere to vertex) and edge normals to sphereCenter (sphere to edge). The last i dont know how to calculate. First picture is working fine(sphere to vertex) and other two cases are giving false positives. Any other way to find minimum translation vector and distance of sphere and aabb would also be appreciated. enter image description here enter image description here enter image description here

Box vs Sphere:

void Physics::colliderBoxVsSphere(BoxCollider* c1, SphereCollider* c2, glm::vec3& mtvAxis, float& mtvDist){
    auto spPos = c2->getPosition();

    std::vector<glm::vec3> obb;
    c1->getOBB(obb);
    auto mid = midPos2(obb);

    // (1) identity axes
    std::vector<glm::vec3> axes;
    c1->getEntity()->transform()->appendIdentiyAxes(axes);

    // (2) vertices
    std::vector<glm::vec3> verts;
    for (auto& vert : obb) {
        auto axisVec = glm::normalize(spPos - vert);
        verts.push_back(axisVec);
    }

    // (3) edges
    //TODO

    axes.insert(axes.end(), verts.begin(), verts.end());

    CollisionMath::instance()->OBBCollideSphere(obb, spPos, c2->getRadius(), axes, mtvAxis, mtvDist);
}

SAT:

bool CollisionMath::OBBCollideSphere(const std::vector<glm::vec3>& obb, const glm::vec3& spherePos, float sphereRad, const std::vector<glm::vec3>& axes, glm::vec3& mtvAxisRet, float& mtvDistanceRet){
glm::vec3 pos1 = midPos(obb);
glm::vec3 pos2 = spherePos;

float mtvDist = std::numeric_limits<float>::max();
glm::vec3 mtvAxis = glm::vec3(0, 0, 0);
for (auto& axis : axes) {
    if (axis == glm::vec3(0, 0, 0)) continue;

    glm::vec2 p1 = Math::instance()->projectOnAxis(obb, axis);
    glm::vec2 p2 = Math::instance()->projectSphereOnAxis(spherePos, sphereRad, axis);

    float d1 = glm::dot(pos1, axis);
    float d2 = glm::dot(pos2, axis);

    if (d1 > d2) {
        if (p1.x > p2.y)
            return false;
        else {      //intersect get depth
            float depth = p2.y - p1.x;
            if (depth < mtvDist) {
                mtvDist = depth;
                mtvAxis = axis;
            }
    }
}
        else {
            if (p2.x > p1.y)
                return false;
            else {      //intersect get depth
                float depth = p1.y - p2.x;
                if (depth < mtvDist) {
                    mtvDist = depth;
                    mtvAxis = -axis;
                }
            }
        }
    }

    mtvAxisRet = mtvAxis;
    mtvDistanceRet = mtvDist;

    return true;
}

Project on axis:

float min = glm::dot(verts[0], axis);
float max = min;

for (unsigned i = 1; i < verts.size(); ++i) {
    float d = glm::dot(verts[i], axis);
    if (d > max) max = d;
    if (d < min) min = d;
}

return glm::vec2(min, max);

project sphere on axis:

glm::vec3 A = center + axis * rad;
glm::vec3 B = center - axis * rad;

float min = glm::dot(B,axis);
float max = glm::dot(A,axis);

return glm::vec2(min, max);
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

Don't use separating axis theorem for this. It's overkill for spheres.

Instead:

  1. Use the OBB's orientation & translation information to inverse-transform the sphere's center into the box's local space.

    localSphereCenter = box.InverseTransformPoint(sphere.worldCenter)

  2. Find the closest point on the box to this point. Now that you're working on the box's local axes, this is as simple as a component-wise clamp on each axis:

    closest.x = max(box.localMin.x, min(localSphereCenter.x, box.localMax.x));
    ... likewise for y, z

  3. Transform that point back to worldspace (to account for scale in your OBB, if applicable), then take the offset vector from this closest point to the sphere center. If it's shorter than your radius, the sphere intersects the box.

The minimum translation vector (if the center is not in the box) is then this offset vector scaled to the remaining length:

 translation = (sphere.radius - offset.length) * offset.normalized

If the sphere center is inside the box, then this displacement vector will be zero. You can clamp the point to the nearest face of the box, then form a penetration vector equal to the sphere center minus this closest face point. Your minimum translation vector is then:

translation = -1 * (sphere.radius + penetration.length) * penetration.normalized

Since we've computed these separations/penetrations in the box's local space, remember to transform them back to world space at the end to use them for moving objects.

\$\endgroup\$
2
  • \$\begingroup\$ What if the sphere has no single radius? What if the sphere gets scaled by, say, (x=2, y=4, z=7)? \$\endgroup\$
    – AudioGuy
    May 19, 2021 at 19:16
  • \$\begingroup\$ Then it's no longer a sphere, and becomes what we call an ellipsoid. You can search "ellipsoid collision detection" for information about how to solve that related problem. \$\endgroup\$
    – DMGregory
    May 19, 2021 at 19:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .