Ten kurs polega na spoglądaniu na otaczający nas świat i wymyślaniu sprytnych sposobów na symulowanie tego świata kodem. Zaczniemy od spojrzenia na podstawową fizykę - jak jabłko spada z drzewa, jak wahadło kołysze się w powietrzu, jak ziemia kręci się wokół słońca, itd. Wszystko, o czym będziemy mówić, wymaga użycia najbardziej fundamentalnego elementu do programowania ruchu - wektora. Tu właśnie zaczynamy naszą opowieść.
Słowo "wektor" może oznaczać wiele różnych rzeczy. Vector to nazwa zespołu New Wave utworzonego w Sacramento, CA na początku lat osiemdziesiątych. To nazwa płatków śniadaniowych tworzonych przez Kellogg w Kanadzie. W świecie epidemiologii, wektor oznacza organizm przenoszący infekcję z jednego gospodarza do drugiego. W języku programowania C++, wektor (std::vector) jest implementacją struktury danych - tablicy dynamicznej. Podczas gdy wszystkie te definicje są ciekawe, nie są tym, czego szukamy. To czego chcemy nazywa się wektorem euklidesowym (także znany jako wektor geometryczny, nazwany tak od greckiego matematyka Euklidesa). Kiedy spotkasz się z terminem “wektor” w tym kursie, możesz założyć, że odnosi się to do wektora euklidesowego, zdefiniowanego jako obiekt posiadający moduł i kierunek.
Wektor z reguły rysowany jest jako strzałka; kierunek oznaczany jest przez to, gdzie strzałka wskazuje, a moduł przez długość samej strzałki.
Schemat wektora z wartością i kierunkiem
Rysunek 1.1: Wektor (narysowany jako strzałka) ma moduł (długość strzałki) oraz kierunek (kierunek który wskazuje).
W powyższej ilustracji wektor jest narysowany jako strzałka z punktu A do punktu B i służy jako instrukcja jak przejść z A do B.

Po co używać wektorów?

Zanim zagłębimy się w szczegóły dotyczące wektorów, spójrzmy na podstawowy program, który zademonstruje dlaczego w ogóle powinniśmy przejmować się wektorami. Jeśli przeszedłeś przez wprowadzający kurs JSa w Khan Academy, to prawdopodobnie w pewnym momencie nauczyłeś się jak napisać prosty program odbijającej się piłki.
W powyższym przykładzie mamy bardzo prosty świat - biały obraz z okrągłym kształtem (“piłką”) przemieszczającym się tam i z powrotem. Ta piłka ma pewne właściwości, które przedstawiane są w kodzie jako zmienne.
PołożeniePredkość
x i yxSpeed i ySpeed
W bardziej złożonym programie moglibyśmy mieć więcej zmiennych:
PrzyspieszeniePolożenie doceloweWiatrTarcie
xacceleration i yaccelerationxtarget i ytargetxwind i ywindxfriction i yfriction
Staje się coraz bardziej jasne, że dla każdego pojęcia w tym świecie (wiatr, położenie, przyspieszenie, itp.) będziemy potrzebować dwóch zmiennych. A to tylko dwuwymiarowy świat. W świecie 3D będziemy potrzebować x, y, z, xSpeed, ySpeed, zSpeed, i tak dalej.
Czy nie byłoby miło gdybyśmy mogli uprościć nasz kod i użyć mniej zmiennych?
Zamiast:
var x = 5;
var y = 10;
var xSpeed;
var ySpeed;
Moglibyśmy mieć po prostu dwie zmienne, gdzie każda z nich jest wektoropodobnym obiektem z dwoma wielkościami zawierającymi informacje:
var position;
var speed;
Poczynienie tego pierwszego kroku w używaniu wektorów nie pozwoli nam zrobić niczego nowego. Zwykłe używanie wektoropodobnych obiektów jako zmiennych magicznie nie sprawi, że twój program będzie symulować fizykę. Jednakże uprości to twój kod i zapewni zbiór funkcji do typowych działań matematycznych, które zdarzają się cały czas podczas programowania ruchu.
Jako wprowadzenie do wektorów, będziemy żyć w dwóch wymiarach. Wszystkie z poniższych przykładów mogą być dość łatwo rozszerzone do trzech wymiarów (obiekt, którego użyjemy - PVector - pozwala na trzy wymiary). Jednakże łatwiej jest zacząć z tylko dwoma.

Programowanie z PVector

Jednym ze sposobów aby rozpatrywać wektor jest różnica między dwoma punktami. Zastanów się jak mógłbyś dostarczyć instrukcji do przejścia z jednego punktu do drugiego.
Oto kilka wektorów i możliwe interpretacje jako przesunięć:
Wykresy wektorów
Rysunek 1.2
| (-15, 3) | Idź piętnaście kroków na zachód; skręć i idź trzy kroki na północ. | | (3, 4) | Idź trzy kroki na wschód; skręć i idź cztery kroki na północ. | | (2, -1) | Idź dwa kroki na wschód; skręć i idź jeden krok na południe. |
Prawdopodobnie robiłeś to wcześniej gdy programowałeś ruch. Dla każdej klatki animacji (np. pojedynczego cyklu pętli draw() w ProcessingJS) zlecasz każdemu obiektowi na ekranie aby przemieścił się o pewną liczbę pikseli poziomo i o pewną liczbę pikseli pionowo.
Rysunek objaśniający wykorzystanie wektora w celu wyznaczenia nowego położenia
Rysunek 1.3
Dla każdej klatki:
nowe położenie = prędkość zastosowana do aktualnego położenia
Jeśli prędkość jest wektorem (różnicą między dwoma punktami), to czym jest położenie? Czy też jest wektorem? Z technicznego punktu widzenia można rozumować, że położenie nie jest wektorem, skoro nie opisuje jak przemieścić się z jednego punktu do drugiego — po prostu opisuje pojedynczy punkt w przestrzeni.
Niemniej, innym sposobem na określenie położenia jest droga z punktu źródłowego, obrana w celu osiągnięcia tego położenia. W związku z tym położenie może być wektorem reprezentującym różnicę między położeniem i źródłem.
Rysunek wyjaśniający jak można przestawić położenie za pomocą wektora
Rysunek 1.4
Zbadajmy zasadnicze dane dla położenia i prędkości. W przykładzie z odbijającą się piłką mieliśmy następujące:
PołożeniePrędkosc
x i yxSpeed i ySpeed
Zauważ jak przechowujemy te same dane dla obu—dwie liczby zmiennoprzecinkowe x i y. Gdybyśmy mieli sami napisać klasę wektora, zaczęlibyśmy od czegoś raczej prostego:
var Vector = function(x, y) {
this.x = x;
this.y = y;
};
W gruncie rzeczy, PVector jest tylko wygodnym sposobem na składowanie dwóch wartości (lub trzech, co zobaczymy w przykładach 3D).
Zatem to …
var x = 100;
var y = 100;
var xSpeed = 1;
var ySpeed = 3.3;
staje się …
var position = new PVector(100,100);
var velocity = new PVector(1,3.3);
Teraz gdy mamy dwa obiekty wektorów (położenie i prędkość), jesteśmy gotowi do zaimplementowania algorytmu ruchu — location = location + velocity. W przykładzie 1.1 (bez wektorów) mieliśmy:
x = x + xSpeed;
y = y + ySpeed;
W idealnym świecie bylibyśmy w stanie przepisać powyższe jako:
position = position + velocity;
Jednakże, w JavaScripcie operator dodawania + jest zarezerwowany tylko dla wartości prostych (liczby, łańcuchy znaków). W niektórych językach programowania operatory mogą być "przeciążane", ale nie w JavaScripcie. Na szczęście dla nas, obiekt PVector zawiera metody dla typowych działań matematycznych, takich jak add().

Dodawanie wektorów

Zanim przejdziemy dalej z omawianiem PVectora i jego metody add(), omówmy dodawanie wektorów za pomocą notacji znanej z książek od matematyki i fizyki.
Wektory są zwykle zapisywane w postaci pogrubionej, lub ze strzałką na górze. Na potrzeby tych lekcji, aby odróżnić wektor od wartości skalarnej (wartość skalarna odnosi się do pojedynczej wartości, takiej jak wartość całkowita lub zmiennoprzecinkowa), będziemy używać notacji ze strzałką:
  • Wektor: u \vec{u}
  • Skalar: x x
Powiedzmy, że mam poniższe dwa wektory:
Rysunek dwóch wektorów
Rysunek 1.5
Każdy z wektorów ma dwie składowe: x i y. Aby dodać do siebie dwa wektory, możemy po prostu dodać obie składowe x i obie y.
Rysunek 1.6
Innymi słowy:
w=u+v \vec{w} = \vec{u} + \vec{v}
może być zapisane jako:
Następnie, zastępując u i v ich wartościami z rysunku 1.6, dostaniemy:
co oznacza, że:
Ostatecznie zapisujemy to jako wektor:
w=(8,6) \vec{w} = (8, 6)
Teraz gdy rozumiemy jak dodawać dwa wektory do siebie, możemy zobaczyć jak dodawanie jest zaimplementowane w samym obiekcie PVector. Napiszmy metodę nazwaną add(), która bierze kolejny obiekt PVector jako jej argument i zwyczajnie dodaje składowe x i y do siebie.
var Vector = function(x, y) {
    this.x = x;
    this.y = y;
};

Vector.prototype.add = function(v) {
  this.y = this.y + v.y;
  this.x = this.x + v.x;
};
Teraz, gdy widzimy, jak add() jest zaimplementowane wewnątrz PVectora, możemy powrócić do naszego przykładu z odbijającą się piłką oraz algorytmu położenie + prędkość i zaprogramować dodawanie wektorów:
position.add(velocity);
Jesteśmy już gotowi aby przepisać przykład z odbijającą się piłką za pomocą obiektu PVector! Przyjrzyjcie się obu programom i postarajcie się znaleźć różnice.
Należy zwrócić uwagę na bardzo ważny element powyższego przejścia do programowania za pomocą wektorów. Pomimo, że używamy obiektów typu PVector do opisywania dwóch wartości — wartości x oraz y położenia i wartości x i y prędkości— musimy często odnosić się do wartości x lub y każdego elementu PVector z osobna. Gdy chcemy narysować obiekt w ProcessingJS, nie mamy możliwości użycia następującej metody:
ellipse(position, 16, 16);
Funkcja ellipse() nie przyjmuje PVectora jako argumentu. Elipsa może być narysowana tylko za pomocą dwóch wartości skalarnych, współrzędnej x oraz y. Musimy więc zajrzeć do wnętrza obiektu PVector i wyciągnąć z niego wartości x i y za pomocą notacji obiektowej z użyciem kropki:
ellipse(position.x, position.y, 16, 16);
Ten sam problem pojawia się podczas testowania, czy koło dotarło do końca ekranu - musimy odnieść się do poszczególnych składowych obu wektorów: położenia oraz prędkości.
if ((position.x > width) || (position.x < 0)) {
  velocity.x = velocity.x * -1;
}
Możecie czuć się trochę rozczarowani. Przecież przez te zmiany wydawać by się mogło, że nasz kod jest bardziej skomplikowany niż wersja oryginalna. Jest to prawdą, ale warto zwrócić też uwagę na to, że nie poznaliśmy jeszcze wszystkich atutów programowania za pomocą wektorów. Patrzenie na prostą odbijającą się piłkę i implementacja dodawania wektorów to tylko pierwszy krok.
Gdy zrobimy krok naprzód, czeka na nas skomplikowany świat wielu obiektów i wielu sił (które wkrótce zostaną wam przedstawione) - a wtedy zalety PVectora będą znacznie bardziej widoczne. Byle do przodu!

Ten kurs "Symulacje Natury" jest pochodną z "Natury Kodu " stworzonej przez Daniela Shiffmana, użytej pod licencją Creative Commons Attribution-NonCommercial 3.0 Unported License.
Ładowanie