Python : Vecteur2D

Des tas de situations demandent de définir un couple de valeurs, que ce soit pour retenir la position d'un objet, stocker des dimensions ou un point sur un graphique. De même, on est souvent amené à effectuer des opérations sur ce type de données. La classe Vecteur2D simplifie la syntaxe lors des calculs avec un vecteur.

Les opérations de base

Vecteur2D s'occupe de mémoriser deux coordonées, représentant un point dans l'espace ou un vecteur (partant de l'origine)

Constructeur

Le constructeur de la classe Vecteur2D définit deux propriétés, x et y, représentant respectivement les coordonnées horizontale et verticale. Les valeurs sont converties dans le type float.

class Vecteur2D: def __init__ (self, x, y): self.x = float(x) self.y = float(y)

Norme

La norme d'un vecteur est sa longueur, c'est-à-dire la distance séparant l'origine (0, 0) et le point représenté par le vecteur. On calcule la norme à l'aide du triangle de Pythagore. Son hypothénus est la norme.

# Norme du vecteur (longueur) def norme (self): return math.sqrt( self.x*self.x + self.y*self.y )

Vecteur unité

Cette méthode retourne le vecteur unité, c'est-à-dire l'orientation du vecteur normalisé sur une échelle de 1.

# Retourne le vecteur unité. Celui-ci est normalisé à 1 et possède la même # direction que le vecteur d'origine. def unite (self): return Vecteur2D( self.x, self.y) n = self.norme() if (n==0): return Vecteur2D( 0, 0 ) else: return Vecteur2D( float(self.x)/float(n), float(self.y)/float(n) )

Inverser

Inverse le vecteur.

# Inverse les coordonnées du vecteur. def inverser (self): self.x *= -1.0 self.y *= -1.0

Cloner

On a souvent besoin d'obtenir la copie d'un vecteur. En effet, l'opérateur '=' crée une référence vers l'objet Vecteur2D. Ainsi position = Vecteur2D(100, 50) crée un nouveau vecteur puis stocke une référence vers cet objet. 'position' est une référence vers l'instance de Vecteur2D pas l'objet lui-même !
Ainsi, pour copier la variable position, l'instruction copie = position n'est pas correcte. Dans ce cas, 'copie' fera aussi référence à la même instance que 'position'. Cela veut dire que toutes les modifications sur 'position' seront aussi effectives sur 'copie'.

Pour copier correctement une instance de Vecteur2D, il suffit de lire les valeurs de ses propriétés, x et y dans notre cas, et de renvoyer une nouvelle instance de Vecteur2D initialisée à ces valeurs.

# Cloner le vecteur. Retourne un vecteur identique def cloner (self): return Vecteur2D( self.x, self.y)

Additioner et soustraire

Additioner

L'addition d'un vecteur à un autre correspondant à la somme de leurs coordonnées x et y.

# Additionne un vecteur def addition (self, v): self.x += v.x self.y += v.y

Additioner et créer un nouveau vecteur

Cette méthode est identique à celle présentée ci-dessus à la différence qu'elle renvoit un nouveau vecteur.

# Retourne un vecteur résultant de l'addition de deux vecteurs def additionN (self, v): r = Vecteur2D(v.x+self.x, v.y+self.y) return r

Soustraction

La

# Soustraction du vecteur avec le vecteur V. def soustraction (self, v): self.x -= v.x self.y -= v.y

Distance entre deux vecteurs

Pour déterminer la distance entre deux vecteurs, on soustrait la position d'un vecteur à la position de l'autre. On obtient ainsi, sous forme de vecteur, la distance entre les deux vecteurs initiaux. Il ne reste plus qu'a calculer cette distance en utilisant la norme d'un vecteur.

# Retourne la distance entre deux vecteurs (norme de la différence) def distance (self, v) : uu = self.cloner() vv = v.cloner() uu.soustraction(vv) return ( uu.norme() )

TEST

# -*- coding: utf-8 -*- """ Représentation d'une ligne sur un plan. La classe Segment décrit une ligne dans un espace à 2 dimensions. Contact : clic4@clic4.org """ __author__ = u"Sébastien L. W. Baelde " __date__ = "2008" __version__ = "1.0" import pygame from Vecteur2D import Vecteur2D import math class Segment: """ Classe : Segment Version : 0.99 Définit un segment (une ligne définie par deux points). """ def __init__(self, a, b) : """ @param a: Point de départ du segment. @type a: L{Vecteur2D} @param b: Point d'arrivée du segment. @type b: L{Vecteur2D} @return: Un segment. @rtype: L{Segment} """ self.a = a.cloner() self.b = b.cloner() def testerPerp (self, seg) : """ Tester si le segment est perpipendiculaire à un segment donné. Retourne True si le segment est perpendiculaire au segment SEG, sinon retourne False. @param seg: Un segment à tester. @type seg: L{Segment} """ va = self.b.soustractionN( self.a ) vb = seg.b.soustractionN( seg.a ) pScal = va.prodScalaire(vb) if pScal < 0.00001 and pScal > -0.00001: return True return False def placerContre(self, pt, ptColl, bille, surface) : """ Placer un objet, contenu dans un cercle, contre le segment. Retourne la nouvelle position de la bille (Vecteur2D). @param pt: Position du centre de l'objet cercle. @type pt: L{Vecteur2D} @param ptColl: Point de collision de l'objet sur le segment. @type ptColl: L{Vecteur2D} @param bille: Un objet Cercle. @type bille: L{Cercle} @param surface : Surface pour l'affichage du debugage. Inutilisé. A SUPPRIMER. @return: Position corrigée de l'objet 'bille'. @rtype: L{Vecteur2D} """ # Calculer la position de la bille collée contre l'obstacle segN = self.ptSeg( pt ) nouvPos = segN.a.cloner() nouvPos.soustraction( segN.b ) nouvPos = nouvPos.unite() dirInverse = bille.dir.cloner() dirInverse.inverser() nouvPos.prodScalaire(dirInverse) nouvPos.multScalaire( (bille.r+1.0) ) p = ptColl.cloner() p.addition( nouvPos ) return p def interSegSeg(self, seg) : """ Tester l'intersection avec un segment. Attention ! En cas de paralèlles, retourne uniquement False si elles ne coincident pas. Pas de point d'intersection ou de True. @param seg: Un segment à tester. @type seg: L{Segment} @return: Retourne un point d'intersection si les deux segments se croisent, sinon False. @rtype: L{Vecteur2D} ou False """ x1 = seg.a.x y1 = seg.a.y x2 = seg.b.x y2 = seg.b.y x3 = self.a.x y3 = self.a.y x4 = self.b.x y4 = self.b.y nume = (x4-x3) * (y1-y3) - (y4-y3) * (x1-x3) deno = (y4-y3) * (x2-x1) - (x4-x3) * (y2-y1) if (deno==0): deno=0.00001 #r0 = float(nume)/float(deno) r0 = nume/deno nume = (x2-x1) * (y1-y3) - (y2-y1) * (x1-x3) #r1 = float(nume)/float(deno) r1 = nume/deno if ((r0>=0.0 and r0<=1.0) and (r1>=0.0 and r1<=1.0)): if deno != 0.00001: return Vecteur2D(x1 + r0 * (x2 - x1), y1 + r0 * (y2 - y1) ) else: # Parall�les ! #v = self.b.soustractionN(self.a) v = self.b - self.a #w2 = seg.b.soustractionN(self.a) w2 = seg.b - self.a #w = seg.a.soustractionN(self.a) w = seg.a - self.a if (v.x != 0) : #t0 = float(w.x) / float(v.x) #t1 = float(w2.x) / float(v.x) t0 = w.x / v.x t1 = w2.x / v.x else : #t0 = float(w.y) / float(v.y) #t1 = float(w2.y) / float(v.y) t0 = (w.y+0.0001) / (v.y+0.0001) t1 = (w2.y+0.0001) / (v.y+0.0001) if (t0 > t1) : # must have t0 smaller than t1 t=t0 t0=t1 t1=t # Les deux parallèles ne se touchent pas if (t0 > 1.0 or t1 < 0.0) : return False else: return False def interCercle(self, cercle, r) : """ Retourne le ou les point(s) d'intersection entre un cercle et le segment. @param cercle: Centre du cercle. @type cercle: L{Vecteur2D} @param r: Rayon du cercle. @type r: L{Vecteur2D} @return: Retourne False si pas d'intersection. Dans le cas contraire, retourne un dictionnaire de deux points 'ptA' et 'ptB' (None si une seule solution). @rtype: E{lb}'ptA': L{Vecteur2D}, 'ptB': L{Vecteur2D}E{rb}, False ou None """ cCercle = cercle.cloner() x3 = cCercle.x y3 = cCercle.y x1 = self.a.x y1 = self.a.y x2 = self.b.x y2 = self.b.y a = float( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) ) # A vérifier if a == 0.0: return False b = float( 2.0 * ( (x2 - x1) * (x1 - x3) + (y2 - y1) * (y1 - y3) ) ) c = float( x3*x3 + y3*y3 + x1*x1 + y1*y1 - 2.0 * (x3 * x1 + y3 * y1 ) - r*r ) nume = float( (x3-x1) * (x2-x1) + (y3-y1) * (y2-y1) ) deno = float( (x2-x1) * (x2-x1) + (y2-y1) * (y2-y1) ) d = b*b - 4.0*a*c if (d<0.0): return False s1 = (-b + math.sqrt(d))/(2.0*a) s2 = (-b - math.sqrt(d))/(2.0*a) if (d > 0.0) : pA = Vecteur2D(x1 + s1 * (x2 - x1), y1 + s1 * (y2 - y1)) pB = Vecteur2D(x1 + s2 * (x2 - x1), y1 + s2 * (y2 - y1)) testA = self.testerPtSurSeg (pA) testB = self.testerPtSurSeg (pB) if testA and testB: #self.a.afficher() #self.b.afficher() return {'ptA':pA, 'ptB':pB} elif testA and not testB: #self.a.afficher() #self.b.afficher() return {'ptA':pA, 'ptB':None} elif not testA and testB: #self.a.afficher() #self.b.afficher() return {'ptA':pB, 'ptB':None} # Si 1 réponse, mettre tjrs ds 'ptA' else: return False elif (d == 0.0) : # Cercle tangent au segment s = -b/(2.0*a) pA = Vecteur2D(x1 + s*(x2 - x1), y1 + s1*(y2 - y1) ) if self.testerPtSurSeg (pA): return {'ptA': pA, 'ptB': None} else: return False def testerPtSurSeg (self, pt) : """ Tester si un point se trouve sur un segment. @param pt: Point à tester. @type pt: L{Vecteur2D} @return: Retourne True ou False. @rtype: Boolean """ if pt == None: return False test = self.ptSeg(pt) return self.testerPerp(test) def segNormal(self) : """ Retourne un segment perpendiculaire au segment. Cette méthode crée une nouvelle instance Segment. @return: Un segment perpendiculaire. @rtype: L{Segment} """ e = self.b.soustractionN( self.a ) n = Vecteur2D(-e.y, e.x) return Segment(self.b, self.b.additionN( n ) ) def reflection(self, pt, collObs, m, surface) : """ Retourne la réflection d'un vecteur (rebond sur un segment). @param pt: Point ? A compléter... @type pt: L{Vecteur2D} @param collObs: Un dictionnaire avec deux arguments: pt=le pt de collision, seg= le segment touché. @param m: Objet missile. @type m: Missile @param surface: Obsolète. A supprimer. @return: Un dictionnaire... """ # Calcul du rebond : Rr = Ri - 2 N (Ri . N) segNormal = collObs['seg'].segNormal() # Construire une normale sur le segment touché # Faire une translation pour amener la normale au point d'intersection alpha = collObs['pt'].cloner() alpha.soustraction( segNormal.a ) normA = segNormal.a.additionN( alpha ) normB = segNormal.b.additionN( alpha ) sN = Segment( normA, normB ) # Représenter le segment normal par un vecteur normal = sN.b.soustractionN(sN.a) normal = normal.unite() # Construire le vecteur incident. Multiplié par le vecteur unitaire de direction pour tjrs avoir un vIncident non nul vraiIncident = m.vitesse.cloner() vraiIncident.multiplier(m.dir) vraiIncident.inverser() vraiIncident.addition(collObs['pt']) segIncident = Segment(vraiIncident, collObs['pt']) # Placer le vecteur incident correctement Ri = vraiIncident.cloner() Ri.soustraction( collObs['pt'] ) const = Ri.prodScalaire( normal ) droite = normal.multScalaireN( 2*const ) Rr = Ri.soustractionN( droite ) Rr.inverser() collObs['seg'].afficher(surface, 0x00ff00) ptCorrige = collObs['seg'].placerContre(pt, collObs['pt'], m, surface) return [ptCorrige, Rr] def segPerp( self, pt ) : """ Retourne le segment le plus court (perpendiculaire) d'un point à une ligne (définie par un segment). @param pt: Point à partir duquel on veut construire la perpendiculaire. @type pt: L{Vecteur2D} @return: Le plus court segment. @rtype: L{Segment} """ v = self.b.soustractionN (self.a) w = pt.soustractionN( self.a ) c1 = w.prodScalaire( v ) c2 = v.prodScalaire( v ) b = float(c1) / float(c2) Pb = v.multScalaireN( b ) Pb.addition( self.a ) return Segment( pt.cloner(), Pb ) def ptSeg( self, pC ) : """ Retourne le segment entre un point et le chemin le plus court à un autre segment. @param pC: Un point sur le plan. @type pC: L{Vecteur2D} @return: Le segment le plus court. @rtype: L{Segment} """ p = Vecteur2D(pC.x, pC.y) p = pC.cloner() s0 = self.a.cloner() s1 = self.b.cloner() v = s1.cloner() w = p.cloner() v.soustraction ( s0 ) w.soustraction ( s0 ) c1 = w.prodScalaire ( v ) if (c1 <= 0) : return Segment(p, s0) c2 = v.prodScalaire ( v ) if (c2 <= c1) : return Segment(p, s1) b = float(c1) / float(c2) v.multScalaire(b) s0.addition(v) return Segment(p, s0) def afficher(self, surface, couleur) : """ Dessine un trait pour le segment. @param surface: Une surface pygame. @type surface: Surface @param couleur: Couleur du segment. @type couleur: ? """ pygame.draw.line( surface, couleur, (self.a.x, self.a.y), (self.b.x, self.b.y) ) def glissement (self, mvtSupp ) : """ Glissement le long d'un obstacle. Convertit un segment mvt restant en un vecteur vitesse le long de l'obstacle. @return: Retourne le nouveau vecteur de vitesse. Le segment correspond à la partie de l'obstacle sur laquelle on glisse. @rtype: L{Vecteur2D} """ segPerp = self.ptSeg(mvtSupp.b) vitesse = segPerp.b.soustractionN(mvtSupp.a) return vitesse

© Clic4.org 2010