Bonjour à tous.
J'ai une cible à l'écran que je dois déplacer d'un point a un autre sur une ligne droite en suivant sont déplacement. Je connais le point de départ et le point d'arrivée.
Comment puis-je faire ?
Merci d'avance
Bonjour à tous.
J'ai une cible à l'écran que je dois déplacer d'un point a un autre sur une ligne droite en suivant sont déplacement. Je connais le point de départ et le point d'arrivée.
Comment puis-je faire ?
Merci d'avance
Tu veux avancer du point I (initial) à F (final). Coordonnées (Ix,Iy) resp. (Fx,Fy) en un temps donné Ttot. Bien sûr, ce temps peut être en fait un nombre de pas (Si Ttot=1000 pas, alors tu auras T1=1, T2=2, T3=3...).
Au temps T, la position (X,Y) de la cible est:
X=Ix+(Fx-Ix)/Ttot*T=Ix+Vx*T (où Vx=(Fx-Ix)/Ttot, la vitesse en X)
Y=Iy+(Fy-Iy)/Ttot*T=Iy+Vy*T (idem, Vy=(Fy-Iy)*Ttot, la vitesse en Y)
Si ton temps T est en fait des pas de 1, par exemple, alors tu peux simplement incrémenter la position de Vx et Vy à chaque pas, mais tu as le risque de cumuler une erreur à chaque pas... Le mieux reste de calculer la position à chaque fois:
Il faut faire bien attention à calculer la vitesse avec autre chose que des integer, sinon tu as une forte déviation dûe à l'approximation!
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 TPoint I(Ix,Iy); TPoint F(Fx,Fy); TPoint pos; double Vx=(double)(Fx-Ix)/Ttot; double VY=(double)(Fy-Iy)/Ttot; for (int t=0;t<1000;++t) { pos.x=Vx*t+Ix; pos.y=Vy*t+Iy; }
Un exemple d'appli tout bête:
1) Tu mets ne nombre de pas dans la case "TempsTot"
2) Tu cliques sur Start
3) Tu fais un cliquer-glisser depuis le point de départ vers le point d'arrivée
Le header
Le code:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components TEdit *TempsTot; TShape *Shape1; TBitBtn *BitBtn1; TShape *From; TShape *To; void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y); void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y); void __fastcall BitBtn1Click(TObject *Sender); private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); bool ready; TPoint Initial; TPoint Final; Start(); };
TPoint
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { ready=false; } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { if (!ready) return; Initial=TPoint(X,Y); From->Left=X; From->Top=Y; } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { if (!ready) return; Final=TPoint(X,Y); To->Left=X; To->Top=Y; Start(); ready=false; } //--------------------------------------------------------------------------- TForm1::Start() { int Ttot=atoi(TempsTot->Text.c_str()); double Vx=(double)(Final.x-Initial.x)/Ttot; double Vy=(double)(Final.y-Initial.y)/Ttot; for (int t=0;t<Ttot;++t) { Shape1->Left=Initial.x+Vx*t; Shape1->Top=Initial.y+Vy*t; Shape1->Invalidate(); Sleep(50); Application->ProcessMessages(); } } void __fastcall TForm1::BitBtn1Click(TObject *Sender) { ready=true; } //---------------------------------------------------------------------------
J'ai réussi et je t'en remercie.
Par contre, j'ai quand même un petit souci, il semblerait que cela ne passe pas exactement sur la ligne droite car j'ai mis un point d'arrêt lorsque mon déplacement s'arrête (teste si le point courant est égal au point final du segment) et cela ne s'arrête pas.
En fait, il y a deux choses:
1) Dans mon code, j'ai fait une erreur dans la boucle: il faut aller de 0 à n et par de 0 à (n-1) pour atteindre le point final
au lieu de
Code : Sélectionner tout - Visualiser dans une fenêtre à part for (int t=0;t<=Ttot;++t)2) Avec les arrondis, tu as toujours le risque de te retrouver un tout petit peu à côté de la valeur si tu fais la comparaison en double, mais c'est très improbable si tu compares la position Top/Left et que tu n'utilises pas les incréments: la formule Px=Ix+Vx*t est très précise pour t=Ttot (Px=Ix+(Fx-Ix)/Ttot*Tot=Fx)
Code : Sélectionner tout - Visualiser dans une fenêtre à part for (int t=0;t<Ttot;++t)
Je pense que ton problème vient de mon erreur (point 1)
Voilà moi comment j'ai fait
Unit1.h
Unit1.cpp
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 //--------------------------------------------------------------------------- #ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <Menus.hpp> #include <ExtCtrls.hpp> //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // Composants gérés par l'EDI TPopupMenu *PopupMenu; TMenuItem *Quitter; TMenuItem *LancerLeMouvement; TTimer *Timer; void __fastcall QuitterClick(TObject *Sender); void __fastcall OnPaint(TObject *Sender); void __fastcall LancerLeMouvementClick(TObject *Sender); void __fastcall OnTimer(TObject *Sender); private: // Déclarations de l'utilisateur public: // Déclarations de l'utilisateur // Variables bool Mouvement; // booléen pour le mode mouvement POINT Pts[3]; // tableau des centres de cibles POINT Point; // Point courant int NumSegment; // Numéro du segment courant double Vitesse; // Vitesse du mouvement double Temps; // Temps pour réaliser le déplacement double VX, VY; // Vitesse sur le segment // Temps de début de cible __int64 TpsDbtCible; // Fréquence de l'horloge __int64 FreqHorloge; // Méthodes __fastcall TForm1(TComponent* Owner); // Méthode qui calcule le temps pour le segment double TempsSegment(int Num); // Méthode qui permet de calculer la longueur du segment double LongueurSegment(int Num); }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230 //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include <math.h> //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { // On initialise les dimensions de la zone client Form1->ClientWidth = Screen->Width; Form1->ClientHeight = Screen->Height; // On positionne la form Form1->Top = 0; Form1->Left = 0; // On initialise le tableau des points Pts[0].x = 70; Pts[0].y = 70; Pts[1].x = ClientWidth / 2; Pts[1].y = ClientHeight / 2; Pts[2].x = ClientWidth - 70; Pts[2].y = ClientHeight - 710; // On calcule la fréquence de l'horloge QueryPerformanceFrequency((LARGE_INTEGER*)&FreqHorloge); } //--------------------------------------------------------------------------- // Méthode qui permet de quitter l'application void __fastcall TForm1::QuitterClick(TObject *Sender) { Application->Terminate(); } //--------------------------------------------------------------------------- // Méthode qui dessine l'écran void __fastcall TForm1::OnPaint(TObject *Sender) { int x1, x2, y1, y2; // Si l'on est en mode mouvement if(Mouvement) { // On dessine l'arrière-plan Canvas->Pen->Color = clWhite; Canvas->Brush->Color = clWhite; Canvas->Rectangle(0, 0, ClientWidth, ClientHeight); // On affiche la cible courante x1 = Point.x - 30; y1 = Point.y - 30; x2 = x1 + 60; y2 = y1 + 60; Canvas->Pen->Color = clRed; Canvas->Brush->Color = clRed; Canvas->Ellipse(x1, y1, x2, y2); // Première cible x1 = Pts[0].x - 30; y1 = Pts[0].y - 30; x2 = x1 + 60; y2 = y1 + 60; Canvas->Pen->Color = clBlue; Canvas->Brush->Color = clBlue; Canvas->Ellipse(x1, y1, x2, y2); // Deuxième cible x1 = Pts[1].x - 30; y1 = Pts[1].y - 30; x2 = x1 + 60; y2 = y1 + 60; Canvas->Pen->Color = clGreen; Canvas->Brush->Color = clGreen; Canvas->Ellipse(x1, y1, x2, y2); // Troisième cible x1 = Pts[2].x - 30; y1 = Pts[2].y - 30; x2 = x1 + 60; y2 = y1 + 60; Canvas->Pen->Color = clYellow; Canvas->Brush->Color = clYellow; Canvas->Ellipse(x1, y1, x2, y2); } // Sinon else { // On efface l'écran Canvas->Pen->Color = clAppWorkSpace; Canvas->Brush->Color = clAppWorkSpace; Canvas->Rectangle(0, 0, ClientWidth, ClientHeight); } } //--------------------------------------------------------------------------- // Méthode qui lance le mouvement void __fastcall TForm1::LancerLeMouvementClick(TObject *Sender) { // On met le booléen de mouvement à true Mouvement = true; // On se positionne sur le premier segment NumSegment = 0; // On initialise la vitesse du mouvement Vitesse = 50; // On positionne le point courant sur le première cible Point.x = Pts[NumSegment].x; Point.y = Pts[NumSegment].y; // On calcule le temps pour effectué le déplacement complet Temps = TempsSegment(NumSegment); // On calcule la vitesse sur le segment en x et en y VX = double(Pts[NumSegment + 1].x - Pts[NumSegment].x) / Temps; VY = double(Pts[NumSegment + 1].y - Pts[NumSegment].y) / Temps; // On configure le timer Timer->Interval = 10; // On lance le timer Timer->Enabled = true; // On calcule le temps au début de la cible QueryPerformanceCounter((LARGE_INTEGER*)&TpsDbtCible); // On dessine l'écran OnPaint(Form1); } //--------------------------------------------------------------------------- // Méthode qui calcule le temps pour le segment double TForm1::TempsSegment(int Num) { return LongueurSegment(Num) / Vitesse; } //--------------------------------------------------------------------------- // Méthode qui permet de calculer la longueur du segment double TForm1::LongueurSegment(int Num) { double opx, opy; opx = (Pts[Num + 1].x - Pts[Num].x) * (Pts[Num + 1].x - Pts[Num].x); opx = (Pts[Num + 1].y - Pts[Num].y) * (Pts[Num + 1].y - Pts[Num].y); return sqrt(opx + opy); } //--------------------------------------------------------------------------- // Méthode déclancher à la fin du timer void __fastcall TForm1::OnTimer(TObject *Sender) { // Temps courant __int64 TpsCourant; // Temps pour le calcul double T; // Si l'on a parcourut tous les segments if(NumSegment == 2) { // On arrête le timer Timer->Enabled = false; // On met le booléen du mode mouvement à faux Mouvement = false; ShowMessage("Fin"); } // Sinon else { if((Point.x == Pts[NumSegment + 1].x) && (Point.y == Pts[NumSegment + 1].y)) { // On passe au segment suivant NumSegment++; //ShowMessage("On passe au segment suivant"); // On positionne le point sur le début du segment Point.x = Pts[NumSegment].x; Point.y = Pts[NumSegment].y; // On calcule le temps pour effectué le déplacement complèt Temps = TempsSegment(NumSegment); // On calcule la vitesse sur le segment en x et en y VX = double(Pts[NumSegment + 1].x - Pts[NumSegment].x) / Temps; VY = double(Pts[NumSegment + 1].y - Pts[NumSegment].y) / Temps; // On calcule le temps au début de la cible QueryPerformanceCounter((LARGE_INTEGER*)&TpsDbtCible); } else { // On mesure le temps courant QueryPerformanceCounter((LARGE_INTEGER*)&TpsCourant); // On calcule le temps du point T = (TpsCourant - TpsDbtCible)/(double)FreqHorloge; Point.x = (VX * T) + Pts[NumSegment].x; Point.y = (VY * T) + Pts[NumSegment].y; } // On appel la méthode de dessin OnPaint(Form1); } } //---------------------------------------------------------------------------
Salut !
Il faut juste arrondir avec un peu plus de précision :
Code : Sélectionner tout - Visualiser dans une fenêtre à part #include <math.h>Je ne sais pas s'il y a plus simple !
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 int arrondir(double *D) { return (*D + (*D - floor(*D))); }
A plus !
Remarque :Un petit inconvénient de ce calcul, mais peut être que dans ton appli ça n'a pas d'importance, est que ta cible peut se déplacer par bonds (ou au contraire stagner à un endroit, ce qui fait du calcul pour rien). Elle peut sauter des positions sur la ligne droite. Dans son déplacement, il peut y avoir des emplacements sur la droite où elle ne se trouvera jamais. Si c'est grave, il faut modifier l'algo de calcul des points de la droitePoint.x = (VX * T) + Pts[NumSegment].x;
Point.y = (VY * T) + Pts[NumSegment].y;
Le problème vient du fait que tu maîtrises mal le temps écoulé dans le soft. Celui-ci est en double et le timer n'est pas forcement d'une très grande précision. Résultat, tu tombes rarement juste dans les points calculés et finaux.
Le mieux est de calculer si tu as atteint ou dépassé la cible d'arrivée et de t'intérrompre à ce moment-là. Pour celà, tu peux soit calculer le temps total écoulé et voir si tu as dépassés ton Temps prévu, mais le plus élégant, c'est de jouer avec les vecteurs pour voir quand ton point P est passé derrière le point F (final). Je te propose de remplacer ton test
par
Code : Sélectionner tout - Visualiser dans une fenêtre à part if((Point.x == Pts[NumSegment + 1].x) && (Point.y == Pts[NumSegment + 1].y))
Le principe est qu'en géométrie vectorielle, le produit scalaire entre deux vecteurs donne la projection de l'un sur l'autre. Lorsqu'ils sont perpendiculaires le produit scalaire est nul, lorsqu'ils sont dans le même sens, il est positif, et lorsqu'ils sont opposé il est négatif.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 // On s'arrête dès qu'on a dépassé le point d'arrivée,càd quand // le vecteur P--F est dans le sens inverse que I--F. On calcule // ça comme le produit scalaire entre (IF) et (PF) qui devient // négatif lorsque les vecteurs sont de sens opposés // ProduitScalaire(<Ax,Ay>,<Bx,By>)=Ax*Bx+Ay+By // // Nous calculons P--F et I--F: // PF=F-P=<Fx-Px,Fy-Py> // IF=F-I=<Ix-Fx,Iy-Fy> // // Dans ton implémentation: // P=Point, // F=Pts[NumSegment+1] // I=Pts[NumSegment] // // Ce qui donne: // ProduitScalaire=(Fx-Px)*(Fx-Ix)+(Fy-Py)*(Fy-Iy) // =(Pts[NumSegment+1].x-Point.x)*(Pts[NumSegment+1].x-Pts[NumSegment].x) // +(Pts[NumSegment+1].y-Point.y)*(Pts[NumSegment+1].y-Pts[NumSegment].y) if( (Pts[NumSegment+1].x-Point.x)*(Pts[NumSegment+1].x-Pts[NumSegment].x) +(Pts[NumSegment+1].y-Point.y)*(Pts[NumSegment+1].y-Pts[NumSegment].y)<=0)
Si tu calcules les vecteurs I-->F et P-->F (P=point courant, I=point initial, F=point final), tu peux ainsi savoir si P a dépassé F car (I-->F)*(P-->F) devient négatif.
Demande-moi si tu veux plus d'explications... Mais le code a l'air de fonctionner!
Il me semble qu'il y a un truc bizarre avec ta vitesse qui n'est pas identique entre les 2 segments, mais je ne trouve pas de faute...
Le produit scalaire était mal écrit dans mon commentaire. Il faut lire:
Code : Sélectionner tout - Visualiser dans une fenêtre à part // ProduitScalaire(<Ax,Ay>,<Bx,By>)=Ax*Bx+Ay*By
Salut !
La progression de la cible se calcule aussi simplement par progression de n points
par intervalle de temps sur la droite reliant les deux points (départ et arrivée).
On a juste, au départ, à calculer une distance, un sinus et un cosinus.
Ici j'utilise :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3TPoint *arrivee; TPoint *depart; double distance, sinus, cosinus, progression, pas;
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 double dx = arrivee->x - depart->x; double dy = arrivee->y - depart->y; distance = hypot(dx, dy); sinus = 0; cosinus = 0; if(distance != 0) { sinus = dy / distance; cosinus = dx / distance; } x = depart->x; y = depart->y; progression = 0;
La position de la cible en mouvement sur la droite se calcule à l'aide de :
A plus !
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 progression = progression + pas; si progression < distance x = (cosinus * progression) + depart->x; y = (sinus * progression) + depart->y; sinon x = arrivee->x; y = arrivee->y;
Merci à toi henderson, je vais regarder ça
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager