You can also try 'Separating axis collision detection', i dont know if it will be faster.
If you just need a collision test (Collision\Detectee), you can delete ChercheDeplacementMini(), and delete these lines in CollisionReponse()
Code: Select all
; Comtois 4.40 b3 22/09/09
If InitSprite()=0 Or InitMouse()=0 Or InitKeyboard()=0
MessageRequester("Erreur","Initialisation impossible",0)
End
EndIf
If OpenScreen(800, 600, 32,"Collision par la méthode de séparation des axes")=0
MessageRequester("Erreur","Ouverture d'un écran 800x600x32 impossible",0)
End
EndIf
#Epsilon = 0.00001
#NbSommet = 32 ;Nombre de Sommet maxi pour un polygone
#NbPlan = (#NbSommet*2)-1 ;Un plan par Sommet pour deux polygones
#DeltaAngle = 0.09/#PI
#NbPolygones = 50
Enumeration
#A
#B
EndEnumeration
Macro PRODUIT_SCALAIRE(V1, V2)
(V1\x * V2\x + V1\y * V2\y)
EndMacro
Structure s_Vecteur2D
x.f
y.f
EndStructure
Structure s_Polygone
Position.s_Vecteur2D
Vitesse.f
Angle.f
NbSommet.l
Couleur.l
Sommet.s_Vecteur2D[#NbSommet]
EndStructure
Structure s_Collision
Detectee.l
Normale.s_Vecteur2D
Distance.f
EndStructure
Structure s_Intervalle
Mini.f
Maxi.f
EndStructure
;- Declare
Declare InitialiseJeu(*Voiture.s_Polygone, List Mur.s_Polygone())
Declare CollisionReponse(*Voiture.s_Polygone, List Mur.s_Polygone())
Declare GestionClavier(*Voiture.s_Polygone, List Mur.s_Polygone())
Declare ConstructionPolygone(*Polygone.s_Polygone, Rayon.f)
Declare ConstructionMur(*Polygone.s_Polygone)
Declare AffichePolygone(*Voiture.s_Polygone, List Mur.s_Polygone())
Declare CollisionPolygone(*A.s_Polygone, *B.s_Polygone, *Distance.s_Vecteur2D, *Collision.s_Collision)
Declare CalculeProjection(*Polygone.s_Polygone, *Axe.s_Vecteur2D, *Projection.s_Intervalle)
Declare CalculeIntersection(*A.s_Polygone, *B.s_Polygone, *Axe.s_Vecteur2D, *Distance.s_Vecteur2D, *Chevauchement.Float)
Declare ChercheDeplacementMini(Array Axe.s_Vecteur2D(1), Array Chevauchement.f(1), NbAxes.l, *Collision.s_Collision)
Declare.f Normalise(*V.s_Vecteur2D)
Define.s_Polygone Voiture
NewList Mur.s_Polygone()
InitialiseJeu(@Voiture, Mur())
;- Main
Repeat
ClearScreen(#Black)
GestionClavier(@Voiture, Mur())
CollisionReponse(@Voiture, Mur())
AffichePolygone(@Voiture, Mur())
FlipBuffers()
Until KeyboardPushed(#PB_Key_Escape)
Procedure InitialiseJeu(*Voiture.s_Polygone, List Mur.s_Polygone())
;Création du jeu
Define.l i, Rayon
;Mouse
*Voiture\Position\x = x
*Voiture\Position\y = y
*Voiture\NbSommet = 12
*Voiture\Vitesse = 3
*Voiture\Couleur = #White
Rayon = 6
ConstructionPolygone(*Voiture, Rayon)
ClearList(Mur())
For i = 0 To #NbPolygones-1
AddElement(Mur())
Rayon = Random(20)+16
Mur()\Position\x = Rayon + Random(800-Rayon*2)
Mur()\Position\y = Rayon + Random(600-Rayon*2)
Mur()\NbSommet = Random(10)+3
Mur()\Couleur = #Yellow
ConstructionPolygone(Mur(), Rayon)
Next i
EndProcedure
Procedure CollisionReponse(*Voiture.s_Polygone, List Mur.s_Polygone())
;A reprendre
; Il faudrait d'abord chercher le point d'impact le plus proche en testant tous les polygones
; Puis calculer la réponse d'une façon récursive, en prenant un vecteur vitesse en compte.
Define.s_Vecteur2D Distance
Define.s_Collision Collision
*Voiture\Couleur = #White
ForEach Mur()
Distance\x = *Voiture\Position\x - Mur()\Position\x
Distance\y = *Voiture\Position\y - Mur()\Position\y
Collision\Detectee = CollisionPolygone(*Voiture, Mur(), @Distance, @Collision)
;Séparation des polygones
If Collision\Detectee
*Voiture\Couleur = #Green
;Mur()\Couleur = #Red
*Voiture\Position\x - (Collision\Normale\x * (Collision\Distance * 0.5)) ; ou un peu moins que 1.5
*Voiture\Position\y - (Collision\Normale\y * (Collision\Distance * 0.5)) ; ou un peu moins que 1.5
MouseLocate(*Voiture\Position\x, *Voiture\Position\y)
Mur()\Position\x + (Collision\Normale\x * (Collision\Distance * 0.5)) ; ou un peu moins que 1.5
Mur()\Position\y + (Collision\Normale\y * (Collision\Distance * 0.5)) ; ou un peu moins que 1.5
EndIf
Next
EndProcedure
Procedure GestionClavier(*Voiture.s_Polygone, List Mur.s_Polygone())
If ExamineKeyboard()
If KeyboardReleased(#PB_Key_Space)
InitialiseJeu(*Voiture, Mur())
EndIf
If KeyboardPushed(#PB_Key_Up)
*Voiture\Position\y - *Voiture\Vitesse
ElseIf KeyboardPushed(#PB_Key_Down)
*Voiture\Position\y + *Voiture\Vitesse
EndIf
If KeyboardPushed(#PB_Key_Left)
*Voiture\Position\x - *Voiture\Vitesse
ElseIf KeyboardPushed(#PB_Key_Right)
*Voiture\Position\x + *Voiture\Vitesse
EndIf
EndIf
; If ExamineMouse()
; *Voiture\Position\x = MouseX()
; *Voiture\Position\y = MouseY()
; EndIf
EndProcedure
Procedure ConstructionPolygone(*Polygone.s_Polygone, Rayon.f)
;Permet de calculer un polygone convexe
Define.l i
Define.f Angle, Rayon
For i = 0 To *Polygone\NbSommet-1
*Polygone\Sommet[i]\x = Cos(Angle) * Rayon
*Polygone\Sommet[i]\y = Sin(Angle) * Rayon
Angle + 2.0 * #PI / *Polygone\NbSommet
Next i
EndProcedure
Procedure ConstructionMur(*Polygone.s_Polygone)
Define.l i
Define.f Angle, Rayon
Rayon = 22.63
Angle = #PI/4
For i = 0 To *Polygone\NbSommet-1
*Polygone\Sommet[i]\x = Cos(Angle) * Rayon
*Polygone\Sommet[i]\y = Sin(Angle) * Rayon
Angle + 2.0 * #PI / *Polygone\NbSommet
Next i
EndProcedure
Procedure AffichePolygone(*Voiture.s_Polygone, List Mur.s_Polygone())
StartDrawing(ScreenOutput())
;Affiche les murs
With Mur()
ForEach Mur()
For i = 0 To \NbSommet-2
LineXY(\Position\x + \Sommet[i]\x, \Position\y + \Sommet[i]\y, \Position\x + \Sommet[i+1]\x, \Position\y + \Sommet[i+1]\y, \Couleur)
Next i
LineXY(\Position\x + \Sommet[\NbSommet-1]\x, \Position\y + \Sommet[\NbSommet-1]\y, \Position\x + \Sommet[0]\x, \Position\y + \Sommet[0]\y, \Couleur)
Next
EndWith
;Affiche la voiture
With *Voiture
For i = 0 To \NbSommet-2
LineXY(\Position\x + \Sommet[i]\x, \Position\y + \Sommet[i]\y, \Position\x + \Sommet[i+1]\x, \Position\y + \Sommet[i+1]\y, \Couleur)
Next i
LineXY(\Position\x + \Sommet[\NbSommet-1]\x, \Position\y + \Sommet[\NbSommet-1]\y, \Position\x + \Sommet[0]\x, \Position\y + \Sommet[0]\y, \Couleur)
EndWith
StopDrawing()
EndProcedure
Procedure CollisionPolygone(*A.s_Polygone, *B.s_Polygone, *Distance.s_Vecteur2D, *Collision.s_Collision)
Define.l j, i
Define.s_Vecteur2D Segment
If *A=0 Or *B=0 : ProcedureReturn #False : EndIf
; Tous les axes de séparation
Dim Axe.s_Vecteur2D(#NbPlan)
Dim Chevauchement.f(#NbPlan)
Define.l NoAxe
;Utilisation de la méthode générale,
;pour un rectangle on pourrait se contenter de tester 2 segments(largeur et longueur)
;Une autre méthode projette le centre et le rayon du rectangle(à tester).
; test séparation des axes du polygone A
j = *A\NbSommet-1
For i = O To *A\NbSommet-1
;Calcule chaque segment du polygone
Segment\x = *A\Sommet[i]\x - *A\Sommet[j]\x
Segment\y = *A\Sommet[i]\y - *A\Sommet[j]\y
;Calcul la normale pour chaque segment du polygone
Axe(NoAxe)\x = -Segment\y
Axe(NoAxe)\y = Segment\x
If CalculeIntersection(*A, *B, Axe(NoAxe), *Distance, @Chevauchement(NoAxe)) = #False
ProcedureReturn #False ; dès qu'on trouve un axe de séparation on peut sortir
EndIf
NoAxe + 1
j = i
Next i
; test séparation des axes du polygone B
j = *B\NbSommet-1
For i = O To *B\NbSommet-1
;Calcule chaque segment du polygone
Segment\x = *B\Sommet[i]\x - *B\Sommet[j]\x ; Le polygone pourrait être stocké avec cette valeur
Segment\y = *B\Sommet[i]\y - *B\Sommet[j]\y ; ça éviterait de la calculer à chaque fois
;Calcul la normale pour chaque segment du polygone
Axe(NoAxe)\x = -Segment\y
Axe(NoAxe)\y = Segment\x
If CalculeIntersection(*A, *B, Axe(NoAxe), *Distance, @Chevauchement(NoAxe)) = #False
ProcedureReturn #False ; dès qu'on trouve un axe de séparation on peut sortir
EndIf
NoAxe + 1
j = i
Next i
;Il faudra chercher le point d'impact !
If ChercheDeplacementMini(Axe(), Chevauchement(), NoAxe, *Collision) = #False
ProcedureReturn #False
EndIf
; Inverse la normale si nécessaire pour être sûr que les polygones seront bien séparés.
If PRODUIT_SCALAIRE(*Collision\Normale, *Distance) < 0.0
*Collision\Normale\x = -*Collision\Normale\x
*Collision\Normale\y = -*Collision\Normale\y
EndIf
ProcedureReturn #True
EndProcedure
; calcule la projection du polygone sur l'axe en cours de test
Procedure CalculeProjection(*Polygone.s_Polygone, *Axe.s_Vecteur2D, *Projection.s_Intervalle)
Define.l i
Define.f Projection
;Calcul la projection du Sommet[0] sur la normale du plan en cours de test
*Projection\mini = *Polygone\Sommet[0]\x * *Axe\x + *Polygone\Sommet[0]\y * *Axe\y
*Projection\maxi = *Projection\mini
;Recherche les projections mini et maxi en testant tous les sommets du polygone
For i = 1 To *Polygone\NbSommet-1
Projection = *Polygone\Sommet[i]\x * *Axe\x + *Polygone\Sommet[i]\y * *Axe\y
If (Projection < *Projection\mini)
*Projection\mini = Projection
ElseIf (Projection > *Projection\maxi)
*Projection\maxi = Projection
EndIf
Next i
EndProcedure
Procedure CalculeIntersection(*A.s_Polygone, *B.s_Polygone, *Axe.s_Vecteur2D, *Distance.s_Vecteur2D, *Chevauchement.Float)
Define.f h, dist0, dist1
Define.s_Intervalle A, B
;Calcul la projection des sommets du polygone A sur la normale du plan en cours de test
CalculeProjection(*A, *Axe, @A)
;Calcul la projection des sommets du polygone B sur la normale du plan en cours de test
CalculeProjection(*B, *Axe, @B)
;Calcul la projection de l'offset entre les polygones
h = *Distance\x * *Axe\x + *Distance\y * *Axe\y
;Ajoute la projection de l'offset à la projection du polygone A
A\mini + h
A\maxi + h
;Calcul le chevauchement entre les projections de A et B
dist0 = A\mini - B\maxi
dist1 = B\mini - A\maxi
;Test le chevauchement
If dist0 > 0.0 Or dist1 > 0.0
ProcedureReturn #False
Else
If dist0 > dist1
*Chevauchement\f = dist0
Else
*Chevauchement\f = dist1
EndIf
ProcedureReturn #True
EndIf
EndProcedure
Procedure ChercheDeplacementMini(Array Axe.s_Vecteur2D(1), Array Chevauchement.f(1), NbAxes.l, *Collision.s_Collision)
Define.l mini, i
Define.f n
;Initialise les données collision
mini = -1
*Collision\distance = 0
*Collision\Normale\x = 0
*Collision\Normale\y = 0
;On cherche parmi tous les axes de séparation le chevauchement le plus petit
For i = 0 To NbAxes-1
n = Normalise(@Axe(i)) ; Normalise l'axe et récupère sa longueur
Chevauchement(i) / n
;On retient le plus petit chevauchement pour se dégager de l'autre polygone
;les valeurs de chevauchement sont négatives d'où le test > ci dessous
;Par la suite il faudra aussi tenir compte du point d'impact !!
If (Chevauchement(i) > *Collision\distance) Or (mini = -1)
mini = i
*Collision\distance = Chevauchement(i)
*Collision\Normale\x = Axe(i)\x
*Collision\Normale\y = Axe(i)\y
EndIf
Next i
ProcedureReturn (mini <> -1)
EndProcedure
Procedure.f Normalise(*V.s_Vecteur2D)
Define.f Longueur
Longueur = Sqr(*V\x * *V\x + *V\y * *V\y)
If Longueur <> 0.0
*V\x / Longueur
*V\y / Longueur
EndIf
ProcedureReturn Longueur
EndProcedure