Comment faire ricocher des Balles entre elles.

Résolu/Fermé
Pulsar360 Messages postés 114 Date d'inscription lundi 12 novembre 2012 Statut Membre Dernière intervention 29 août 2018 - Modifié par Pulsar360 le 6/04/2013 à 19:09
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 - 14 avril 2013 à 20:09
Bonjour,

Je cherche à faire ricocher 2 balles entre elles, donc évidement prendre les positions X,Y de la balle 1 et la balle 2 puis soustraire le diamètre de la balle de gauche et si X atteint 0 pour la balle 2 la faire repartir dans l'autre sens, simpliste, MAIIIS ceci est valable si les deux balles sont sur le même axe Y, hors si elles sont décaler l'une de l'autre, l'axe X aura toujours zéro et renverra la balle alors que visuellement elle ne se touche pas.

Je ne me fait peut être pas très bien comprendre, donc j'ai fait un dessin :
http://hpics.li/4774935

Donc sur le dessin, il y a l'axe Y1 sur le quel les deux balle vont se percuter parfaitement mais sur l'axe Y2 elles vont se percuter mais que mathématiquement, à l'oeil ont verra des balles se percuter sans se toucher.

Comment faire?
En calculent mathématiquement Y ET X; suivant l'éloignement des 2 Y faire pénétrer plus profondément les axes X ??? Exemple si 1px de différence entre les axe Y faire pénétrer l'axe X de 1 px ??? Est ce bien de faire ainsi?

3 réponses

KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
14 avril 2013 à 20:09
En faisant cela bien :

Je pars toujours de l'article de Wikipédia sur les Chocs élastiques, mais en faisant bien attention car le code fourni est un cas particulier lorsque toutes les masses sont identiques, j'avais essayé de l'adapter au cas où il y avait des masses différentes, mais mes calculs étaient faux.
Après avoir fait un peu plus attention, voici les bons calculs.

PS. j'ai laissé en commentaire les calculs qui permettent de vérifier que c'est bon.

public class Balle
{
    /** Rayon de la balle. */ 
    protected final double r;
    
    /** Masse de la balle. */
    protected final double m;
    
    /** Position de la balle. */
    protected double x,y;
    
    /** Vitesse de la balle. */
    protected double vx,vy;
    
    /**
     * Modifies les vecteurs vitesses de deux balles qui entrent en collision.
     */
    protected static final void collision(Balle b1, Balle b2)
    {        
    // Quantité de mouvement et énergie cinétique avant la collision
        
        //double qmx1 = b1.m * b1.vx + b2.m * b2.vx;
        //double qmy1 = b1.m * b1.vy + b2.m * b2.vy;
        //double ec1  = b1.m * (b1.vx*b1.vx + b1.vy*b1.vy) + b2.m * (b2.vx*b2.vx + b2.vy*b2.vy);
        
    // Calcul de la base orthonormée (n,g)
        
        double dx = b1.x - b2.x;
        double dy = b1.y - b2.y;
        double r = Math.sqrt(dx*dx + dy*dy);
        
        // n est perpendiculaire au plan de collision        
        double nx = (b2.x - b1.x) / r;
        double ny = (b2.y - b1.y) / r;
        
        // g est tangent au plan de collision        
        double gx = -ny;
        double gy =  nx;
        
    // Transition des vitesses de la base (x,y) vers (n,g)
        
        double v1n = nx*b1.vx + ny*b1.vy;
        double v1g = gx*b1.vx + gy*b1.vy;
        double v2n = nx*b2.vx + ny*b2.vy;
        double v2g = gx*b2.vx + gy*b2.vy;
        
    // Détermination des nouvelles vitesses dans (n,g)
        
        double m = b1.m + b2.m;
        
        double m12 = (b1.m - b2.m)/m;
        double m22 = (b2.m + b2.m)/m;
        double m11 = (b1.m + b1.m)/m;
        double m21 = (b2.m - b1.m)/m;
        
        double v1n2 = m12*v1n + m22*v2n;
        double v1g2 = m12*v1g + m22*v2g;
        double v2n2 = m11*v1n + m21*v2n;
        double v2g2 = m11*v1g + m21*v2g;
        
    // Modification des vitesses dans la base (x,y)
        
        b1.vx = nx*v1n2 + gx*v1g2;
        b1.vy = ny*v1n2 + gy*v1g2;
        b2.vx = nx*v2n2 + gx*v2g2;
        b2.vy = ny*v2n2 + gy*v2g2;
        
    // Quantité de mouvement et énergie cinétique après la collision
        
        //double qmx2 = b1.m * b1.vx + b2.m * b2.vx;
        //double qmy2 = b1.m * b1.vy + b2.m * b2.vy;
        //double ec2 = b1.m * (b1.vx*b1.vx + b1.vy*b1.vy) + b2.m * (b2.vx*b2.vx + b2.vy*b2.vy);
        
    //Vérification de la conservation de la quantité de mouvement et de l'énergie cinétique
        
        //System.out.printf("%.2f = %.2f\t%.2f = %.2f\t%.2f = %.2f\n",qmx1,qmx2,qmy1,qmy2,ec1,ec2);
    }
}

Remarque :
Ce calcul n'est valable qu'au moment exact où on a le ricochet, c'est à dire que les deux balles sont séparées d'une distance r = b1.r + b2.r

Or en pratique, le déplacement est généralement effectué sur un pas de temps discret, c'est à dire avec un code comme ceci :

/**
 * Modifie la position de la balle en fonction de sa vitesse.
 * @param dt durée du déplacement
 */
protected void deplacement(double dt)
{
    x += vx * dt;
    y += vy * dt;
}

Il y a donc un problème, parce que s'il y a collision entre deux balles, c'est très certainement en plein milieu de ce temps "dt", il faudrait donc pouvoir appeler la méthode de collision au bon moment. Je propose pour cela de "remonter le temps".

Première étape (la plus facile), deux balles sont en collisions si leur distance est égale (ou inférieure) à la somme de leur rayon. Cela se calcule comme ça :

/**
 * @return true si les deux balles sont en collision
 */
protected static boolean sontCollision(Balle b1, Balle b2)
{
    double dx = b1.x-b2.x, dy = b1.y-b2.y, r = b1.r+b2.r;
    return dx*dx + dy*dy <= r*r;
}

Ensuite, supposons que b1 et b2 soient détectées en collision, cela signifie qu'avant qu'on les déplace de "dt" elles ne l'étaient pas, mais qu'après ce déplacement elles le sont. On va donc remonter le temps de "dt" puis avancer progressivement jusqu'au temps "exact" où la collision s'est produit.

// On remonte le temps

b1.deplacement(-dt);
b2.deplacement(-dt);

Pour calculer le temps "exact" où se produit la collision je propose de procéder par dichotomie, on cherche donc (entre 0 et dt) la valeur de "t" tel qu'on ait la distance "r" entre b1 et b2 qui soit égale à b1.r + b2.r

Remarque : je dis temps "exact" avec des guillemets, car en réalité je vais tolérer une petite marge d'erreur (par exemple de 1e-9)

Voici ce que donnerait le calcul :

double tps = tpsCollision(b1,b2,0,dt,1e-9);

/**
 * @return t tel que distance(b1,b2) = b1.r+b2.r
 */
private static double tpsCollision(Balle b1, Balle b2, double t0, double t2, double e)
{        
    double t1 = (t0+t2)/2;

    if (t2-t0<e)
        return t1;
    else
    {
        if (Math.pow((b1.x+b1.vx*t1)-(b2.x+b2.vx*t1),2) + Math.pow((b1.y+b1.vy*t1)-(b2.y+b2.vy*t1),2) > Math.pow(b1.r+b2.r,2))
            return tpsCollision(b1,b2,t1,t2,e);
        else
            return tpsCollision(b1,b2,t0,t1,e);
    }
}

Une fois ce temps obtenu, il s'agit "simplement" de faire avancer normalement les deux balles jusqu'à la collision, puis traiter la collision, et enfin continuer leur déplacement après la collision du temps qu'il reste.

// déplacement des balles jusqu'à la collision

b1.deplacement(tps);
b2.deplacement(tps);      	

// traitement de la collision

collision(b1,b2);

// déplacement des balles après la collision

b1.deplacement(dt-tps);
b2.deplacement(dt-tps);

Et comme ceci, on se retrouve avec des collisions physiquement réalistes...

Voici un exemple complet de balles qui se déplacent dans une fenêtre.
Remarque : les calculs de rebonds sur les bords pourraient être améliorés de la même manière, mais j'ai surtout insisté sur le rebond des balles entre elles.

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.Random;

import javax.swing.JFrame;

class Balle
{
    /** Rayon de la balle. */ 
    protected final double r;
    
    /** Masse de la balle. */
    protected final double m;
    
    /** Position de la balle. */
    protected double x,y;
    
    /** Vitesse de la balle. */
    protected double vx,vy;
    
    protected final Color clr;
    
    public Balle(double r, double x0, double y0, double vx0, double vy0, Color clr)
    {
        this.r = r;
        this.m = Math.pow(r,3);
        this.clr = clr;
        x=x0;
        y=y0;
        vx=vx0;
        vy=vy0;
    }
    
    /**
     * Déplace toutes les balles dans le plan et gère les collisions.
     * @param width largeur du plan
     * @param height hauteur du plan
     * @param dt durée du déplacement
     * @param balles toutes les balles du plan
     */
    public static void deplacer(double width, double height, double dt, Balle...balles)
    {
        for (Balle balle : balles)
        {
            balle.deplacement(dt);
            balle.contactAuxBords(width,height);
        }
        
        for (int i=0, n=balles.length; i<n; i++)
        for (int j=i+1; j<n; j++)
        {
            if (sontCollision(balles[i],balles[j]))
            {
                Balle b1 = balles[i], b2=balles[j];
                
                // on remonte le temps (on annule le dernier déplacement qui va conduire à une collision)
                
                b1.deplacement(-dt);
                b2.deplacement(-dt);
                
                // calcul du temps "exact" de la collision
                
                double tps = tpsCollision(b1,b2,0,dt,1e-9);
                
                // déplacement des balles jusqu'à la collision
                
                b1.deplacement(tps);
                b2.deplacement(tps);          
                
                // traitement de la collision
                
                collision(b1,b2);
                
                // déplacement des balles après la collision
                
                b1.deplacement(dt-tps);
                b2.deplacement(dt-tps); 
            }
        }
    }
    
    /**
     * Modifie la position de la balle en fonction de sa vitesse.
     * @param dt durée du déplacement
     */
    protected void deplacement(double dt)
    {
        x += vx * dt;
        y += vy * dt;
    }
    
    /**
     * Gère le contact d'une balle sur un bord du plan.
     * @param width largeur du plan
     * @param height hauteur du plan
     */
    protected void contactAuxBords(double width, double height)
    {
        if (x-r < 0)
        {
            x = r;
            vx = -vx;
        }
        else if (x+r > width)
        {
            x = width-r;
            vx = -vx;
        }
        
        if (y-r < 0)
        {
            y = r;
            vy = -vy;
        }
        else if (y+r > height)
        {
            y = height-r;
            vy = -vy;
        }
    }
    
    /**
     * @return true si les deux balles sont en collision
     */
    protected static boolean sontCollision(Balle b1, Balle b2)
    {
        double dx = b1.x-b2.x, dy = b1.y-b2.y, r = b1.r+b2.r;
        return dx*dx + dy*dy <= r*r;
    }
    
    /**
     * @return t tel que distance(b1,b2) = b1.r+b2.r
     */
    private static double tpsCollision(Balle b1, Balle b2, double t0, double t2, double e)
    {        
        double t1 = (t0+t2)/2;
        
        if (t2-t0<e)
            return t1;
        else
        {
            if (Math.pow((b1.x+b1.vx*t1)-(b2.x+b2.vx*t1),2)+Math.pow((b1.y+b1.vy*t1)-(b2.y+b2.vy*t1),2) > Math.pow(b1.r+b2.r,2))
                return tpsCollision(b1,b2,t1,t2,e);
            else
                return tpsCollision(b1,b2,t0,t1,e);
        }
    }
    
    /**
     * Modifies les vecteurs vitesses de deux balles qui entrent en collision.
     */
    protected static final void collision(Balle b1, Balle b2)
    {        
    // Quantité de mouvement et énergie cinétique avant la collision
        
        //double qmx1 = b1.m * b1.vx + b2.m * b2.vx;
        //double qmy1 = b1.m * b1.vy + b2.m * b2.vy;
        //double ec1  = b1.m * (b1.vx*b1.vx + b1.vy*b1.vy) + b2.m * (b2.vx*b2.vx + b2.vy*b2.vy);
        
    // Calcul de la base orthonormée (n,g)
        
        double dx = b1.x - b2.x;
        double dy = b1.y - b2.y;
        double r = Math.sqrt(dx*dx + dy*dy);
        
        // n est perpendiculaire au plan de collision        
        double nx = (b2.x - b1.x) / r;
        double ny = (b2.y - b1.y) / r;
        
        // g est tangent au plan de collision        
        double gx = -ny;
        double gy =  nx;
        
    // Transition des vitesses de la base (x,y) vers (n,g)
        
        double v1n = nx*b1.vx + ny*b1.vy;
        double v1g = gx*b1.vx + gy*b1.vy;
        double v2n = nx*b2.vx + ny*b2.vy;
        double v2g = gx*b2.vx + gy*b2.vy;
        
    // Détermination des nouvelles vitesses dans (n,g)
        
        double m = b1.m + b2.m;
        
        double m12 = (b1.m - b2.m)/m;
        double m22 = (b2.m + b2.m)/m;
        double m11 = (b1.m + b1.m)/m;
        double m21 = (b2.m - b1.m)/m;
        
        double v1n2 = m12*v1n + m22*v2n;
        double v1g2 = m12*v1g + m22*v2g;
        double v2n2 = m11*v1n + m21*v2n;
        double v2g2 = m11*v1g + m21*v2g;
        
    // Modification des vitesses dans la base (x,y)
        
        b1.vx = nx*v1n2 + gx*v1g2;
        b1.vy = ny*v1n2 + gy*v1g2;
        b2.vx = nx*v2n2 + gx*v2g2;
        b2.vy = ny*v2n2 + gy*v2g2;
        
    //Quantité de mouvement et énergie cinétique après la collision
        
        //double qmx2 = b1.m * b1.vx + b2.m * b2.vx;
        //double qmy2 = b1.m * b1.vy + b2.m * b2.vy;
        //double ec2 = b1.m * (b1.vx*b1.vx + b1.vy*b1.vy) + b2.m * (b2.vx*b2.vx + b2.vy*b2.vy);

    //Vérification de la conservation de la quantité de mouvement et de l'énergie cinétique
        
        //System.out.printf("%.2f = %.2f\t%.2f = %.2f\t%.2f = %.2f\n",qmx1,qmx2,qmy1,qmy2,ec1,ec2);
    }
}

public class Animation extends Component
{
    private static final long serialVersionUID = 1;
    
    private final Balle[] balles;
    private final Thread daemon;
    
    public Animation(final Dimension dim, final int nbBalles, final double rMax, final double v0, final long freq, final double dt)
    {
        Random random = new Random();
        
        balles = new Balle[nbBalles];
        for (int i=0; i<nbBalles; i++)
        {
            balles[i] = new Balle(random.nextDouble()*rMax,
                    random.nextDouble()*dim.width,
                    random.nextDouble()*dim.height,
                    random.nextDouble()*v0,
                    random.nextDouble()*v0,
                    new Color(Color.HSBtoRGB(random.nextFloat(), 1f, 0.85f)));
        }
        
        daemon = new Thread()
        {
            @Override
            public void run()
            {
                while (true)
                {
                    if (isInterrupted())
                        return;
                    
                    Balle.deplacer(dim.getWidth(), dim.getHeight(), dt, balles);
                    repaint();

                    try
                    {
                        Thread.sleep(freq);
                    }
                    catch (InterruptedException e)
                    {
                        return;
                    }
                }
            }
        };
        
        daemon.setDaemon(true);
        
        setSize(dim);
    }
    
    public void start()
    {
        daemon.start();
    }
    
    public void stop()
    {
        daemon.interrupt();
    }
    
    @Override
    public void paint(Graphics g)
    {
        for (Balle b : balles)
        {
            g.setColor(b.clr);
            g.fillOval((int) (b.x-b.r), (int) (b.y-b.r), (int) (2*b.r), (int) (2*b.r));
        }
    }
    
    public static void main(String[] args)
    {
        JFrame frame = new JFrame();
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
        frame.setVisible(true);
        
        Animation animation = new Animation(frame.getSize(), 10, 50, 10, 50, 1);
        frame.add(animation);
        animation.start();
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}
6
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
7 avril 2013 à 03:02
J'ai adapté le calcul présenté sur l'article Chocs élastique de Wikipédia en fonction de tes contraintes, si tu as des questions théoriques je t'invite donc à lire cet article.
Pour le code en Java, tu devras bien sûr l'adapter en fonction de ce que tu as déjà.

public class Balle
{
    /** Rayon de la balle. */ 
    protected final double r;
    
    /** Masse de la balle. */
    protected final double m;
    
    /** Position de la balle. */
    protected double x,y;
    
    /** Vitesse de la balle. */
    protected double vx,vy;
    
    /**
     * Modifie la position de la balle en fonction de sa vitesse.
     * @param dt durée du déplacement
     */
    protected void deplacement(double dt)
    {
    	x += vx * dt;
    	y += vy * dt;
    }
    
    /**
     * Modifies les vecteurs vitesses de deux balles qui entrent en collision.
     */
    protected static final void collision(Balle b1, Balle b2)
    {
        double nx = (b2.x - b1.x) / (b1.r+b2.r);
        double ny = (b2.y - b1.y) / (b1.r+b2.r);
        double gx = -ny;
        double gy = nx;
        
        double v1n = nx*b1.vx*(b1.m-b2.m)/(b1.m+b2.m) + ny*b2.vx*(2*b2.m)/(b1.m+b2.m);
        double v1g = gx*b1.vx*(b1.m-b2.m)/(b1.m+b2.m) + gy*b2.vx*(2*b2.m)/(b1.m+b2.m);
        double v2n = nx*b2.vx*(b2.m-b1.m)/(b1.m+b2.m) + ny*b1.vx*(2*b1.m)/(b1.m+b2.m);
        double v2g = gx*b2.vx*(b2.m-b1.m)/(b1.m+b2.m) + gy*b1.vx*(2*b1.m)/(b1.m+b2.m);
        
        b1.vx = nx*v2n + gx*v1g;
        b1.vy = ny*v2n + gy*v1g;
        b2.vx = nx*v1n + gx*v2g;
        b2.vy = ny*v1n + gy*v2g;
    }
}
1
Pulsar360 Messages postés 114 Date d'inscription lundi 12 novembre 2012 Statut Membre Dernière intervention 29 août 2018 10
7 avril 2013 à 08:44
Merci KX c'est parfait !!
+1
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
14 avril 2013 à 19:23
En fait c'était n'importe quoi ^^

Je vais faire une autre réponse pour expliquer comment faire ça bien ;-)
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
6 avril 2013 à 23:21
J'ai bien une petite idée, mais j'aimerais avoir quelques précisions avant de me lancer, est-ce que :

1) tes deux balles sont identiques ? (même taille, même poids...)
2) la vitesse est constante ? (est-ce que les balles peuvent ralentir, s'arrêter...)
3) tes deux balles vont à la même vitesse ?
4) tu veux juste une impression de réalisme, ou un calcul exact ?
0
Pulsar360 Messages postés 114 Date d'inscription lundi 12 novembre 2012 Statut Membre Dernière intervention 29 août 2018 10
7 avril 2013 à 01:02
Bonsoir,
1) Non mes deux balles ne sont pas identique (je vais utiliser des diamètres différent et des images pour les balles)
2) Non les vitesses ne seront pas constantes.
3) Les balles ne vont pas non plus à la même vitesse.
4) Ben j'avoue que j'aime la précision donc je veux bien des calculs exact mais au vue de mes réponse si je doit me contenter d'une impression ça ne seras pas trèèès grave.

Merci =)
0