Aller au contenu

Moteur physique #3


daemondragon
 Share

Recommended Posts

Et voila la fin de la création d'un moteur physique !
Il ne nous restent plus qu'a détecter les collisions et les corriger, et le moteur est finit. :lol: :lol:

 

Détection des collisions :

La théorie :

Pour que deux rectangles soit en collisions, il faut qu'ils se "rentrent" dedans.
Pour cela, la distance (sur un seul axe pour l'instant) entre les deux points doit être inférieur
à la somme de la moitié de leur largueur (dans le cas de l'axe x, la hauteur pour l'axe y).
C'est pour cela que dans le précédent tuto ( lors de la création de corps, on ne gardait que la moitié de la largueur et de la hauteur.
Ont peut ainsi tester chaque axe pour voir s'il y a une collision sur celui-ci.
S'il n'y a une collision que sur un seul axe, alors l'objet n'est pas en collision avec l'autre, il est simplement à coté.

Une magnifique image qui résume cela :

carre.png

La pratique :

En code, on teste chaque axe l'un après l'autre. Si sur le premier axe il n'y a pas de collision, alors on a pas besoin de tester le deuxième.
 

float mlb_abs(float number)
{
    if (number < 0)
    {
        number *= -1;
    }
    return (number);
}

float penetration_x(t_body *f_body, t_body *s_body)
{
    return (mlb_abs(f_body->box.x - s_body->box.x) - (f_body->box.w + s_body->box.w));
}

float penetration_y(t_body *f_body, t_body *s_body)
{
    return (mlb_abs(f_body->box.y - s_body->box.y) - (f_body->box.h + s_body->box.h));
}

char is_collide(t_body *f_body, t_body *s_body)
{
    if (penetration_x(f_body, s_body) <= 0. 
     && penetration_y(f_body, s_body) <= 0.)
    {
            return (1);
    }
    return (0);
}

La fonction mlb_abs est strictement équivalente a abs() de math.h, mais à chaque fois que le moteur serait inclus dans un projet, il faudrait linker la biblio, et c'est pas pratique, donc créer la fonction est pour moi plus simple. Vous pouvez évidement la remplacer par celle de math.h si cela vous arrange.
On calcule donc les collisions sur chaque axe, et la fonction renvoie 1 en cas de collision.

 

Correction des collisions :

 

Il faut tester tout les corps entre eux pour savoir s'il y a une collision. Je vous rappelle que deux corps statiques ne seront jamais en collisions, donc on ne vas pas les tester entre eux.
 

void check_collision(t_physic_world *world)
{
    t_body *update = world->dynamic_body;
    t_body *collide_with = world->static_body;;
    update = world->dynamic_body;
    while (update != NULL)
    {
        /*collide with static body*/
        while (collide_with != NULL)
        {
            if (is_collide(update, collide_with))
            {
                correct_collision(update, collide_with);
            }
            collide_with = collide_with->next;
        }
        /*collide with dynamic body*/
        collide_with = update->next;
        while (collide_with != NULL)
        {
            if (is_collide(update, collide_with))
            {
                correct_collision(update, collide_with);
            }
            collide_with = collide_with->next;
        }
        
        update = update->next;
    }
    
}

On teste d'abord les collisions avec les corps statiques, car cela permet d'éviter que le corps soit repoussé a travers un mur s'il est poussé par un corps dynamique, ce qu'il ne faut surtout pas.

Et maintenant, il faut pouvoir corriger ces collisions une fois quelle sont détectées !
Tout d'abord, les corrections ne seront pas les mêmes en cas de collisions dynamique / statique que des collisions dynamique / dynamique.
Les première collisions pousseront le corps dynamique hors du corps statique alors que les deuxième collisions, les deux corps se pousseront mutuellement, et celui avec la plus grande force l'emportera.
Il faut d'abord dispatcher les corps dans les bonnes collisions :
 

void correct_collision(t_body *f_body, t_body *s_body)
{
    if (f_body->dynamic && !s_body->dynamic)
    {
        correct_static_collision(s_body, f_body);
    }
    else if (!f_body->dynamic && s_body->dynamic)
    {
        correct_static_collision(f_body, s_body);
    }
    else if (f_body->dynamic && s_body->dynamic)
    {
        correct_dynamic_collision(f_body, s_body);
    }    
}

Normalement, les collisions entre corps statique et dynamique se font dans cette ordre dans la fonction, mais cela permet de tester les collisions entre un corps qui n'appartient pas a la structure physic_world avec ce que vous voulez, sans vous souciez de l'ordre dans lequel les corps doivent être mis.
On va commencer avec les collisions avec des corps statiques :
 

void correct_static_collision(t_body *static_body, t_body *dynamic_body)
{
    if (penetration_x(static_body, dynamic_body) > penetration_y(static_body, dynamic_body))
    {
        /*x axis penetration*/
        float direction = dynamic_body->force.total_force.x / mlb_abs(dynamic_body->force.total_force.x);
        dynamic_body->box.x = static_body->box.x - (dynamic_body->box.w + static_body->box.w) * direction;

        add_contact_force(&(dynamic_body->force), -dynamic_body->force.total_force.x, static_body->force.contact_force.y);
    }
    else
    {
        /*y axis penetration*/
        float direction = dynamic_body->force.total_force.y / mlb_abs(dynamic_body->force.total_force.y);
        dynamic_body->box.y = static_body->box.y - ((dynamic_body->box.h + static_body->box.h) * direction);

        add_contact_force(&(dynamic_body->force), static_body->force.contact_force.x, -dynamic_body->force.total_force.y);
    }
}

On corrige la collision en fonction de l'axe ou le corps est le moins en collision, afin de ne pas voir un corps se faire repousser violemment alors que l'on peut faire une chose moins visible.
La fonction est simple : 1ere ligne ont calcule la direction de la collision, deuxième ligne, on la corrige, et troisième ligne, on met la force contraire de l'axe ou il y a eut la collisions dans le corps dynamique afin de l’empêcher de passer a travers. Mais pourquoi le faire, si on corrige simplement, il ne passera pas non plus non ?
Ce qu'il faut savoir, c'est que le moteur physique ne peut pas être sur qu'il va être appelé suffisamment pour éviter l’effet "tunnel", le corps passe alors a travers alors qu'il aurait du y avoir une collisions. Si l'ont ajoute une force contraire mais de contact, elle ne disparaîtra que lorsque le moteur physique sera remis a jour, donc on pourra alors recorriger la collision.

De plus on peut voir que dans l'axe ou il n'y a pas de colliisons, on rajoute un force. Mais qu'est ce que c'est ?
C'est le méga truc de la mort qui tue du deuxième tuto ! Grâce a cela, on peut rajouter facilement des tapis roulant et des plate-formes mobiles, alors que sinon, dans les autres moteurs physique, ont doit tricher en rajoutant une force lorsque le personnage se trouve sur celui-ci.
Evidemment, la force de contact n'est là que si vous l'ajouter, elle est de base à 0.

Et maintenant, la correction la plus difficile : les collisions dynamiques / dynamiques dont voici la fonction :
 

void correct_dynamic_collision(t_body *f_body, t_body *s_body)
{
    t_force common_force;
    t_force total_force;
    float weight;

    if (penetration_x(f_body, s_body) > penetration_y(f_body, s_body))
    {
        /*x axis penetration*/
        /*calculate the total force for the total body and divise it*/
        calc_total_force(&(f_body->force));
        calc_total_force(&(s_body->force));
        total_force.x = f_body->force.total_force.x;
        total_force.y = s_body->force.total_force.x;
        
        common_force.x = total_force.x + total_force.y;
        weight = f_body->weight + s_body->weight;
        common_force.y = common_force.x / weight * s_body->weight;
        common_force.x = common_force.x / weight * f_body->weight;
        
        add_contact_force(&(f_body->force), common_force.x - total_force.x, 0);
        add_contact_force(&(s_body->force), common_force.y - total_force.y, 0);

        /*correct collision*/
        float average_x = (f_body->box.y * s_body->weight + s_body->box.y * f_body->weight) / weight;
        float direction = average_x - f_body->box.x;
        if (direction < 0)
        {
            direction = -1;
        }
        else
        {
            direction = 1;
        }
        f_body->box.x = average_x - (f_body->box.w * direction);
        s_body->box.x = average_x + (s_body->box.w * direction);
    }
    else
    {
        /*y axis penetration*/
        /*calculate the total force for the total body and divise it*/
        calc_total_force(&(f_body->force));
        calc_total_force(&(s_body->force));
        total_force.x = f_body->force.total_force.y;
        total_force.y = s_body->force.total_force.y;
        
        common_force.y = total_force.x + total_force.y;
        weight = f_body->weight + s_body->weight;
        common_force.x = common_force.y / weight * f_body->weight;
        common_force.y = common_force.y / weight * s_body->weight;

        add_contact_force(&(f_body->force), 0, common_force.x - total_force.x);
        add_contact_force(&(s_body->force), 0, common_force.y - total_force.y);

        /*correct collision*/
        float average_y = (f_body->box.y * s_body->weight + s_body->box.y * f_body->weight) / weight;
        float direction = average_y - f_body->box.y;
        if (direction < 0)
        {
            direction = -1;
        }
        else
        {
            direction = 1;
        }
        f_body->box.y = average_y - (f_body->box.h * direction);
        s_body->box.y = average_y + (s_body->box.h * direction);
    }
}

Les fonctions sont exactement les mêmes sur les deux axes, il n'y a que des ajustement au niveau des axes a faire.
La fonction marche comme cela : elle calcule les forces des deux corps, calcule la moyenne de l'endroit de leur collisions en fonctions de la force de l'autre corps (car plus un corps aura de force, moins il devra être bougé car c'est lui le plus fort).
On transfère les forces dans les deux corps afin qu'ils aient la même vitesse en sortie, le tout dans des forces de contact, cela permet de faire des caisses que l'ont pourra pousser !
Et ont corrige a la fin la collision, et c'est bon.
Malgré que la fonction soit longue, elle reste relativement simple.

Et voila, le moteur physique est fini !
J'espère qu'il vous aura plus, et que vous vous en servirez :D

Modifié par daemondragon
  • Upvote 1
Lien vers le commentaire
Partager sur d’autres sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Invité
Répondre à ce sujet…

×   Vous avez collé du contenu avec mise en forme.   Supprimer la mise en forme

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Chargement
 Share

×
×
  • Créer...