Implementacja wektorów 2D

ubek666

Implementacja wektorów 2D by Willem van Doesburg Š Marzec 2000

Oryginał: Wersja angielska

Tłumaczył UBEK - UPortal

Przykłady do artykułu:
2Dvector.h
2Dvector.cpp
2Dvector.h
2Dvector.h

Tłumaczenie może być niedokładne, ale dopiero się uczę! Więc jakby coś to poprawię!

Instrukcja

</p>

W moich badaniach jako początkujący programista gier posługiwałem się
internetem i różnymi książkami, nie mam jak dotąd robić dokumentu
wyjaśniającego jak implementować wektory w grach. Faktycznie, Mam
założyć liczne dokumenty o: wektorach w matematyce, rysowaniu linii,
klasach wektorów w fizyce. Dokument o wektorach w matematyce nie
zajmuje się systemem współrzędnych na ekranie komputera.
Dokument o rysowaniu linii nie wyjaśnia jak przedstawiać linie za pomocą
klasy. Dokument o klasie wektora nie wyjaśnia operacji na tej klasie.
Dokument o fizyce nie daje tobie wszystkich przydatnych algorytmów
dla twojego programu. Ten dokument probóje zaspokoić wszystkie braki
innych dokumentów. Dokument używa notacji C++ dla przykładów,
ponieważ jest to jedyny język jaki znam.

Wektor (klasa)

</p>

Wektory generalnie mogą być reprezentowane na 2 różne sposoby.
1: przez długość i jakiś kąt
2: przez początkowy i końcowy punkt

Te dwie metody wykorzystują inną matematykę. Metoda 1 używa
trygonometri do obliczenia kątów i normalnych. Metoda 2 używa
macierzy do obliczenia obrotu i innych operacji. W tym dokumencje
my implementujemy wektor reprezentowany w metodzie 1.

Oto klasa:

 class C2DVector
 {
 private:
  long x, y; //współrzędne punktów
  int angle; //kąt wektora
  long length; //długośc wektora
 public:
  void setX(long Xin); //ustawia punkt x
  void setY(long Yin); //ustawia punk y
  void setLength(long Lin); //ustawia długość w pikselach
  void setAngle(int Ain); //ustawia kąt w stopniach

  long getX(); //zwraca punkt x
  long getY(); //zwraca punkt y
  long getLength(); //zwraca długość
  int getAngle(); //zwraca kąt
 public:
  void operator =(C2DVector aVector); //porównuje ten wektor z innym

  C2DVector(); //konstruktor
  C2DVector(long Xin, long Yin); //konstruktor przeładowany
  ~C2DVector(); //destruktor
};

Zauważ że dołączyłem współrzędne w mojej klasie. Chwila przecież
powiedziałem wcześniej że potrzebna jest ci tylko długość i kąt wektora,
aby go określić. Wybieram dołączenie współrzędnych, ponieważ wtedy
klasa jest sprawniejsza. Potrzebujesz kosinusa i sinusa, aby obliczyć
współrzędne. Żeby mieć współrzędne musimy tylko obliczyć
je raz na jakiś czas, natomiast zawsze potrzebujemy spółrzędne gotowe
do użycia. Długość wektora jest określona przez liczbe pikseli wtedy
może swobodnie zaspokoić wszystkie twoje potrzeby.

Inną ważną rzeczą jest używanie systemu współrzędnych. Normalne
wektory matematyczne zazwyczaj używają kartezjańskiego układu
współrzędnych.

Spójrz na kartezjański układ współrzędnych:

user image

Projektancji komputerowi oczywiście wybrali inne podejście do
sprawy(jak zwykle).

Ekran komputerowy używa następującego systemu współrzędnych:

user image

Nie potrzebujemy przebudowywać wielu formuł.
Chcemy używać układu kartezjańskiego! Później w tym dokumencie wszystko ci powiem o zamienianiu na twój układ z układu kartezjańskiego i
odwrotnie.

Przygotowania

</p>

Potrzebujemy kosinusa i sinusa, żeby obliczyć obrót. Doświadczeni czytelnicy zobaczą, że Ja wyrażam kąt wektora w stopniach, z chwilą obecną większość bibliotek matemtycznych wykorzystuje do obliczenia kosinusa i sinusa radiany. Więc musimy zamienić stopnie na radiany dla tej funkcji. Dlatego zdefiniujemy liczbę PI:
#define PI 3.141592654

Jeżeli chodzi o wydajność będę przechowywał sinusa i kosinusa dla każdego kąta w tablicy, więc nie musimy używać wolnych funkcji sin() i cos() dla ostatnich liczb. Teraz zadeklarujemy tablice:

 float costable[360];
 float sintable[360];

My będziemy potrzebować ostatnie liczby, żeby uzupełnić te tablice.

Ta funkcja konwertuje stopnie na radiany:
red = deg / 180 * PI

 void initTables()
 
  float temp;
  for (int i = 0; i < 360; i++)
  {
   temp = ((float)(360 - i) / 180);
   /*
      potrzebne obliczenia. używam tego (360 - i), dla tego dziwnego
      układu współrzędnych.
   */
   costable[i] = (float)cos(temp) 
   sintable[i] = (float)sin(temp);
  }
 }

Definicja klasy

Teraz zdefiniujemy dwie ważne funkcje w naszej klasie. setLength(long Lin) i
setAngle(long Ain) obliczają one x i y.

My możemy użyć tych wzorów do obliczeń:
x = cos(angle) * length
y = sin(angle) * length

Oto kod obydwu funkcji:

 void C2DVector::setLength(long Lin)
 {

  length = Lin;
  x = costable[angle] * length;
  y = sintable[angle] * length;
 }

 void C2DVector::setAngle(int Ain)
 {
  angle = Ain;
  x = costable[angle] * length;
  y = sintable[angle] * length;
 }

Jak używać klasy

</p>

Skoro już mamy gotową klasę, chcemy dowiedzieć się jak z niej korzystać.
Dobrze teraz przetestujemy nasz wektor na stole bilardowym.
W tej scenerii my musimy mieć obiekt piłki dla której chcę stworzyć wektor
określający tor jej lotu i prędkość. To jest całkiem proste,
określamy ruch naszej piłki wokół kąta i długości, a następnie
kontynuujemy dodawanie punktów x, y do punktów x, y naszej piłki.

Problem powstaje, kiedy nasza piłka trafia w bande stołu. Czasem zdarza się
że musimy obliczyć nową prędkość wektora dla naszej piłki, żeby odbiła się
naturalnie. Jeśli piłka odbiła się od bandy normalnie możemy obliczyć
nowy wektor dla tej dorgi:

  1. odwracamy wektor prędkości
  2. obliczamy delta(v, n)
  3. angle = angle +/- delta(v, n)

Oto kod:

 void CBall::HitBoundary(C2DVector *BoundaryNormal)
 {
   int angle;
   int OppAngle;
   int NormDiffAngle;

   angle = Velocity.getAngle();
   OppAngle = (angle + 180) % 360;
   if (BoundaryNormal->getAngle() >= OppAngle)
   {
    NormDiffAngle = BoundaryNormal->getAngle() - OppAngle;
    angle = (BoundaryNormal->getAngle() + NormDiffAngle) % 360;
   }

   if (BoundaryNormal->getAngle() < OppAngle)
   {
    NormDiffAngle = OppAngle - BoudnaryNormal->getAngle();
    angle = BoudnaryNormal->getAngle() - NormDiffAngle;
   }

   Velocity.setAngle(angle);
 }

W implementacji klasy mojego wektora. Dostosowałem go dla dziwnego
układu współrzędnych. Jeśli kiedyś będziesz potrzebował zamienić układ
kartezjański na ten dziwny to masz tu kod:

 void ConvertCoordinates(long *X, long *Y, bool convert)
 {
  int Xmiddle;
  int Ymiddle;
  Xmiddle = (int)(XRES / 2); //XRES to rozdzielczość ekranu w poziomie
  Ymiddle = (int)(YRES / 2); //YRES to rozdzielczość ekranu w pionie

  if (convert)
  {
   *Y = -*Y;
   *X = *X + Xmiddle;
   *Y = *Y + Ymiddle;
  } else {
   *X = *X - Xmiddle;
   *Y = *Y - Ymiddle;
   *Y = -*Y;
  }
 }

Zakończenie

</p>

Teraz wiesz jak skutecznie używać wektorów w twoich grach. Na górze są
zamieszczone linki do kodu źródłowego załączonego do artykułu.<url>

9 komentarzy

Tak pomysł dobry, ale wykonanie do kitu. Po pierwsze Primo: ta klasa powinna mieć tylko współrzędne jako obiekty składowe, bo szkoda pamięci na coś co i tak będzie się zmieniało, a zamiast tego robi się funkcje do obliczenia długości wektora, obliczenia kąta między wektorami itp. Po drugie Primo: defy funkcji robi się jako inline - bo tak jest szybciej, nie? Po trzecie Primo: operator= nie porównuje (bo do tego jest operator==) tylko przypisuje, oczywiście to zależy od ciebie, bo po to zostało wymyślone przeładowanie, ale z defa wynika przypisanie.

I nie bardzo mi się podoba ten "konstruktor przeładywujący". Pewnie było overloaded constructor? Jeśli tak, to bardziej konstruktor przeładowany :)

Hehe :) Może masz rację!

A moze to taki uklad gdzie kazda wspolrzedna jest traktowana funkcją ABS :p

nie wiem dlaczego tak jest. Mnie też to dziwiło! :)

popraw fatalne błędy ortograficzne!

Pomysl dobry. Ale uwazam, ze powinienes jeszcze troche czasu poswiecic nauce angielskiego, bo z twojego tlumaczenia - nie obraz sie - wychodza totalne bzdury :)
Co ma wspolnego wektor 2d z psychika ? :D
physics - fizyka

Nic się nie stało, a angielskiego dopiero się uczę!
Spróbuje poprawić ten tekst! :)

mnie troche martwi wygląd tego układu kartezjańskiego - czemu są aż cztery strzałki, a nie tylko 2 (góra, prawo)?