Code Vonc

Trajectoires de projectiles

v 1.0

TéléchargerDownload
Faire un donMake a donation
CommentairesComments

Simulation de la trajectoire d'un projectile.

Interface graphique avec PyGTK.


Fonctionnalités

- Paramétrage de la vitesse de tir.
- Paramétrage de l'angle de tir.
- Paramétrage de la gravité et du vent.
- Gestion des collisions cubiques.
- Positionnement des objets de collesion (clic gauche et droit).
- Coefficient d'absorption aux rebonds paramétrable.
- Précalcul de toute la trajectoire.



Documentation
Calcul de trajectoire d’un projectile
Sommaire
Vecteurs 2D Opérateurs arithmétiques sur des vecteurs 2D Produit vectoriel Symétrie d’un vecteur par rapport à une normale Longueur du vecteur Résolution d’une équation du second degré Calcul du vecteur de tir à partir de l’angle et de la force Calcul de la trajectoire Sans friction Implémentation du vent Calcul de la hauteur max Calcul du temps à l’impact (sans friction) Calcul de la hauteur d’un sol pour une collision à une longueur donnée (sans friction) Calcul du rebond La vitesse à l’impact La normale au plan de collision La vitesse de rebond
Vecteurs 2D

Afin d’intégrer facilement les formules de calculs de projection et autre, une classe Vecteur2D a été créée en Python, comportant les principaux opérateurs arithmétiques, permettant de manipuler des vecteurs comme des nombres.

Opérateurs arithmétiques sur des vecteurs 2D

La classe Vecteur2D comporte deux attributs : x et y. Les opérateurs +, -, *, / sont définis ci-dessous.


class Vecteur2 :

    def __init__(soi, x = 0., y = 0.) :
        soi.x = x
        soi.y = y
    
    def __add__(soi, vec) :
        vec2 = Vecteur2(soi.x + vec.x, soi.y + vec.y)
        return vec2

    def __sub__(soi, vec) :
        vec2 = Vecteur2(soi.x - vec.x, soi.y - vec.y)
        return vec2

    def __mul__(soi, a) :
        vec2 = Vecteur2(soi.x * a, soi.y * a)
        return vec2

    def __div__(soi, a) :
        vec2 = Vecteur2(soi.x / a, soi.y / a)
        return vec2
Produit vectoriel

Le produit vectoriel renvoie un vecteur perpendiculaire au plan formé par deux vecteurs donnés. L’ordre d’entrée détermine le sens du vecteur de sortie.


def __ixor__(soi, vec) : # Produit vectoriel ^
	vec2 = Vecteur2((soi.y * vec.x) - (soi.x * vec.y), (soi.x * vec.y) - (soi.y * vec.x))
	return vec2
Produit scalaire

Le produit scalaire sert à calculer l’influence d’un vecteur sur un autre, par rapport à l’angle entre les deux.
Deux vecteurs perpendiculaires auront un produit scalaire nul (le premier vecteur ne va pas du tout dans le même sens que l’autre).
Tandis que deux mêmes vecteurs auront une influence maximale (égale au produit de leur longueur), car les deux vecteurs vont dans le même sens, multipliant ainsi leur force.


def scalaire(soi, vec) : # Produit scalaire
	return vec.x * soi.x + vec.y * soi.y
Symétrie d’un vecteur par rapport à une normale

Pour calculer la symétrie VB d’un vecteur VA par rapport à une normale N, on remarque que le triangle OAB est isocèle en O. VB et B sont inconnus. Le but dans une premier temps est de calculer le milieu M de AB.
Pour cela, on calcule le produit scalaire de VA sur N, qui va donner l’intensité qu’a le vecteur VA sur N (voir ci-dessus), pour le multiplier à N et en déduire N’, qui arrive entre A et B.

On en déduit le Vecteur AM = N’ - VA

On sait que AM = MB, donc OB = AM × 2

Donc :
VB = 2 × N’ - VA
VB = 2 × N × (N • VA) - VA

La symétrie utilisée pour le TP concerne le rebond, le vecteur VA est inversé, on pose donc :

VB = VA - 2 × N × (N • VA)


def symetrie(soi, vec) :
	return Vecteur2(soi.x, soi.y) - (vec * 2.0 * soi.scalaire(vec));
Longueur du vecteur

La longueur est calculée avec le théorème de Pythagore, en calculant l'hypoténuse du triangle dont les côtés perpendiculaires sont le segments X et Y du vecteur.


def getLongueur(soi) :
	return (soi.x ** 2 + soi.y ** 2) ** 0.5
Résolution d’une équation du second degré

Certaines formules ci-dessous demandent la résolution d’une équation du second degré. Ci-dessous son implémentation en Python avec le calcul de Delta et les deux résultats renvoyés.

Paramètres d’entrée

Les facteurs a, b, c, correspondants à l’équation ax² + bx + c= 0

Valeurs de sortie

Les solutions x1 et x2 s’il y en a

Code Python

    @staticmethod
    def resoutEquationPremierDegre(a, b) :
        # ax + b = 0
        
        if a == 0 : return None
        return -b / a
    
    def resoutEquationSecondDegre(soi, a, b, c) :
        # ax² + bx + c = 0
        
        if a == 0 :
            x = soi.resoutEquationPremierDegre(b, c)
            return x, x
            
        delta = (b ** 2) - (4. * a * c)
        if delta < 0 :
            return None, None
        racineDelta = delta ** .5
        x1 = (-b - racineDelta) / (2. * a)
        x2 = (-b + racineDelta) / (2. * a)
        return x1, x2
Calcul du vecteur de tir à partir de l’angle et de la force

Afin de déterminer le vecteur de vitesse initial à partir d’un angle et une vitesse donnée, on calcule le cosinus de l’angle, qui va donner le vecteur X, et le sinus de l’angle : le vecteur Y.
Multiplié par la vitesse en m/s, on obtient le vecteur vitesse.

Paramètres d’entrée

Angle en degré
Vitesse en m/s (puissance)

Valeur de sortie

Vecteur de vitesse initiale en m/s

Code Python

    @staticmethod
    def calculeVecteurVitesse(angle, puissance) :
        vec = Vecteur2(math.cos(angle), math.sin(angle))
        vec *= puissance
        return vec
Calcul de la trajectoire
Sans friction

L’équation de calcul de trajectoire sans friction est la suivante :
f(t) = p + v × t + (g × t²) / 2

Paramètres d’entrée

Vecteur position initiale en m (p)
Vecteur vitesse en m/s (v)
Temps en secondes (t)
Gravité + vent en m·s−2 (g)

Valeur de sortie

Vecteur de position du projectile à un instant t

Code Python

    @staticmethod
    def formuleSansFriction(p, v, t, g) :
        return p + v * t + g * .5 * (t ** 2)
Implémentation du vent

Les calculs comportent l’implémentation de la force du vent, notée sous forme d’un vecteur 2D. Celle-ci est additionnée à la force de gravitation.

Calcul de la hauteur max

Le calcul de la hauteur max ne comporte que la composante Y des vecteurs, à partir de la vitesse initiale, la position initiale, la gravité et la force du vent.

Paramètres d’entrée

Position Y en m (p)
Vitesse Y en m/s (v)
Gravité Y + vent Y en m·s−2 (g)

Valeur de sortie

Hauteur max en m

Code Python

    @staticmethod
    def formuleCalculeHauteurMax(p, v, g) :
        if g.y == 0 : return None
        return p.y + ((v.y ** 2) / (2. * -g.y))
Calcul du temps à l’impact (sans friction)

L’équation de calcul de trajectoire renvoyant la position à un instant t, le calcul du temps à l’impact doit vérifier que l’équation de trajectoire = la position d’impact à un instant t.
Il suffit ensuite d’isoler le temps t de l’équation de trajectoire.

pi = p + v × t + (g × t²) / 2

Avec

Position d’impact Y en m (pi)
Position de départ Y en m (p)
Vitesse Y en m/s (v)
Gravité Y + vent Y en m·s−2 (g)
Temps d’impact en sec (t)
On résout l’équation du second degré pour trouver les solutions de t :

0 = (g / 2) × t² + v × t + p - pi

Valeur de sortie

Les temps d’impact t1 et t2 en sec. L’un est négatif car la courbe de trajectoire coupe la droite définie par f(x) = pi à deux endroits, la valeur pertinente est le t positif car on évolue avec un temps qui augmente.

Exemple de code Python pour une position d’impact Y donnée

t1, t2 = soi.resoutEquationSecondDegre(.5 * g.y, v.y, p.y - pi.y)
Calcul de la hauteur d’un sol pour une collision à une longueur donnée (sans friction)

Pour savoir à quelle hauteur il faut placer un sol pour avoir une collision à une distance X donnée, on utilise la formule de calcul de trajectoire avec le temps d’impact calculé précédemment.

h = p + v × t + (g × t²) / 2

Avec

Position de départ Y en m (p)
Vitesse Y en m/s (v)
Gravité Y + vent Y en m·s−2 (g)
Temps d’impact en sec (t)

Valeur de retour

Position Y du sol en m (h)

Calcul du rebond

Pour calculer le vecteur de vitesse au rebond, on calcule :

La vitesse à l’impact

Elle est égale à la vitesse de départ + les forces × le temps d’impact.

vi = f × ti + v

Avec :

Vecteur vitesse impact en m/s
Forces (gravité + vent) en m·s−2
Temps impact en sec
Vecteur vitesse de départ en m/s

La normale au plan de collision

Elle est la perpendiculaire au plan de collision.

AB = B - A
nor = (-AB.y, AB.x)

Avec

Vecteur normale (nor)
Vecteur point A du plan de collision
Vecteur point B du plan de collision
Vecteur AB

La vitesse de rebond

C’est la symétrie de la vitesse d’impact par rapport à la normale du plan de collision, multipliée par un coefficient d'absorption.
vr = vi.symétrie(nor) × a

(Voir Vecteur symétrie)

vr = vi - 2 × nor × (vi • nor) × a

Avec

Vecteur de rebond (vr)
Vecteur d’impact (vi)
Vecteur normale (nor)
Coefficient d’absorption (a)





Codes sourcesSources code

trajectoires_projectiles.py

#! /usr/bin/python
# coding: utf-8
# GPLv3
# César Guillotel
# 3A 3DJV

import gtk
import gobject
import math

class Vecteur2 :

    def __init__(soi, x = 0., y = 0.) :
        soi.x = x
        soi.y = y
    
    def __add__(soi, vec) :
        vec2 = Vecteur2(soi.x + vec.x, soi.y + vec.y)
        return vec2

    def __sub__(soi, vec) :
        vec2 = Vecteur2(soi.x - vec.x, soi.y - vec.y)
        return vec2

    def __mul__(soi, a) :
        vec2 = Vecteur2(soi.x * a, soi.y * a)
        return vec2

    def __div__(soi, a) :
        vec2 = Vecteur2(soi.x / a, soi.y / a)
        return vec2

    def __ixor__(soi, vec) : # Produit vectoriel ^
        vec2 = Vecteur2((soi.y * vec.x) - (soi.x * vec.y), (soi.x * vec.y) - (soi.y * vec.x))
        return vec2
    
    def scalaire(soi, vec) : # Produit scalaire
        return vec.x * soi.x + vec.y * soi.y
    
    def symetrie(soi, vec) :
        return Vecteur2(soi.x, soi.y) - (vec * 2.0 * soi.scalaire(vec));
    
    def __str__(soi) :
        return "(" + str(soi.x) + ", " + str(soi.y) + ")"
        
    def getLongueur(soi) :
        return (soi.x ** 2 + soi.y ** 2) ** 0.5
    

class Boite :
    
    def __init__(soi, pos, taille, nom = "") :
        pos = pos - Vecteur2(16., 16.)
        taille = taille + Vecteur2(16., 16.)
        soi.pos = pos
        soi.taille = taille
        soi.min = Vecteur2(pos.x - taille.x / 2., pos.y - taille.y / 2.)
        soi.max = Vecteur2(pos.x + taille.x / 2., pos.y + taille.y / 2.)
        soi.nom = nom
    
class Trajectoire :
    
    def __init__(soi, tDepart, posDepart, vitDepart, posImpact, norImpact, tImpact, t, forces, absorption = 0.6) :
    
        soi.tDepart = tDepart
        soi.posDepart = posDepart
        soi.vitDepart = vitDepart
        soi.posImpact = posImpact
        soi.norImpact = norImpact
        soi.tImpact = tImpact
        soi.t = t
        soi.vitImpact = None
        soi.vitRebond = None
        
        if vitDepart is not None and tImpact is not None :
            soi.vitImpact = forces * t + vitDepart
            
        if soi.vitImpact is not None and tImpact is not None and norImpact is not None :
            # Symétrie
            soi.vitRebond = soi.vitImpact.symetrie(norImpact) * (1. - absorption)
        
    def __str__(soi) :
        s = ""
        s += "\ntDepart : " + str(soi.tDepart)
        s += "\nposDepart : " + str(soi.posDepart)
        s += "\nvitDepart : " + str(soi.vitDepart)
        s += "\nposImpact : " + str(soi.posImpact)
        s += "\nnorImpact : " + str(soi.norImpact)
        s += "\ntImpact : " + str(soi.tImpact)
        s += "\nt : " + str(soi.t)
        s += "\nvitImpact : " + str(soi.vitImpact)
        s += "\nvitRebond : " + str(soi.vitRebond)
        s += "\n"
        return s
        
    def __repr__(soi) :
        s = ""
        s += "\ntDepart : " + str(soi.tDepart)
        s += "\nposDepart : " + str(soi.posDepart)
        s += "\nvitDepart : " + str(soi.vitDepart)
        s += "\nposImpact : " + str(soi.posImpact)
        s += "\nnorImpact : " + str(soi.norImpact)
        s += "\ntImpact : " + str(soi.tImpact)
        s += "\nt : " + str(soi.t)
        s += "\nvitImpact : " + str(soi.vitImpact)
        s += "\nvitRebond : " + str(soi.vitRebond)
        s += "\n"
        return s
        
class ProjetMaths :
    
    def __init__(soi) :
        
        soi.bouleLarg = 32
        soi.bouleHaut = 32
        soi.boulePosInit = Vecteur2(0, 0)
        soi.boulePos = Vecteur2(0, 0)
        soi.bouleVitInit = Vecteur2(50, 70)
        soi.bouleAngle = 0
        
        soi.gravite = Vecteur2(0, -9.81)
        soi.vent = Vecteur2(0, 0)
        
        soi.solPos = Vecteur2(0, 100)
        soi.sol2Pos = Vecteur2(600, 100)
        soi.sol2Larg = 128
        soi.solLarg = 128
        soi.solHaut = 512
        
        soi.hMaxPos = Vecteur2()
        soi.hMaxLarg = 64
        soi.hMaxHaut = 64
        
        soi.trajectoires = []
        soi.trajetI = 0
        
        soi.impactPos = Vecteur2()
        soi.impactLarg = 32
        soi.impactHaut = 32
        
        soi.boitePos = Vecteur2(700., 400.)
        soi.boiteTaille = Vecteur2(200., 128.)
        soi.boite2Pos = Vecteur2(1400., 300.)
        soi.boite2Taille = Vecteur2(128., 128.)
        
        soi.x = 50
        soi.y = 50
        soi.t = 0
        soi.l = 1600
        soi.h = 800
        
        soi.absorption = 0.6
        soi.nbc = 10
        soi.nbi = 3
        
        soi.courbe = []
        soi.boitesColl = []
        
        soi.fenetre = gtk.Window()
        # fenetre.set_size_request(soi.l , soi.h)
        soi.fenetre.set_position(gtk.WIN_POS_CENTER)
        soi.fenetre.connect("destroy", gtk.main_quit)
        
        soi.eve = gtk.EventBox()
        soi.fenetre.add(soi.eve)
        
        vb1 = gtk.HBox()
        soi.eve.add(vb1)
        
        hb1 = gtk.VBox()
        hb2 = gtk.HBox()
        vb1.add(hb1)
        vb1.add(hb2)
        
        hb1_menu = gtk.HBox()
        hb2_menu = gtk.HBox()
        hb3_menu = gtk.HBox()
        hb4_menu = gtk.HBox()
        hb5_menu = gtk.HBox()
        hb6_menu = gtk.HBox()
        hb7_menu = gtk.HBox()
        hb8_menu = gtk.HBox()
        hb9_menu = gtk.HBox()
        hb10_menu = gtk.HBox()
        hb11_menu = gtk.HBox()
        hb1.add(hb1_menu)
        hb1.add(hb2_menu)
        hb1.add(hb3_menu)
        hb1.add(hb4_menu)
        hb1.add(hb5_menu)
        hb1.add(hb6_menu)
        hb1.add(hb7_menu)
        hb1.add(hb8_menu)
        hb1.add(hb9_menu)
        hb1.add(hb10_menu)
        hb1.add(hb11_menu)
        
        soi.timer = None
        
        soi.bouleBuf = gtk.gdk.pixbuf_new_from_file_at_size("boule.png", soi.bouleLarg, soi.bouleHaut)
        soi.hmaxBuf = gtk.gdk.pixbuf_new_from_file_at_size("hmax.png", 64, 64)
        soi.solBuf = gtk.gdk.pixbuf_new_from_file_at_size("sol.png", soi.sol2Larg, 512)
        soi.impactBuf = gtk.gdk.pixbuf_new_from_file_at_size("impact.png", soi.impactLarg, soi.impactHaut)
        soi.ventBuf = gtk.gdk.pixbuf_new_from_file_at_size("vent.png", 32, 32)
        soi.boiteBuf = gtk.gdk.pixbuf_new_from_file_at_size("boite2.png", int(soi.boiteTaille.x), int(soi.boiteTaille.y))
        soi.boite2Buf = gtk.gdk.pixbuf_new_from_file_at_size("boite.png", int(soi.boite2Taille.x), int(soi.boite2Taille.y))
        
        soi.background = gtk.gdk.pixbuf_new_from_file("ap.jpg")
        soi.frame = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, soi.l, soi.h)
        soi.da = gtk.DrawingArea()
        soi.da.connect("expose_event", soi.exposer)
        soi.da.set_size_request(soi.l , soi.h)
        soi.da.connect("button-press-event", soi.positionneBoite)
        soi.da.set_events(gtk.gdk.BUTTON_PRESS_MASK)
        
        hb2.add(soi.da)
        
        # Interface
        
        soi.btnLancer = gtk.Button("Lancer")
        soi.btnLancer.connect("clicked", soi.clique)
        
        soi.libelleSol = gtk.Label("Position Sol X,Y")
        soi.libelleSol.set_size_request(100, -1)
        soi.champSol = gtk.Entry()
        soi.champSol.set_text("200,200")
        soi.champSol.connect("activate", soi.actualise)
        
        soi.libelleSol2 = gtk.Label("Position Sol 2 Y")
        soi.libelleSol2.set_size_request(100, -1)
        soi.champSol2 = gtk.Entry()
        soi.champSol2.set_text("195")
        soi.champSol2.connect("activate", soi.actualise)
        
        soi.libelleBoule = gtk.Label("Position Boule X,Y")
        soi.libelleBoule.set_size_request(100, -1)
        soi.champBoule = gtk.Entry()
        soi.champBoule.set_text("0,0")
        soi.champBoule.connect("activate", soi.actualise)
        
        soi.libelleAngle = gtk.Label("Angle (°)")
        soi.libelleAngle.set_size_request(100, -1)
        soi.champAngle = gtk.Entry()
        soi.champAngle.set_text("60")
        soi.champAngle.connect("activate", soi.actualise)
        
        soi.libelleVitesse = gtk.Label("Vitesse (m/s)")
        soi.libelleVitesse.set_size_request(100, -1)
        soi.champVitesse = gtk.Entry()
        soi.champVitesse.set_text("100")
        soi.champVitesse.connect("activate", soi.actualise)
        
        soi.libelleGravite = gtk.Label("Gravité (m/s-2)")
        soi.libelleGravite.set_size_request(100, -1)
        soi.champGravite = gtk.Entry()
        soi.champGravite.set_text("-9.81")
        soi.champGravite.connect("activate", soi.actualise)
        
        soi.libelleVent = gtk.Label("Vent X,Y")
        soi.libelleVent.set_size_request(100, -1)
        soi.champVent = gtk.Entry()
        soi.champVent.set_text("0,0")
        soi.champVent.connect("activate", soi.actualise)
        
        soi.libelleAbs = gtk.Label("Absorption")
        soi.libelleAbs.set_size_request(100, -1)
        soi.champAbs = gtk.Entry()
        soi.champAbs.set_text("0.3")
        soi.champAbs.connect("activate", soi.actualise)
        
        soi.libelleNbi = gtk.Label("Nb iterations")
        soi.libelleNbi.set_size_request(100, -1)
        soi.champNbi = gtk.Entry()
        soi.champNbi.set_text("20")
        soi.champNbi.connect("activate", soi.actualise)
        
        soi.libelleNbc = gtk.Label("Nb affichés")
        soi.libelleNbc.set_size_request(100, -1)
        soi.champNbc = gtk.Entry()
        soi.champNbc.set_text("3")
        soi.champNbc.connect("activate", soi.actualise)
        
        hb1_menu.add(soi.libelleSol)
        hb1_menu.add(soi.champSol)
        hb2_menu.add(soi.libelleSol2)
        hb2_menu.add(soi.champSol2)
        hb3_menu.add(soi.libelleBoule)
        hb3_menu.add(soi.champBoule)
        hb4_menu.add(soi.libelleAngle)
        hb4_menu.add(soi.champAngle)
        hb5_menu.add(soi.libelleVitesse)
        hb5_menu.add(soi.champVitesse)
        hb6_menu.add(soi.libelleGravite)
        hb6_menu.add(soi.champGravite)
        hb7_menu.add(soi.libelleVent)
        hb7_menu.add(soi.champVent)
        hb8_menu.add(soi.libelleAbs)
        hb8_menu.add(soi.champAbs)
        hb9_menu.add(soi.libelleNbi)
        hb9_menu.add(soi.champNbi)
        hb10_menu.add(soi.libelleNbc)
        hb10_menu.add(soi.champNbc)
        hb11_menu.add(soi.btnLancer)
        
        soi.rect = gtk.gdk.Rectangle()
        soi.rect.x = 0
        soi.rect.y = 0
        soi.rect.width = soi.l
        soi.rect.height = soi.h
        
        # Lancement
        
        soi.fenetre.show_all()
        
        soi.style = soi.da.get_style()
        
        soi.gc = soi.style.fg_gc[gtk.STATE_NORMAL]
        
        soi.actualise(None)
        
        gtk.main()
    
    def exposer(soi, draw_area, event) :
        soi.style = soi.da.get_style()
        soi.gc = soi.style.fg_gc[gtk.STATE_NORMAL]
        
        rowstride = soi.frame.get_rowstride()
        pixels = soi.frame.get_pixels()
        draw_area.window.draw_rgb_image(draw_area.style.black_gc,
                                        event.area.x,
                                        event.area.y,
                                        event.area.width,
                                        event.area.height,
                                        'normal',
                                        pixels,
                                        rowstride,
                                        event.area.x,
                                        event.area.y)
                                        
        
        soi.dessineFond()
        soi.dessineSol()
        soi.dessineSol2()
        soi.dessineBoite()
        soi.dessineBoite2()
        soi.dessineBoule()
        soi.dessineHmax()
        soi.dessineVent()
        soi.dessineImpact()
        soi.dessinePuissance()
        soi.dessineCourbe()
        # soi.gc = self.style.fg_gc[gtk.STATE_NORMAL]
    
    def positionneBoite(soi, widget, event) :
        if event.button == 3 :
            soi.boite2Pos.x = event.x
            soi.boite2Pos.y = soi.h - event.y
        else :
            soi.boitePos.x = event.x
            soi.boitePos.y = soi.h - event.y
        soi.actualise(None)
    
    def positionneHmax(soi) :
        ymax = soi.formuleCalculeHauteurMax(soi.boulePosInit, soi.bouleVitInit, soi.gravite + soi.vent)
        
        if ymax is None :
            soi.hMaxPos.x = -999
            return
        
        ymax += soi.bouleHaut + 32
        xmax = soi.formuleCalculeXMax(soi.boulePosInit, soi.bouleVitInit, soi.gravite + soi.vent, ymax)
        xmax += soi.bouleLarg * .5 - 32
        
        soi.hMaxPos.x = xmax
        soi.hMaxPos.y = ymax
        
    
    def positionneSol(soi) :
        soi.boulePosInit += soi.solPos
        soi.boulePos = soi.boulePosInit
    
    def positionneSol2(soi) :
        soi.tToucheSol2 = soi.formuleCalculeTempsToucheY(soi.boulePosInit, soi.bouleVitInit, soi.gravite + soi.vent, soi.sol2Pos)
        
        if soi.tToucheSol2 is None :
            soi.sol2Pos.x = -999
            soi.impactPos.x = -999
            return
        
        posToucheSol2 = soi.formuleSansFriction(soi.boulePosInit, soi.bouleVitInit, soi.tToucheSol2, soi.gravite + soi.vent)
        
        if posToucheSol2 is None :
            soi.sol2Pos.x = -999
            soi.impactPos.x = -999
            return
        
        soi.sol2Pos.x = posToucheSol2.x
        soi.impactPos.x = posToucheSol2.x
        soi.impactPos.y = posToucheSol2.y
    
    def initBoitesColl(soi) :
        soi.boitesColl = []
        soi.boitesColl.append(Boite(Vecteur2(soi.solPos.x, soi.solPos.y - 256.), Vecteur2(128., 512.), "sol"))
        soi.boitesColl.append(Boite(Vecteur2(soi.sol2Pos.x, soi.sol2Pos.y - 256.), Vecteur2(128., 512.), "sol 2"))
        soi.boitesColl.append(Boite(Vecteur2(soi.l / 2., 0.), Vecteur2(soi.l, 1.), "bord bas"))
        soi.boitesColl.append(Boite(Vecteur2(soi.l / 2., soi.h), Vecteur2(soi.l, 1.), "bord haut"))
        soi.boitesColl.append(Boite(Vecteur2(soi.l, soi.h / 2.), Vecteur2(1., soi.h), "bord droit"))
        soi.boitesColl.append(Boite(Vecteur2(0., soi.h / 2.), Vecteur2(1., soi.h), "bord gauche"))
        soi.boitesColl.append(Boite(soi.boitePos, soi.boiteTaille, "boite"))
        soi.boitesColl.append(Boite(soi.boite2Pos, soi.boite2Taille, "boite 2"))
    
    def detecteCollision(soi, p, v, g, t, boite) :
        
        aColl = False
        
        tImpact = None
        posImpact = None
        norImpact = None
        
        # Collision cubique : cherche la collision la plus proche dans le temps des 4 faces
        
        ti = soi.formuleCalculeTempsToucheY(p, v, g, boite.min, True)
        if ti is not None and ti > 0.0001 :
            pi = soi.formuleSansFriction(p, v, ti, g)
            if pi.x > boite.min.x and pi.x < boite.max.x :
                if tImpact is None or ti < tImpact :
                    tImpact = ti
                    posImpact = pi
                    norImpact = Vecteur2(0., 1.)
        
        ti = soi.formuleCalculeTempsToucheY(p, v, g, boite.max)
        if ti is not None and ti > 0.0001 :
            pi = soi.formuleSansFriction(p, v, ti, g)
            if pi.x > boite.min.x and pi.x < boite.max.x :
                if tImpact is None or ti < tImpact :
                    tImpact = ti
                    posImpact = pi
                    norImpact = Vecteur2(0., -1.)
        
        ti = soi.formuleCalculeTempsToucheX(p, v, g, boite.min)
        if ti is not None and ti > 0.0001 :
            pi = soi.formuleSansFriction(p, v, ti, g)
            if pi.y > boite.min.y and pi.y < boite.max.y :
                if tImpact is None or ti < tImpact :
                    tImpact = ti
                    posImpact = pi
                    norImpact = Vecteur2(-1., 0.)
        
        ti = soi.formuleCalculeTempsToucheX(p, v, g, boite.max)
        if ti is not None and ti > 0.0001 :
            pi = soi.formuleSansFriction(p, v, ti, g)
            if pi.y > boite.min.y and pi.y < boite.max.y :
                if tImpact is None or ti < tImpact :
                    tImpact = ti
                    posImpact = pi
                    norImpact = Vecteur2(1., 0.)
        
        return tImpact, posImpact, norImpact
        
    
    def calculeImpacts(soi, t, pos, vit, n) :
    
        if n == soi.nbi : return
        
        tImpact = None
        posImpact = None
        norImpact = None
        
        for boite in soi.boitesColl :
            ti, pi, ni = soi.detecteCollision(pos, 
                                                                 vit, 
                                                                 soi.gravite + soi.vent, 
                                                                 t,
                                                                 boite)
            if ti is not None :
                # Prend l'impact le plus proche dans le temps
                if tImpact is None or ti < tImpact :
                    tImpact = ti
                    posImpact = pi
                    norImpact = ni
        
        if tImpact is None :
            
            trajectoire = Trajectoire(t, pos, vit, 
                                      None, None, None, None, 
                                      soi.gravite + soi.vent, soi.absorption)
            
            soi.trajectoires.append(trajectoire)
        
        else :
        
            trajectoire = Trajectoire(t, pos, vit, 
                                      posImpact, norImpact, tImpact + t, tImpact, 
                                      soi.gravite + soi.vent, soi.absorption)
        
            soi.trajectoires.append(trajectoire)
            
            soi.calculeImpacts(tImpact + t, posImpact, trajectoire.vitRebond, n + 1)
    
    
    def calculeImpacts2(soi, t, pos, vit, n) :
        
        if n == 10 : return
        
        tImpact = soi.formuleCalculeTempsToucheY(pos, vit, soi.gravite + soi.vent, soi.sol2Pos)
        
        if tImpact is not None : tImpact += t
        
        if tImpact is None :
            
            trajectoire = Trajectoire(t, pos, vit, 
                                      None, None, None, None, 
                                      soi.gravite + soi.vent, soi.absorption)
            
            soi.trajectoires.append(trajectoire)
        
        else :
        
            posImpact = soi.formuleSansFriction(pos, vit, tImpact-t, soi.gravite + soi.vent)
            
            trajectoire = Trajectoire(t, pos, vit, 
                                      posImpact, Vecteur2(0., 1.), tImpact, tImpact - t, 
                                      soi.gravite + soi.vent, soi.absorption)
        
            soi.trajectoires.append(trajectoire)
            
            soi.calculeImpacts(tImpact, posImpact, trajectoire.vitRebond, n + 1)
            
            # trajectoire2 = Trajectoire(tImpact, posImpact, trajectoire.vitRebond, 
                                      # None, None, None, soi.gravite + soi.vent)
        
            # soi.trajectoires.append(trajectoire2)
    
    def calculeCourbe(soi) :
        soi.courbe = []
        
        if soi.nbc == 0 : return
        
        for traj in soi.trajectoires :
            ct = []
            t = 0
            tmax = 40
            if traj.tImpact is not None :
                tmax = traj.tImpact - traj.tDepart
            
            if tmax > 100 : tmax = 100
            
            while t < tmax :
                pos = soi.formuleSansFriction(traj.posDepart, traj.vitDepart, t, soi.gravite + soi.vent)
                ct.append(pos)
                t += 0.2
                
            soi.courbe.append(ct)
    
    @staticmethod
    def calculeVecteurVitesse(angle, puissance) :
        vec = Vecteur2(math.cos(angle), math.sin(angle))
        vec *= puissance
        return vec
    
    @staticmethod
    def formuleSansFriction(p, v, t, g) :
        return p + v * t + g * .5 * (t ** 2)
    
    @staticmethod
    def formuleAvecFriction(p, v, t, g) :
        return p + v * t + g * .5 * (t ** 2)
    
    @staticmethod
    def formuleCalculeHauteurMax(p, v, g) :
        if g.y == 0 : return None
        return p.y + ((v.y ** 2) / (2. * -g.y))
    
    def formuleCalculeXMax(soi, p, v, g, ymax) : # Marche pas bien
        return p.x + ((v.x ** 2) / (2. * -g.y))
        
        # Temps quand on atteint la hauteur max
        # t1, t2 = soi.resoutEquationSecondDegre(.5 * g.y, v.y, p.y - ymax - 0.01)
        # print t1, t2
        # if t1 is None : return 0
        # t = t1
        # if t1 < t2 : t = t2
        
        # Position x à h max
        
        # posHmax = soi.formuleSansFriction(p, v, t, g)
        # print posHmax
        # return posHmax.x
    
    @staticmethod
    def resoutEquationPremierDegre(a, b) :
        # ax + b = 0
        
        if a == 0 : return None
        return -b / a
    
    def resoutEquationSecondDegre(soi, a, b, c) :
        # ax² + bx + c = 0
        
        if a == 0 :
            x = soi.resoutEquationPremierDegre(b, c)
            return x, x
            
        delta = (b ** 2) - (4. * a * c)
        if delta < 0 :
            return None, None
        racineDelta = delta ** .5
        x1 = (-b - racineDelta) / (2. * a)
        x2 = (-b + racineDelta) / (2. * a)
        return x1, x2
    
    def formuleCalculeTempsToucheY(soi, p, v, g, pi, x = False) :
        t1, t2 = soi.resoutEquationSecondDegre(.5 * g.y, v.y, p.y - pi.y)
        if t1 is None : return None
        if x : return t2
        return t1
        return t2
        if t1 < 0.001 :
            return t2
        return t1
    
    def formuleCalculeTempsToucheX(soi, p, v, g, pi) :
        t1, t2 = soi.resoutEquationSecondDegre(.5 * g.x, v.x, p.x - pi.x)
        if t1 is None : return None
        if t1 < 0.001 :
            return t2
        return min(t1, t2)
    
    def dessine(soi, buf, x, y, l, h) :
        r1 = gtk.gdk.Rectangle()
        r1.x = int(x)
        r1.y = int(y)
        r1.width = l
        r1.height = h
        
        dest = r1.intersect(soi.rect)
        if dest is not None :
            buf.composite(soi.frame,
                            dest.x,
                            dest.y,
                            dest.width,
                            dest.height,
                            x,
                            y,
                            1,
                            1,
                            gtk.gdk.INTERP_NEAREST, 255)
    
    def dessineFond(soi) :
        soi.background.copy_area(0, 0, soi.l, soi.h, soi.frame, 0, 0)
        
    def dessineBoule(soi) :
        xpos = int(soi.boulePos.x)
        ypos = soi.h - int(soi.boulePos.y) - soi.bouleHaut
        soi.dessine(soi.bouleBuf, xpos, ypos, soi.bouleLarg, soi.bouleHaut)
    
    def dessineSol(soi) :
        # xpos = int(soi.solPos.x)
        xpos = int(soi.solPos.x + soi.bouleLarg*.5 - soi.solLarg*.5) - 16
        ypos = soi.h - int(soi.solPos.y)
        soi.dessine(soi.solBuf, xpos, ypos, soi.solLarg, soi.solHaut)
    
    def dessineSol2(soi) :
        xpos = int(soi.sol2Pos.x + soi.bouleLarg*.5 - soi.sol2Larg*.5) - 16
        ypos = soi.h - int(soi.sol2Pos.y)
        soi.dessine(soi.solBuf, xpos, ypos, soi.solLarg, soi.solHaut)
    
    def dessineBoite(soi) :
        soi.dessine(soi.boiteBuf, int(soi.boitePos.x) - int(soi.boiteTaille.x) / 2, 
                                  int(soi.h - soi.boitePos.y) - int(soi.boiteTaille.y) / 2,
                                  int(soi.boiteTaille.x), int(soi.boiteTaille.y))
    
    def dessineBoite2(soi) :
        soi.dessine(soi.boite2Buf, int(soi.boite2Pos.x) - int(soi.boite2Taille.x) / 2, 
                                  int(soi.h - soi.boite2Pos.y) - int(soi.boite2Taille.y) / 2,
                                  int(soi.boite2Taille.x), int(soi.boite2Taille.y))
    
    def dessineHmax(soi) :
        soi.dessine(soi.hmaxBuf, int(soi.hMaxPos.x), soi.h - int(soi.hMaxPos.y), soi.hMaxLarg, soi.hMaxHaut)
    
    def dessineVent(soi) :
        if abs(soi.vent.x) <= 0.001 and abs(soi.vent.y) <= 0.001 :
            return
        soi.dessine(soi.ventBuf, soi.l - 100, 50, 32, 32)
        x1 = soi.l - 100
        y1 = 58
        x2 = int(x1 + soi.vent.x * 10)
        y2 = int(y1 + soi.vent.y * -10)
        soi.da.window.draw_line(soi.gc, x1, y1, x2, y2)
        soi.da.window.draw_line(soi.gc, x1+1, y1, x2+1, y2)
        # soi.da.window.draw_line(soi.gc, x1, y1, x1, y1+30)
        # soi.da.window.draw_line(soi.gc, x1+1, y1, x1+1, y1+30)
        # soi.da.window.draw_line(soi.gc, x1-1, y1, x1-1, y1+30)
    
    def dessineImpact(soi) :
        soi.dessine(soi.impactBuf, int(soi.impactPos.x + soi.bouleLarg * .25), soi.h - int(soi.impactPos.y) - soi.impactHaut, soi.impactLarg, soi.impactHaut)
    
    def dessinePuissance(soi) :
        if not soi.gc : return
        x1 = int(soi.boulePosInit.x + soi.bouleLarg / 2.)
        y1 = int(soi.h - soi.boulePosInit.y - soi.bouleHaut / 2.)
        x2 = int(soi.bouleVitInit.x + soi.boulePosInit.x + soi.bouleLarg / 2.)
        y2 = soi.h - int(soi.bouleVitInit.y + soi.boulePosInit.y + soi.bouleHaut / 2.)
        soi.da.window.draw_line(soi.gc, x1, y1, x2, y2)
        soi.da.window.draw_line(soi.gc, x1+1, y1, x2+1, y2)
    
    def dessineCourbe(soi) :
        if not soi.gc : return
        
        i = soi.trajetI
        imax = i + soi.nbc
        tc = len(soi.courbe)
        
        if imax > tc :
            imax = tc
        
        while i < imax :
            ct = soi.courbe[i]
            for p in ct :
                x = int(p.x) + soi.bouleLarg
                y = soi.h - int(p.y)
                x = int(p.x + soi.bouleLarg * .5)
                y = soi.h - int(p.y + soi.bouleHaut * .5)
                
                # soi.gc.set_foreground(gtk.gdk.Color(0, 0, 0))
                soi.da.window.draw_point(soi.gc, x, y)
                
                # soi.gc.set_foreground(gtk.gdk.Color(128, 128, 128))
                soi.da.window.draw_point(soi.gc, x+1, y)
                soi.da.window.draw_point(soi.gc, x, y+1)
                soi.da.window.draw_point(soi.gc, x+1, y+1)
                
            i += 1
    
    def timeout(soi) :
        
        if not soi.trajectoires : return True
        
        soi.t += 0.13
        
        traj = soi.trajectoires[soi.trajetI]
        
        if traj.tImpact and soi.t > traj.tImpact :
        
            if soi.trajetI >= len(soi.trajectoires) - 1 :
                return True
                
            soi.trajetI += 1
            traj = soi.trajectoires[soi.trajetI]
            
        if len(soi.trajectoires) > soi.trajetI :
            
            soi.boulePos = soi.formuleSansFriction(traj.posDepart, traj.vitDepart, soi.t - traj.tDepart, 
                                                   soi.gravite + soi.vent)
            
        # Frame
        
        soi.fenetre.queue_draw()
        
        return True
    
    # Eve
    
    def clique(soi, arg) :
        soi.actualise(None)
        
        if soi.timer is not None :
            gobject.source_remove(soi.timer)
            soi.timer = None
            return
        
        soi.timer = gobject.timeout_add(20, soi.timeout)
    
    def reinit(soi) :
        soi.actualise(None)
    
    def actualise(soi, arg) :
        soi.boulePosInit = Vecteur2()
        soi.t = 0
        
        txtVec = soi.champSol.get_text().split(',')
        if len(txtVec) == 2 :
            soi.solPos = Vecteur2(int(txtVec[0]), int(txtVec[1]))
        
        txt = soi.champAbs.get_text()
        soi.absorption = float(txt)
        
        txtVec = soi.champSol2.get_text()
        soi.sol2Pos = Vecteur2(0, int(txtVec))
        
        txtVec = soi.champBoule.get_text().split(',')
        if len(txtVec) == 2 :
            soi.boulePosInit = Vecteur2(int(txtVec[0]), int(txtVec[1]))
        
        txtVec = soi.champVent.get_text().split(',')
        if len(txtVec) == 2 :
            soi.vent = Vecteur2(int(txtVec[0]), int(txtVec[1]))
        
        soi.nbc = int(soi.champNbc.get_text())
        soi.nbi = int(soi.champNbi.get_text())
        
        vitesse = float(soi.champVitesse.get_text())
        angle = math.radians(float(soi.champAngle.get_text()))
        soi.gravite.y = float(soi.champGravite.get_text())
        # if soi.gravite.y == 0. : soi.gravite.y = 0.00001
        
        soi.bouleVitInit = soi.calculeVecteurVitesse(angle, vitesse)
        
        soi.trajetI = 0
        
        soi.positionneSol()
        soi.trajectoires = []
        soi.positionneSol2()
        soi.initBoitesColl()
        soi.calculeImpacts(0, soi.boulePosInit, soi.bouleVitInit, 0)
        
        soi.positionneHmax()
        soi.calculeCourbe()
        
        soi.dessineFond()
        soi.dessineSol()
        soi.dessineSol2()
        soi.dessineBoite()
        soi.dessineBoite2()
        soi.dessineBoule()
        soi.dessineHmax()
        soi.dessineVent()
        soi.dessineImpact()
        soi.dessinePuissance()
        soi.dessineCourbe()
        
        soi.fenetre.queue_draw()
        
        
if __name__ == "__main__" :
    ProjetMaths = ProjetMaths()