Ten artykuł traktuje o problemie zachowania się ciał podczas kolizji z uwzględnieniem ich masy i prędkości. Inaczej mówiąc, które ciało będzie się szybciej poruszać po zderzeniu lub wolniej. Ponadto kod programu odpowiednio wyznacza kierunek odbicia ciał po takim zderzeniu wraz z rotacją obrazu „duszka”. Symulacja jest napisana w DELPHI 5 + OMEGA.
WADA ROZWIĄZANIA: symulacja traktuje ciała jako punkty materialne.
Można zadać pytanie: Jak zachowa się ciało o mniejszej masie lub prędkości podczas zderzenia z ciałem o masie większej lub większej prędkości?…Intuicyjnie nasuwa się odpowiedź, że skutki odczuwane dla tego ciała będą dużo „tragiczniejsze” (czytaj: bardziej odczuwane).
Kolejne pytanie jak TO przedstawić w symulacji lub grze?
Otóż należy odwołać się do dwóch fundamentalnych zasad przyrody: zasada zachowania energii i pędu.
Dla ułatwienia zakładamy, że będziemy mieć do czynienia ze zderzeniami sprężystymi (brak rozproszenia energii zderzających się ciał np. zniszczenie karoserii, choć oczywiście można wprowadzić sobie taki współczynnik pochłaniania energii).
Zanim przejdę do omówienia odpowiedniego algorytmu wprowadzę lub przypomnę wyżej wspomniane zasady odpowiednim rysunkiem poglądowym i wzorami.
(przyjmuję oznaczenia: czcionka pogrubiona oznacza wielkość wektorową)
ZASAD ZACHOWANIA PĘDU
Należy pamiętać, że pęd jest wielkością wektorową stąd p1′ + p2′ = p1 + p2
Gdzie:
p1 – pęd ciała pierwszego przed zderzeniem;
p2 – pęd ciała drugiego przed zderzeniem;
p1′ – pęd ciała pierwszego po zderzeniem;
p2′ – pęd ciała drugiego po zderzeniem;
Pęd ciała wyrażony jest przez iloczyn masy ciała i jego prędkości. Masa ciała nie ulega zmianie (chyba, że uwzględnimy ubytek np. paliwa), ale prędkość tak- zwłaszcza przy zderzeniach.
Dla ułatwienia rozpatrzmy zderzenie na płaszczyźnie (w 3D dojdzie w analogiczny sposób składowa Z)
Zasadę zachowania pędu rozpiszemy na składową w kierunku osi X i osi Y (to samo będzie dotyczyć energii)
p’x1 + p’x2 = px1 + px2
p’y1 + p’y2 = py1 + py2
stąd można wyrazić zmianę pędu przez zmianę prędkości (ta wielkość nas interesuje)
m1V’x1 + m2V’x2 = m1Vx1 + m1Vx2
m1V’y1 + m2V’y2 = m1Vx1 + m2Vx2
W analogiczny sposób postępuje się z energią. W tym wypadku energią kinetyczną, wyrażoną zależnością E=mv^2/2. Poprzez, którą zasadę zachowania energii zapisać można:
E = E1 + E2
Kolejne kroki wyprowadzenia zależności na prędkość ciała po zderzeniu pomijam, można to znaleźć w każdej książce z fizyki. Poniżej podam odpowiednie zależności na składowe wektora prędkości w kierunku osi X i Y, przyjmując oznaczenie u dla nowego wektora prędkości.
Dla ciała pierwszego
Dla ciała drugiego
Jeżeli znamy składowe wektora prędkości, to jesteśmy wstanie wyliczyć kierunek wektora prędkości, czyli kąt w obranym układzie odniesienia, w jakim porusza się nasze ciało lub w jakim będzie się poruszać ciało po zderzeniu (stosunek Vy/ Vx jest tangensem kąta kierunku, Vy/ sqrt(sqr(Vx)+sqr(Vy)) jest sinusem kąta kierunku, można też zastosować cosinus, jak kto woli…)
Powiedzmy, że tyle o fizyce. Możemy teraz przystąpić do napisania symulacji zderzenia ciał. Symulacja będzie uwzględniać:
a) zmianę wartości prędkości po zderzeniu
b) wyznaczać kierunek ruchu po zderzeniu
c) obrót ciała- obrazu zgodnie z kierunkiem ruchu
Pełny opis i dodatkowy komentarz znajduje się w kodzie programu.
Duszek naszego programu żyje dzięki poniższej klasie
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 |
TCialo=class(TSprite) m :single; //masa rAlfa :single; //wartosc kata w radianach fKolizja :boolean; //flaga stanu kolizji dv :single; //wspolczynnik wektora predkosci dVx,dVy :single; //skaldowe wspolczynnika wektora predkosci private function Rotacja:single; public constructor Create(const Parametry:TParametry);virtual; procedure Move(const MoveCount:single);override; procedure onCollision(const Sprite:Tsprite;const colX,colY:integer);override; procedure Zderzenie_GranicaEkranu; end; |
funkcja Rotacja wyznacza obrót obrazu duszka według poniższego algorytmu
(nie będę się tu rozpisywać nad pojęciem ćwiartki układu odniesienia i radianami)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function TCialo.Rotacja:single; begin result:=rotation; //I cwiartka if(dvx>=0)and(dVy>=0)then result:=dVy*180/(sqrt(sqr(dVx)+sqr(dVy))*pi); //II cwiartaka if(dvx<0)and(dVy>=0)then result:=180-dVy*180/(sqrt(sqr(dVx)+sqr(dVy))*pi); //III cwiartaka if(dvx<=0)and(dVy<=0)then result:=180-dVy*180/(sqrt(sqr(dVx)+sqr(dVy))*pi); //IV cwiartaka if(dvx>0)and(dVy<=0)then result:=360+dVy*180/(sqrt(sqr(dVx)+sqr(dVy))*pi); end; |
Fizyka zderzenia ujęta jest w procedurze kolizji, którą przedstawiam poniżej
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 |
//TA PROCEDURA ZAWIERA "FIZYKĘ" ZDERZENIA SPRĘZYSTEGO CIAŁ //(ZASADA ZACHOWANIA PEDU I ENERGII) procedure TCialo.onCollision(const Sprite:Tsprite;const colX,colY:integer); var dUx,dUy:single; begin // if (Sprite is TCialo)then begin if (not fKolizja)or(not TCialo(Sprite).fKolizja) then begin //oblicz nowe tymczasowe skaldowe wektora predkosci po zderzeniu dla ciala 1 dUx:=((m-TCialo(Sprite).m)*dVx+2*TCialo(Sprite).m*TCialo(Sprite).dVx)/(m+TCialo(Sprite).m); dUy:=((m-TCialo(Sprite).m)*dVy+2*TCialo(Sprite).m*TCialo(Sprite).dVy)/(m+TCialo(Sprite).m); //oblicz nowe skaldowe wektora predkosci po zderzeniu dla ciala 2 TCialo(Sprite).dVx:=((TCialo(Sprite).m-m)*TCialo(Sprite).dVx+2*m*dVx)/(m+TCialo(Sprite).m); TCialo(Sprite).dVy:=((TCialo(Sprite).m-m)*TCialo(Sprite).dVy+2*m*dVy)/(m+TCialo(Sprite).m); //uaktualnij skladowe predkosci ciala pierwszego dVx:=dUx; dVy:=dUy; //oblicz wspołrzedne srodka wspolrzednych zderzajacych sie cial(układu) xSR:=(x+TCialo(Sprite).x)/2; ySR:=(y+TCialo(Sprite).y)/2; //******TEN BLOK MOZNA POMINĄĆ ALE CIAłA BĘDĄ NACHDZIĆ NA SIEBIE***** //UKATULANIJ WSPOLRZEDNE CIALA 1 WZGLEDEM WSPOLRZEDNYCH SRODKA //ZDAERZENIA UKŁADU if x>=xSR then x:=xSr+Image.ImageWidth div 2 else x:=xSr-Image.ImageWidth div 2; if y>=ySR then y:=ySr+Image.ImageHeight div 2 else y:=ySr-Image.ImageHeight div 2; //UAKTULANIJ WSPOLRZEDNE CIALA 2 WZGLEDEM WSPOLRZEDNYCH SRODKA //ZDAERZENIA UKLADU if TCialo(Sprite).x>=xSR then TCialo(Sprite).x:=xSr+TCialo(Sprite).Image.ImageWidth div 2 else TCialo(Sprite).x:=xSr-TCialo(Sprite).Image.ImageWidth div 2; if TCialo(Sprite).y>=ySR then TCialo(Sprite).y:=ySr+TCialo(Sprite).Image.ImageHeight div 2 else TCialo(Sprite).y:=ySr-TCialo(Sprite).Image.ImageHeight div 2; //********************************** rotation:=rotacja; fKolizja:=true; TCialo(Sprite).rotation:=TCialo(Sprite).rotacja; TCialo(Sprite).fKolizja:=true; end; end; end; |
Tu chciałbym zwrócić uwagę osobą zaczynającym programować obiektowo z użyciem komponentu OMEGI (to samo dotyczy i DELPHIX). Jak zobaczycie w pełnym kodzie programu można wygenerować do 100 ciał i nie ma pętli lub referencji, która by obsługiwała każde ciało z osobna. Za to odpowiedzialne są linie kodu umieszczone w procedurze „zegara” aplikacji. Poniżej przedstawiam (pełna postać w źródle programu)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TForm1.OmegaTimer1Timer(Sender: TObject); ...... begin .... OmegaSprite1.Collision; OmegaSprite1.Draw; OmegaSprite1.Move(OmegaTimer1.DeltaSecs); ...... end; |
Poniższa procedura odpowiedzialna jest za ruch duszków ciał.
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 |
procedure TCialo.Move(const MoveCount:single); var alfa,dv,v:single; begin inherited Move(MoveCount); dV:=sqr(dvx)+sqr(dvy); if dV<>0 then v:=sqrt(dV); alfa:=rotation*pi/180; x:=x+V*MoveCount*cos(alfa); y:=y+V*MoveCount*sin(alfa); Zderzenie_GranicaEkranu; fKolizja:=false; end; |
Można jeszcze przytoczyć postać procedury zderzenia ze ściankami, czyli krawędziami ekranu. Krawędzie ekranu można potraktować jako ciała o bardzo dużej masie (dążącej do nieskończoności w porównaniu z masą ciała uderzającego w krawędź). Jej działanie można zobrazować tym rysunkiem
Zaś procedurę poniższym kodem
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 |
procedure TCialo.Zderzenie_GranicaEkranu; begin //zabezpiecz przed wypchaniem poza ekran if x<0 then x:=0; if x>800-Image.ImageWidth then x:=800-Image.ImageWidth; if y<0 then y:=0; if y>=600-Image.ImageHeight then y:=600-Image.ImageHeight; //zderzenie z lewa i prawą scianą if(x<=0)or(x>=800-Image.ImageWidth)then dVx:=-dVx; //zderzenie z gorną i dolną scianą if(y<=0)or(y>=600-Image.ImageHeight)then dVy:=-dVy; rotation:=rotacja; end; |
Podsumowując najważniejszym elementem tych rozważań jest procedura TCialo.onCollision(const Sprite:Tsprite;const colX,colY:integer)pozostałe procedury są otoczką funkcjonowania programu. Stąd też osoby nie uznające DELPHI powinny zwrócić na nią uwagę. Pozostałe elementy programu oprogramować według własnego uznania.
Pozdrawiam oksal
Autor: oksal