Poprzedni rozdział zakończyliśmy przykładem jak obliczyć przyspieszenie na podstawie znajomości wektora skierowanego od kółka do pozycji kursora. Ruch, jaki uzyskaliśmy w ten sposób przypominał działanie siły przyciągania pomiędzy kółkiem a myszką, tak jakby jakaś siła przyciągała kółko w kierunku punktu, w którym znajdowała się myszka .
W tym rozdziale podejdziemy do zagadnienia siły i jej związku z przyspieszeniem obiektu, na który działa, bardziej formalnie. Naszym celem jest zrozumienie, w jaki sposób stworzyć grupę obiektów poruszających się po ekranie i reagujących na działające na nie siły.
Zanim zaczniemy badać praktyczne symulowanie sił w kodzie, przyjrzyjmy się czym jest siła w prawdziwym świecie. Tak jak słowo “wektor,” “siła” może oznaczać wiele różnych rzeczy. Może oznaczać spory wysiłek, na przykład w “Odsunęła głaz z wielką siłą”. Definicja siły, na której nam zależy jest znacznie bardziej formalna i pochodzi z praw dynamiki Newtona:
Siła to wektor, który powoduje, że obiekt posiadający masę przyspiesza.
Dobrą wiadomością jest to, że rozumiemy pierwszą część definicji: siła to wektor. Całe szczęście, że właśnie ukończyliśmy obszerny dział poświęcony wektorom i wiemy jak je zaprogramować za pomocą obiektów PVector!
Przyjrzyjmy się trzem prawom dynamiki Newtona w odniesieniu do sił.

Pierwsze Prawo Dynamiki Newtona

Powszechnie pierwsze prawo dynamiki przedstawione jest w sposób następujący:
Obiekt będący w stanie spoczynku pozostaje w stanie spoczynku, a obiekt będący w ruchu pozostaje w ruchu.
Niestety, brakuje tutaj powiązania z siłami, o których mówimy. Możemy rozszerzyć powyższe stwierdzenie dodając:
Obiekt będący w stanie spoczynku pozostaje w stanie spoczynku, a obiekt będący w ruchu pozostaje w ruchu ze stałą prędkością i kierunkiem, dopóki nie wpływa na niego siła, która nie została zrównoważona.
Zanim pojawił się Newton, główna teoria dynamiki —stworzona przez Arystotelesa — miała już prawie dwa tysiące lat. Twierdziło ono, że jeżeli obiekt jest w trakcie ruchu, jakiś rodzaj siły jest wymagany, aby się poruszało. Dopóki obiekt nie jest ciągnięty lub pchany, zacznie zwalniać lub się zatrzyma. Prawda?
Oczywiście, nie jest to prawda. W przypadku braku wszelkich sił, żadna siła nie jest potrzebna by obiekt się przemieszczał. Obiekt (taki jak piłka) rzucony w ziemskiej atmosferze zaczyna zwalniać z powodu oporów powietrza (czyli siły). Prędkość obiektu pozostanie stała tylko, jeżeli nie będą na nie działały żadne siły, lub gdy siły te się negują, czyli ich siła wypadkowa równa się zero. Jest to często określane jako równowaga sił. Spadająca piłka osiągnie prędkość graniczną (która jest stała) gdy siła oporu powietrza równa się sile grawitacji.
W świecie naszego ProcessingJS, możemy opisać pierwsze prawo dynamiki w sposób następujący:
Wartość prędkości obiektu PVector pozostaje stała, jeżeli jest w stanie równowagi.
Pomińmy na chwilę drugie prawo dynamiki Newtona (prawdopodobnie najważniejsze dla nas w tym momencie) i omówmy trzecie prawo dynamiki.

Trzecie prawo dynamiki Newtona

Zwykle opisuje się je w sposób następujący:
Dla każdej akcji istnieje równa jej, przeciwna reakcja.
Sposób, w jaki jest sformułowane to prawo czasami może wprawić w zakłopotanie. Dla przykładu, brzmi jakby jedna siła powodowała występowanie innej. Tak, jeżeli kogoś popchniesz, ta osoba może postanowić również Ciebie popchnąć. Ale to nie ten rodzaj akcji i reakcji, o którym mówimy przy okazji trzeciego prawa dynamiki.
Powiedzmy, że próbujesz popchnąć ścianę. Ściana oczywiście nie postanowi w ramach rewanżu popchać Ciebie. Nie ma “źródła” siły. Twoje pchanie zawiera obie siły, do których odnosimy się jako “pary akcji i reakcji”
Lepszym sposobem na opis tego prawa jest przedstawienie go w postaci następującej:
Siły zawsze występują parami. Te dwie siły mają jednakową wartość, ale przeciwne kierunki..
To również brzmi trochę abstrakcyjnie, ponieważ brzmi jakby dwie siły zawsze się równoważyły. To nie ma miejsca. Pamiętaj, siły działają na różne obiekty. A to, że dwie siły są równe, wcale nie oznacza, że ich ruch jest równy (albo, że obiekty te przestaną się przemieszczać).
Spróbuj popchnąć zaparkowaną ciężarówkę. Chociaż ciężarówka jest znacznie silniejsza od Ciebie, w przeciwieństwie do poruszającej się, zaparkowana ciężarówka nigdy nie sprawi, że odlecisz do tyłu. Siła którą wywierasz jest równa i ma przeciwny zwrot do siły wywieranej na twoje ręce. Wynik zależy jeszcze od innych czynników. Jeżeli ciężarówka to mniejszy model stojący zaparkowany na zamarzniętej drodze na wzgórzu, prawdopodobnie będziesz w stanie ją przesunąć. Dla kontrastu, jeżeli jest to bardzo duża ciężarówka na polnej drodze i będziesz wystarczająco mocno pchał (może nawet spróbujesz z rozbiegu), możesz uszkodzić swoją rękę.
A co, jeżeli pchałbyś ciężarówkę będąc na wrotkach?
Przeróbmy trzecie prawo dynamiki, aby pasowało do naszego świata ProcessingJS:
Jeżeli obliczamy PVector f który jest siłą, z jaką obiekt A działa na obiekt B, musimy także dodać siłę — PVector.mult(f,-1);—wywieraną przez obiekt B na obiekt A.
Wkrótce okaże się, że w świecie programowania ProcessingJS, nie zawsze prawo to zachodzi. Czasami, tak jak w przypadku przyciągania grawitacyjnego pomiędzy ciałami, chcemy zamodelować równe i przeciwne siły. Kiedy indziej po prostu stwierdzimy “Hej, w otoczeniu czuć podmuch wiatr.”, ale nie będzie to na tyle istotne, aby modelować siłę którą ciało wywiera na wiatr. Tak na prawdę, wcale nie modelujemy wiatru! Pamiętaj, tylko wzorujemy się na fizyce prawdziwego świata, nie symulujemy wszystkiego z idealną precyzją.

Drugie prawo dynamiki Newtona

Przechodzimy do najważniejszego prawa dla programisty ProcessingJS.
To prawo zwykle jest określane w następujący sposób:
Siła równa się iloczynowi masy i przyspieszenia.
Lub:
F=MA \vec{F} = M\vec{A}
Czemu jest to dla nas najważniejsze prawo? Cóż, zapiszmy je w trochę inny sposób.
A=F/M \vec{A} = \vec{F}/M
Przyspieszenie jest wprost proporcjonalne do siły i odwrotnie proporcjonalne do masy. Oznacza to, że jeżeli ktoś Cię popchnie, im mocniej to zrobi, tym szybciej będziesz się ruszać (przyspieszać). Im większa osoba, tym wolniej się porusza.
Ciężar kontra masa
Masa obiektu to miara ilości materii, z której składa się ten obiekt (mierzona w kilogramach).
Waga, często mylona z masą, to siła, z jaką grawitacja oddziałuje na dany obiekt. Korzystając z drugiej zasady Newtona, możemy ją wyznaczyć jako iloczyn masy i przyciągania grawitacyjnego (w = m * g). Wagę mierzymy w niutonach.
>Gęstość jest definiowana jako ilość masy na jednostkę objętości (dla przykładu gram na centymetr sześcienny).
Zwróć uwagę, że obiekt o masie jednego kilograma będzie miał również masę jednego kilograma na księżycu. Ale jego ciężar wynosiłby jedynie jedną szóstą tego, co na Ziemi.
W świecie ProcessingJS, czym jest masa? Czy nie mamy do czynienia z pikselami? By zacząć od prostych kwestii, przyjmijmy, że w naszym zmyślonym pikselowym świecie wszystkie obiekty mają masę równą 1. F/1 = F. Tak więc:
A=F \vec{A} = \vec{F}
Przyspieszenie obiektu jest równe sile. To dobra wiadomość. Widzieliśmy przecież w sekcji poświęconej wektorom, że przyspieszenie to klucz do kontrolowania ruchu naszych obiektów na ekranie. Położenie jest zależne od prędkości, a prędkość zależy od przyspieszenia. Wszystko zależy więc od przyspieszenia. Teraz odkryliśmy, że to siła jest prawdziwym źródłem zmian.
Sprawdźmy, czy możemy zastosować to, czego się nauczyliśmy na naszym obiekcie Mover, który aktualnie posiada położenie, prędkość i przyspieszenie. Naszym aktualnym celem jest dodanie możliwości dodawania sił do obiektu, takich jak:
mover.applyForce(wind);
Albo:
mover.applyForce(gravity);
gdzie wiatr i grawitacja to PVectory. Zgodnie z drugim prawem dynamiki, możemy zaimplementować te funkcje w sposób następujący:
Mover.prototype.applyForce = function(force) {
  this.acceleration = force;
};

Dodawanie sił

Wygląda to całkiem nieźle. W końcu przyspieszenie = siła to dokładne przetłumaczenie drugiego prawa Newtona (bez masy). Mimo wszystko, to dosyć duży problem. Przypomnijmy sobie, co chcemy osiągnąć: stworzyć poruszający się obiekt który reaguje na wiatr i grawitację.
mover.applyForce(wind);
mover.applyForce(gravity);
mover.update();
mover.display();
Dobrze, udajmy przez chwilę, że jesteśmy komputerem. Wpierw wywołujemy applyForce() z wiatrem. Tak więc przyspieszenie obiektu Mover ma teraz przypisany PVector wiatru. Następnie, wywołujemy applyForce() z grawitacją. Teraz przyspieszenie obiektu Mover ma przypisany PVector grawitacji. Teraz wykonujemy update(). Co się dzieje w update()? Przyspieszenie jest dodane do prędkości.
velocity.add(acceleration);
Nie zobaczymy żadnych błędów w programie, ale niespodzianka! Mamy spory problem. Jaka jest wartość przyspieszenia, gdy zostaje dodane do prędkości? Równa się sile grawitacji. Wiatr został pominięty! Jeżeli wywołamy applyForce() więcej niż raz, nadpisuje ona poprzednie wywołanie. Jak będziemy obsługiwać więcej niż jedną siłę?
Prawdą jest to, że zaczęliśmy od uproszczonego drugiego prawa dynamiki Newtona. Oto bardziej dokładny sposób określenia go:
Wypadkowa siła równa się iloczynowi masy i przyspieszenia.
Albo, że przyspieszenie jest równe sumie wszystkich sił podzielonej przez masę. To ma sens. Jak zostało to przecież określone w pierwszym prawie dynamiki Newtona, jeżeli suma wszystkich sił daje wartość zero, obiekt jest w stanie równowagi (np. nie ma przyspieszenia). Zaimplementujemy to za pomocą procesu znanego jako dodawanie sił. To bardzo proste: musimy po prostu dodać wszystkie siły do siebie. W dowolnym momencie może być 1, 2, 6, 12 lub 303 sił działających na obiekt. Tak długo, jak obiekt wie jak je do siebie dodać, nie ma problemu z tym, ile ich jest.
Zmodyfikujmy metodę applyForce() aby móc dodawać kolejne siły do przyspieszenia, łącząc je:
Mover.prototype.applyForce = function(force) {
  this.acceleration.add(force);
};
Ale jeszcze nie skończyliśmy. Dodawanie sił wymaga jeszcze jednej rzeczy. Skoro dodajemy wszystkie siły działające w dowolnym momencie, musimy się upewnić że wyczyścimy wartość przyspieszenia (np. ustawimy, żeby była równa zero) za każdym razem gdy update() jest wywoływana. Pomyślmy przez chwilę o wietrze. Czasami wiatr jest bardzo silny, czasami jest słaby, a czasami w ogóle nie występuje. W dowolnym momencie może istnieć ogromny podmuch wiatru, na przykład gdy użytkownik przytrzyma myszkę:
if (mouseIsPressed) {
  var wind = new PVector(0{,}5, 0);
  mover.applyForce(wind);
}
Gdy użytkownik zwolni przycisk myszy, wiatr zaniknie, a zgodnie z pierwszym prawem Newtona, obiekt będzie w dalszym ciągu poruszał ze stałą prędkością. Jednakże jeśli zapomnimy wyzerować przyspieszenie, ruch będzie nadal wyglądać tak, jak pod wpływem podmuchu wiatru. Co gorsza, efekt doda się do tego, co było na poprzedniej klatce, ponieważ dodajemy do siebie siły na kolejnych klatkach!
W naszej symulacji, przyspieszenie nie ma pamięci; za każdym razem obliczamy je na podstawie sił, działających w układzie w danej chwili czasu. Inaczej,niż w przypadku położenia, które musi pamiętać gdzie nasz obiekt znajdował się na poprzedniej klatce, aby móc prawidłowo obliczyć jego położenie na klatce następnej.
Najprostszym sposobem jest zerowanie przyspieszenia po każdym przebiegu mnożąc wartość PVector razy 0 na końcu update().
Mover.prototype.update = function() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.acceleration.mult(0);
};

Co robimy z masą

OK. Pozostała nam jedna rzecz do dodania, zanim skończymy implementować siły do naszej klasy Mover i przystąpienia do przykładów. W końcu drugie prawo Newtona to F=MA \vec{F} = M\vec{A} , a nie A=F \vec{A} = \vec{F} . Uwzględnienie masy to rzecz banalna, polegająca na dodaniu właściwości do naszego obiektu, ale potrzebujemy spędzić trochę więcej czasu nad tą kwestią, gdyż pojawiają się niewielkie komplikacje.
Jednostki miary
Uwzględnienie masy to dobra okazja by zastanowić się przez chwilę nad jednostkami. W świecie rzeczywistym, wielkości mierzymy i wyrażamy w odpowiednich jednostkach. Mówimy, że dwa obiekty znajdują się w odległości 3 metrów od siebie, albo że pociąg jedzie z prędkością 200 kilometrów na godzinę, a piłka lekarska waży 5 kilogramów. Jak się później przekonamy, przyjdzie taki czas, kiedy będziemy chcieli wziąć pod uwagę jednostki, ale w tym rozdziale nie będziemy w zasadzie zwracać na nie uwagi.
W naszym przypadku naturalną jednostką długości są piksele (“Odległość pomiędzy tymi dwoma kółkami wynosi 100 pikseli)”) i klatki animacji (“To kółko porusza się w tempie 2 pikseli na klatkę”). W przypadku masy, nie mamy równie naturalnej jednostki, z której moglibyśmy skorzystać. Po prostu, masa będzie jakąś liczbą. W tym przykładzie, ustaliliśmy arbitralnie że masa wynosi 10. Ta 10 nie niesie ze sobą żadnych konkretnych jednostek, choć możesz wymyślić swoje własne, np. "1 moog", albo "1 yurkle".
Aby przedstawić masy obiektów w poglądowy sposób, powiążemy rozmiar obiektu z jego masą -- jeśli masa obiektu wynosi 10, narysujemy kółko o promieniu 10 pikseli. W ten prosty sposób oznaczymy masę każdego obiektu i będziemy mogli łatwiej zrozumieć, jaki ma wpływ na jego ruch w naszym kodzie. W rzeczywistości nie ma tak prostego związku pomiędzy rozmiarem obiektu a jego masą. Niewielka, wykonana z metalu kulka ma większą masę niż duży balon
Masa to skalar, a nie wektor, gdyż jest to tylko jedna liczba opisująca ilość materii w obiekcie. Moglibyśmy podejść do tego kreatywnie i obliczać obszar kształtu jako jego masę, ale łatwiej jest zacząć poprzez podejście typu “Hej, masa tego obiektu to…yyy, no nie wiem...…około 10?”
var Mover = function() {
    this.mass = 10;
    this.position = new PVector(random(width), random(height));
    this.velocity = new PVector(0, 0);
    this.acceleration = new PVector(0, 0);
};
najlepszy sposób, gdyż zacznie robić się ciekawie dopiero gdy będziemy mieli obiekty o różnej masie, ale jest to jakiś początek. Gdzie przyda nam się masa? Użyjemy jej podczas stosowania drugiego prawa dynamiki w naszym obiekcie.
Mover.prototype.applyForce = function(force) {
  force.div(this.mass);
  this.acceleration.add(force);
};
Po raz kolejny, pomimo, że nasz kod wygląda raczej w porządku, mamy do czynienia ze sporym problemem. Rozważmy scenariusz z dwoma obiektami typu Mover, które są przesuwane przez siłę wiatru.
var m1 = new Mover();
var m2 = new Mover();

var wind = new PVector(1, 0);

m1.applyForce(wind);
m2.applyForce(wind);
Ponownie, wcielmy się w rolę komputera. Obiekt m1.applyForce() otrzymuje siłę wiatru (1,0) , dzieli je przez masę (10) i dodaje do przyspieszenia.
programwiatr
var wind = new PVector(1, 0);(1, 0)
m1.applyForce(wind)(0.1, 0)
OK. Przechodzimy do obiektu m2. W tym przypadku siła wiatru wynosi także—(1,0). Zaraz. Zastanówmy się przez sekundę. Ile wynosi siła wiatru? Spójrz, siła wiatru równa się teraz (0,1,0)!
Pamiętasz ten mały smakołyk związany z obiektami? W Java Script zmienna, która przechowuje obiekt (jak PVector) faktycznie wskazuje na konkretny element w pamięci. Kiedy przekazujesz ten obiekt do funkcji, nie przekazujesz kopii - przekazujesz wskaźnik do tego elementu. Jeśli funkcja zmienia wartość elementu (jak w tym przypadku, dzieląc go przez liczbę oznaczającą masę), to obiekt zapisany w pamięci będzie miał zupełnie inną wartość.
Dlatego coś poszło nie tak, jak powinno. Nie chcemy, aby na m2 działała siła, podzielona przez masę m1. Chcemy zastosować pierwotną wartość tej siły —(1,0). Oznacza to, że powinniśmy wykonać kopię PVector f, zanim podzielimy ją przez masę.
Na szczęście,obiekt PVector ma wygodną metodę wykonywania kopii—get(). get() zwraca nowy obiekt PVector z tymi samymi wartościami. Możemy więc zmodyfikować applyForce() w następujący sposób:
Mover.prototype.applyForce = function(force) {
  var f = force.get();
  f.div(this.mass);
  this.acceleration.add(f);
};
Alternatywnie możemy przepisać metodę używając statyczną wersję div(), używając tego, czego nauczyliśmy się o funkcjach statycznych z poprzedniej sekcji:
Mover.prototype.applyForce = function(force) {
  var f = PVector.div(force, this.mass);
  this.acceleration.add(f);
};
Najważniejsze jest, aby znaleźć sposób który nie wpłynie na oryginalny wektor siły, tak, aby mógł być użyty na wielu obiektach Mover.

Tworzenie sił

Wiemy czym jest siła(jest wektorem) oraz wiemy jak zastosować siłę do obiektu (podzielić ją przez masę tego obiektu i dodać do wektora przyspieszenia tego obiektu). Czego nam brakuje? Cóż, musimy znaleźć sposób, aby wiedzieć jak możemy uzyskać siłę. Skąd biorą się siły?
Przez tą sekcję, skupimy się na dwóch sposobach tworzenia sił w naszym świecie ProcessingJS:
  • Wymyśl siłę! Przecież jesteś programistą, stworzycielem swojego świata. Nie ma żadnego powodu, który zabroniłby Ci stworzyć własną siłę i użyć jej na obiektach.
  • Odwzoruj siłę! Tak, siły istnieją w prawdziwym świecie. A książki od fizyki często posiadają wzory na te siły. Możemy wykorzystać te wzory, przetłumaczyć je do postaci naszego kodu i odwzorować prawdziwe siły w ProcessingJS.
Najłatwiejszym sposobem na wymyślenie siły to wybranie liczby. Zacznijmy od symulacji wiatru. Co powiecie na siłę oznaczającą wiatr, który wieje w prawo i jest raczej słaby? Zakładając obiekt typu Mover oznaczony jako m, nasz kod wyglądałby tak:
var wind = new PVector(0.01, 0);
m.applyForce(wind);
Wynik nie jest zbyt ciekawy, ale to dobry początek. Tworzymy PVector, inicjalizujemy go, a następnie przekazujemy do obiektu (który następnie doda go do swojego przyspieszenia). Jeśli chcemy mieć dwie siły, powiedzmy wiatr oraz grawitację (trochę mocniejsza siła, ciągnąca w dół), możemy napisać tak:
var wind = new PVector(0.01, 0);
var gravity = new PVector(0, 0.1);
m.applyForce(wind);
m.applyForce(gravity);
Teraz mamy dwie różne siły, wskazujące w różnych kierunkach z różną mocą, obie odnoszące się do obiektu m. To już coś. Zbudowaliśmy świat z naszych obiektów w ProcessingJS, środowisko w którym mogą naprawdę reagować.
A oto jak wygląda nasz program po złączeniu tego wszystkiego w całość:
Hurra! Omówiliśmy tu naprawdę dużo, ale teraz możemy zrobić znacznie więcej. Nie przestawaj - naucz się używać siły!

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