web-dev-qa-db-fra.com

Détection de collision OBB Box orientée simple Explication

Je peux implémenter la méthode AABB pour détecter les collisions, c’est facile et pas cher, mais je souhaite implémenter OBB pour plus de précision. Je crée donc le cadre de sélection avec l’initialisation du modèle. Il est composé de 8 sommets et centre, chaque image transformant tous les sommets avec la matrice de transformation pour s’adapter au boîtier de délimitation orienté mais je ne comprends pas la méthode de détection de la collision entre deux OBB et je ne trouve pas de tutoriel simplifié et clair qui explique l’algorithme avec le point de vue code je ne suis pas mathématicien.

si j'ai 

struct Box {
    glm::vec3 vertices[8];
    Box() {
        for (int i = 0; i < 8; i++) {
            vertices[i] = glm::vec3(0);
        }
    }
    glm::vec3 max;
    glm::vec3 min;
    glm::vec3 Origin;

    void reCompute() {
        max = vertices[0];
        min = vertices[0];
        for (int i = 1; i < 8; i++) {
            max.x = max.x > vertices[i].x ? max.x : vertices[i].x;
            max.y = max.y > vertices[i].y ? max.y : vertices[i].y;
            max.z = max.z > vertices[i].z ? max.z : vertices[i].z;

            min.x = min.x < vertices[i].x ? min.x : vertices[i].x;
            min.y = min.y < vertices[i].y ? min.y : vertices[i].y;
            min.z = min.z < vertices[i].z ? min.z : vertices[i].z;
        }
        Origin = glm::vec3((max.x + min.x) / 2.0f, (max.y + min.y) / 2.0f, (max.z + min.z) / 2.0f);
    }
//AABB intersection
    bool intersects(const Box &b) const {
        return (min.x < b.max.x) && (max.x > b.min.x) && (min.y < b.max.y) && (max.y > b.min.y) && (min.z < b.max.z) && (max.z > b.min.z) && *this != b;
    }

    bool operator==(const Box& b) const {
        return (max.x == b.max.x && max.y == b.max.y && max.z == b.max.z && min.x == b.min.x && min.y == b.min.y && min.z == b.min.z);
    }
    bool operator!=(const Box& b) const {
        return (max.x != b.max.x) || (max.y != b.max.y) || (max.z != b.max.z) || (min.x != b.min.x) || (min.y != b.min.y) || (min.z != b.min.z);
    }
};

lors de l'initialisation du modèle, je crée la boîte

    box.vertices[0] = glm::vec3(meshMinX, meshMinY, meshMinZ);
    box.vertices[1] = glm::vec3(meshMaxX, meshMinY, meshMinZ);
    box.vertices[2] = glm::vec3(meshMinX, meshMaxY, meshMinZ);
    box.vertices[3] = glm::vec3(meshMaxX, meshMaxY, meshMinZ);
    box.vertices[4] = glm::vec3(meshMinX, meshMinY, meshMaxZ);
    box.vertices[5] = glm::vec3(meshMaxX, meshMinY, meshMaxZ);
    box.vertices[6] = glm::vec3(meshMinX, meshMaxY, meshMaxZ);
    box.vertices[7] = glm::vec3(meshMaxX, meshMaxY, meshMaxZ);

et chaque image je recalcule la boîte avec la matrice de transformation du modèle

for (int n = 0; n < 8; n++) {
        boxs[j].vertices[n] = glm::vec3(matrix * glm::vec4(box.vertices[n], 1));
    }
boxs[j].reCompute();
5

Pour savoir si deux OBB entrent en collision, utilisez le SAT (théorème des axes séparateurs): vous devez projeter tous les points des deux formes sur chaque normale des deux formes. Ensuite, vous voyez si la projection des deux formes se chevauchent sur chaque normale, il y a collision. S'il y a au moins une normale où il n'y a pas de chevauchement, alors elles ne se rencontrent pas. et une méthode pour voir si deux intervalles se chevauchent.

J'ai du code en Java:

Projection orthagonale de U sur V:

/**
 * Vec u is projected on Vec v
 * @param u 2d point
 * @param v 2d axe
 * @return the orthogonal projection
 */
public static float orthagonalProjectionOf(Vector2f u, Vector2f v){
    float norme_u = u.lenght();
    float norme_v = v.lenght();
    float dot_u_v = dot(u, v);
    float buffer = (dot_u_v/(norme_u*norme_v))*norme_u;
    if(Float.isNaN(buffer))return 0;//If the vector u is null, then is orthogonal projection is 0, not a NaN
    else return buffer;
}

Chevauchement de deux intervalles:

/**
 * Get the overlapping of two interval on an axis.
 * @param minA
 * @param maxA
 * @param minB
 * @param maxB
 * @return true overlapping. false if there is no overlapping
 */
public static boolean isOverlapping(float minA, float maxA, float minB, float maxB) {

    float minOverlap = Float.NaN;
    float maxOverlap = Float.NaN;


    //If B contain in A
    if(minA <= minB && minB <= maxA) {
        if(Float.isNaN(minOverlap) || minB < minOverlap)minOverlap = minB;
    }
    if(minA <= maxB && maxB <= maxA) {
        if(Float.isNaN(maxOverlap) || maxB > minOverlap)maxOverlap = maxB;
    }

    //If A contain in B
    if(minB <= minA && minA <= maxB) {
        if(Float.isNaN(minOverlap) || minA < minOverlap)minOverlap = minA;
    }
    if(minB <= maxA && maxA <= maxB) {
        if(Float.isNaN(maxOverlap) || maxA > minOverlap)maxOverlap = maxA;
    }

    if(Float.isNaN(minOverlap) || Float.isNaN(maxOverlap))return false; //Pas d'intersection
    else return true;//Intersection

}

Avec cela, vous êtes capable de faire une méthode pour tester la collision entre deux OBB:

public boolean OBBwOBB(RigidBody bodyA, RigidBody bodyB) {
    Shape shapeA = bodyA.getObb().getShape();
    Shape shapeB = bodyB.getObb().getShape();

    short overlapCompt = 0;

    //We test for each normal the projection of the two shape
        //Shape A :
    for(int i = 0; i < shapeA.getNbrOfNormals(); i++) {
        Vector2f normal = shapeA.getNormal(i, bodyA.getAngle());
        boolean overlap = overlapOnThisNormal(bodyA, bodyB, normal);
        if(overlap) {
            overlapCompt++;
        }
    }
        //Shape B :
    for(int i = 0; i < shapeB.getNbrOfNormals(); i++) {
        Vector2f normal = shapeB.getNormal(i, bodyB.getAngle());
        boolean overlap = overlapOnThisNormal(bodyA, bodyB, normal);
        if(overlap){
            overlapCompt++;
        }
    }

    //Now we see if there is a collision
    short howManyNormals = (short) (shapeA.getNbrOfNormals() + shapeB.getNbrOfNormals());
    if(overlapCompt == howManyNormals){//If the number of overlap equal the number of normal in both shape :
        return true;
    }
    else return false;

}

Et vous en aurez besoin pour obtenir les min et max de la projection des deux formes projetées sur un vecteur:

/**
 * Test if the orthogonal projection of two shape on a vector overlap.
 * @param bodyA
 * @param bodyB
 * @param normal
 * @return null if no overlap, else Vector2f(minOverlaping, maxOverlaping).
 */
public static boolean overlapOnThisNormal(RigidBody bodyA, RigidBody bodyB, Vector2f normal) {
    Shape shapeA = bodyA.getObb().getShape();
    Shape shapeB = bodyB.getObb().getShape();

    //We test each vertex of A
    float minA = Float.NaN;
    float maxA = Float.NaN;
    for(short j = 0; j < shapeA.getNbrOfPoint(); j++){
        Vector2f vertex = shapeA.getVertex(j, bodyA.getScale().x, bodyA.getScale().y, bodyA.getPosition().x, bodyA.getPosition().y, bodyA.getAngle());
        float bufferA = Vector2f.orthagonalProjectionOf(vertex, normal);
        if(Float.isNaN(minA) || bufferA < minA)minA = bufferA;//Set min interval
        if(Float.isNaN(maxA) || bufferA > maxA)maxA = bufferA;//Set max interval
    }

    //We test each vertex of B
    float minB = Float.NaN;
    float maxB = Float.NaN;
    for(short j = 0; j < shapeB.getNbrOfPoint(); j++){
        Vector2f vertex = shapeB.getVertex(j, bodyB.getScale().x, bodyB.getScale().y, bodyB.getPosition().x, bodyB.getPosition().y, bodyB.getAngle());
        float bufferB = Vector2f.orthagonalProjectionOf(vertex, normal);
        if(Float.isNaN(minB) || bufferB < minB)minB = bufferB;//Set min interval
        if(Float.isNaN(maxB) || bufferB > maxB)maxB = bufferB;//Set max interval
    }

    //We test if there overlap
    boolean overlap = isOverlapping(minA, maxA, minB, maxB);
    return overlap;
}

J'espère que ceci vous aide ;)

3
Corentin

Une implémentation en code C++ du théorème des axes séparateurs pour la détection de collision simple entre deux OBB 3D serait la suivante:

#include <iostream>

// define the operations to be used in our 3D vertices
struct vec3
{
    float x, y, z;
    vec3 operator- (const vec3 & rhs) const { return{ x - rhs.x, y - rhs.y, z - rhs.z }; }
    float operator* (const vec3 & rhs) const { return{ x * rhs.x + y * rhs.y + z * rhs.z }; } // DOT PRODUCT
    vec3 operator^ (const vec3 & rhs) const { return{ y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x }; } // CROSS PRODUCT
    vec3 operator* (const float& rhs)const { return vec3{ x * rhs, y * rhs, z * rhs }; }
};

// set the relevant elements of our oriented bounding box
struct OBB
{
    vec3 Pos, AxisX, AxisY, AxisZ, Half_size;
};

// check if there's a separating plane in between the selected axes
bool getSeparatingPlane(const vec3& RPos, const vec3& Plane, const OBB& box1, const OBB&box2)
{
    return (fabs(RPos*Plane) > 
        (fabs((box1.AxisX*box1.Half_size.x)*Plane) +
        fabs((box1.AxisY*box1.Half_size.y)*Plane) +
        fabs((box1.AxisZ*box1.Half_size.z)*Plane) +
        fabs((box2.AxisX*box2.Half_size.x)*Plane) + 
        fabs((box2.AxisY*box2.Half_size.y)*Plane) +
        fabs((box2.AxisZ*box2.Half_size.z)*Plane)));
}

// test for separating planes in all 15 axes
bool getCollision(const OBB& box1, const OBB&box2)
{
    static vec3 RPos;
    RPos = box2.Pos - box1.Pos;

    return !(getSeparatingPlane(RPos, box1.AxisX, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisY, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisZ, box1, box2) ||
        getSeparatingPlane(RPos, box2.AxisX, box1, box2) ||
        getSeparatingPlane(RPos, box2.AxisY, box1, box2) ||
        getSeparatingPlane(RPos, box2.AxisZ, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisX^box2.AxisX, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisX^box2.AxisY, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisX^box2.AxisZ, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisY^box2.AxisX, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisY^box2.AxisY, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisY^box2.AxisZ, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisZ^box2.AxisX, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisZ^box2.AxisY, box1, box2) ||
        getSeparatingPlane(RPos, box1.AxisZ^box2.AxisZ, box1, box2));
}

// a quick test to see the code working
int _tmain(int argc, _TCHAR* argv[])
{
    // create two obbs
    OBB A, B;

    // set the first obb's properties
    A.Pos = { 0.f, 0.f, 0.f }; // set its center position

    // set the half size
    A.Half_size.x = 10.f; 
    A.Half_size.y = 1.f; 
    A.Half_size.z = 1.f;

    // set the axes orientation
    A.AxisX = { 1.f, 0.f, 0.f };
    A.AxisY = { 0.f, 1.f, 0.f };
    A.AxisZ = { 0.f, 0.f, 1.f };

    // set the second obb's properties
    B.Pos = { 20.f, 0.f, 0.f }; // set its center position

    // set the half size
    B.Half_size.x = 10.f;
    B.Half_size.y = 1.f;
    B.Half_size.z = 1.f;

    // set the axes orientation
    B.AxisX = { 1.f, 0.f, 0.f };
    B.AxisY = { 0.f, 1.f, 0.f };
    B.AxisZ = { 0.f, 0.f, 1.f };

    // run the code and get the result as a message
    if (getCollision(A, B)) std::cout << "Collision!!!" << std::endl;
    else std::cout << "No collision." << std::endl;

    // pause and quit
    std::cout << std::endl;
    system("pause");
    return 0;
}
1
Haalef