Główna zawartość
Programowanie
Kurs: Programowanie > Rozdział 5
Lekcja 8: Systemy cząstek- Wprowadzenie do systemów cząstek
- Pojedyncza cząsteczka
- Wyzwanie: opadające liście
- System cząstek
- Wyzwanie: Rybie bąbelki
- Systemy systemów cząstek
- Wyzwanie: Podpalacz
- Typy cząstek
- Wyzwanie: Magiczny kocioł
- System cząstek z siłami
- Wyzwanie: Rzeczne skały
- Projekt: Kolonie stworzeń
© 2023 Khan AcademyWarunki użytkowaniapolitykę prywatnościInformacja o plikach cookie
System cząstek z siłami
Do tej pory w tej sekcji skupialiśmy się tylko na kształtowaniu naszego kodu w celu zarządzania kolekcją cząsteczek w sposób zorientowany obiektowo. Może zauważyliście, a może nie, ale w trakcie tych czynności nieświadomie cofnęliśmy się o parę kroków w postępie w porównaniu z tym, co udało nam się osiągnąć we wcześniejszych rozdziałach. Przeanalizujmy konstruktor naszego obiektu Particle:
var Particle = function(position) {
this.acceleration = new PVector(0, 0.05);
this.velocity = new PVector(random(-1, 1), random(-1, 0));
this.position = new PVector(position.x, position.y);
this.timeToLive = 255.0;
};
A teraz spójrzmy na metodę
update()
:Particle.prototype.update = function(){
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.timeToLive -= 2;
};
Zauważcie, że nasze przyspieszenie jest stałe, nigdy nie ustawiane poza konstruktorem. Znacznie lepszym wyjściem byłoby trzymać się drugiej zasady dynamiki Newtona( ) i wdrążyć algorytm akumulacji sił nad którym przecież tak ciężko pracowaliśmy w dziale Siły.
Pierwszym krokiem jest dodanie metody
applyForce()
. (Pamiętajcie, że musimy stworzyć kopię PVectora
przed podzieleniem jego wartości przez masę.)Particle.prototype.applyForce = function(force) {
var f = force.get();
f.div(this.mass);
this.acceleration.add(f);
};
Mając to zaimplementowane, możemy dodać jedną dodatkową linię kodu aby zerować przyspieszenie na końcu
update()
.Particle.prototype.update = function() {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.acceleration.mult(0);
this.timeToLive -= 2.0;
};
I w ten sposób mamy obiekt typu
Particle
, na który możemy wpływać różnymi siłami. Pozostaje jedno pytanie - gdzie wywoływać funkcję applyForce()
? Gdzie w naszym kodzie znajduje się odpowiednie miejsce, w którym wpłyniemy na naszą cząsteczkę za pomocą siły? Prawda jest taka, że nie ma dobrego lub złego miejsca; wszystko zależy od funkcjonalności konkretnego programu i celów, jakie sobie postawiliśmy. Pomimo tego, możemy stworzyć ogólną sytuację, która prawdopodobnie będzie mieć zastosowanie w większości przypadków i stworzyć model dodawania sił do poszczególnych cząsteczek w systemie.Dodawanie wiatru
Postawmy sobie następujący cel: Dodanie siły do wszystkich cząsteczek przy każdym wywołaniu funkcji draw(). Zaczniemy od dodania prostej siły imitującej wiatr, która będzie wypychać cząstki w prawo:
var wind = new PVector(0.4, 0);
Powiedzieliśmy, że należy zawsze dodawać tą siłę w metodzie
draw()
, więc przyjrzyjmy się temu, jak nasza funkcja draw()
wygląda.draw = function() {
background(168, 255, 156);
particleSystem.addParticle();
particleSystem.run();
};
Zdaje się, że mamy mały problem.
applyForce()
to metoda wewnątrz obiektu Particle
, ale nie posiadamy żadnych referencji do poszczególnych cząsteczek, a jedynie do obiektu ParticleSystem
, za pomocą zmiennej particleSystem
.Skoro jednak chcemy, aby wszystkie cząsteczki otrzymały tą siłę, możemy zdecydować się na dodanie siły do systemu cząsteczek, a samemu systemowi pozwolić na dodanie sił do poszczególnych cząstek.
draw = function() {
background(168, 255, 156);
particleSystem.applyForce(wind);
particleSystem.addParticle();
particleSystem.run();
};
Oczywiście, jeżeli wywołamy nową funkcję obiektu
ParticleSystem
w draw()
, będziemy musieli ją napisać wewnątrz obiektu ParticleSystem
. Opiszmy zadanie, jakie ta funkcja musi wykonać: musi ona odebrać siłę w postaci PVectora
i dodać ją do wszystkich cząstek.W postaci kodu prezentuje się to następująco:
ParticleSystem.prototype.applyForce = function(f){
for(var i = 0; i < this.particles.length; i++){
this.particles[i].applyForce(f);
}
};
Pisanie tej funkcji może wydawać się śmieszne. To, co robimy można opisać jako “dodajmy siłę do systemu czasteczek po to, aby system mógł dodać tą siłe do wszystkich swoich cząsteczek.” Pomimo tego, jest to bardzo rozsądne podejście. W końcu to obiekt
ParticleSystem
odpowiada za zarządzanie cząsteczkami, tak więc jeżeli chcemy operować na cząsteczkach, musimy to robić za pomocą tego obiektu.Oto całość. Poeksperymentujcie z siłami wiatru aby zobaczyć, jak wpływa na ruch cząsteczek. Zauważcie także, że wiatr wpływa inaczej na cząsteczki z różną masą. Zastanówcie się, dlaczego tak się dzieje.
Dodawanie grawitacji
Dodajmy teraz bardziej złożoną siłę, grawitację, która różni się od wiatru, ponieważ zależy od masy obiektu, do którego jest przyłożona.
Przypomnijmy sobie równanie pozwalające na obliczenie siły grawitacji pomiędzy dwoma masami:
Pamiętajcie, że gdy modelujemy siłę grawitacji na Ziemi, siła wywierana przez Ziemię znacząco przekracza inne siły grawitacyjne, więc jedynym równaniem z jakim mamy do czynienia jest to pozwalające obliczyć siłę grawitacji pomiędzy Ziemią a danym obiektem. i są identyczne dla każdej cząstki, a (promień Ziemi) jest praktycznie taki sam(z racji, że promień ten jest taki duży, że wartość odległości cząsteczki od Ziemi można pominąć), więc zwykle uproszczamy te zapisując wynik jako g, stałą grawitacyjną Ziemi:
Teraz możemy zapisać siłę grawitacji jako wartość iloczynu stałej g oraz masy cząsteczek, pomnożonej przez wektor jednostkowy w kierunku siły(który zawsze będzie skierowany w dół):
W kodzie oznacza to, że będziemy musieli dodawać inną siłę grawitacji do różnych cząstek w zależności od ich masy. Jak możemy to zrobić? Nie możemy ponownie użyć istniejącej funkcji
applyForce
, ponieważ oczekuje ona tego samego rodzaju siły dla każdej cząsteczki. Możemy rozważyć przekazanie parametru, który będzie informował applyForce
, że musi pomnożyć wartość przez masę, ale zostawmy tą funkcję w spokoju i stwórzmy nową, applyGravity
, która oblicza siłę na postawie stałego globalnego wektora:// Stały wektor skierowany w dół, deklarowany na początku kodu programu
var gravity = new PVector(0, 0.2);
ParticleSystem.prototype.applyGravity = function() {
for(var i = 0; i < this.particles.length; i++) {
var particleG = gravity.get();
particleG.mult(this.particles[i].mass);
this.particles[i].applyForce(particleG);
}
};
Jeżeli wszystko zrobiliśmy poprawnie, wszystkie nasze cząsteczki powinny spadać w podobnym tempie jak w poniższej symulacji. Jest to spowodowane tym, że siła grawitacji jest oparta na mnożeniu przez masę, ale przyspieszenie wyliczane jest poprzez dzielenie przez masę, tak więc w końcowym rozrachunku masa nie ma żadnego wpływu na nasze cząstki. Może Wam się wydawać, że głupotą było wkładać w to tyle wysiłków, skoro nie daje to żadnego rezultatu, ale będzie to bardzo istotne gdy zaczniemy łączyć wiele różnych sił.
Dodanie odpychaczy
Co mamy zrobić, jeżeli chcemy rozwinąć ten przykład i dodać obiekt odpychający cząsteczki gdy się do niego zbliżą? Byłby to obiekt podobny do atraktora stworzonego wcześniej, tylko przesuwający obiekt w przeciwnym kierunku. Ponownie, tak jak w przypadku grawitacji, musimy obliczyć oddzielną siłę dla każdej cząstki, jednak w przypadku odpychacza różnica jest taka, że nasze obliczenia nie będą oparte na masie, tylko odległości. W przypadku grawitacji wszystkie wektory siły miały ten sam kierunek, jednak w przypadku odpychacza każdy wektor siły będzie miał inny kierunek:
Ponieważ obliczanie sił odpychacza jest trochę trudniejsze od obliczania grawitacji(a możemy w przyszłości chcieć wiele odpychaczy!), rozwiążemy ten problem poprzez wdrążeniu obiektu typu Repeller do naszego przykładu z prostym systemem cząstek oraz grawitacją. Będziemy potrzebować dwóch dużych dodatków w naszym kodzie:
- Obiekt typu
Repeller
(zadeklarowany, zainicjowany i wyświetlony). - Funkcję która przekazuje obiekt
Repeller
do obiektuParticleSystem
, dzięki czemu będziemy w stanie przekazać tą siłę do każdej cząsteczki.
var particleSystem = new ParticleSystem(new PVector(width/2, 50));
var repeller = new Repeller(width/2-20, height/2);
var gravity = new PVector(0, 0.1);
draw = function() {
background(214, 255, 171);
// Dodaj siłę grawitacji do wszystkich cząsteczek
particleSystem.applyForce(gravity);
particleSystem.applyRepeller(repeller);
repeller.display();
particleSystem.addParticle();
particleSystem.run();
};
Tworzenie widocznego obiektu typu
Repeller
jest łatwe: jest to kopia utworzonego przez nas wcześniej obiektu Attractor
:var Repeller = function(x, y) {
this.position = new PVector(x, y);
};
Repeller.prototype.display = function() {
stroke(255);
strokeWeight(2);
fill(127);
ellipse(this.position.x, this.position.y, 32, 32);
};
Trudniejszym pytaniem jest to, jak mamy napisać metodę
applyRepeller()
. Zamiast przekazywać PVector
do funkcji, tak jak robimy to z applyForce()
, będziemy zamiast tego przekazywać Repeller
do applyRepeller()
i to właśnie ta funkcja będzie odpowiedzialna za obliczanie siły pomiędzy odpychaczem a wszystkimi cząsteczkami:ParticleSystem.prototype.applyRepeller = function(r) {
for(var i = 0; i < this.particles.length; i++){
var p = this.particles[i];
var force = r.calculateRepelForce(p);
p.applyForce(force);
}
};
Najważniejszą różnicą jest to, że teraz nowa siła jest obliczana dla każdej cząstki, ponieważ, jak zauważyliśmy wcześniej, siła jest inna w zależności od właściwości poszczególnych cząstek w odniesieniu do odpychacza. Obliczamy tą siłę za pomocą funkcji
calculateRepelForce
, która jest przeciwieństwem funkcji calculateAttractionForce
z naszego obiektu Attractor
.Repeller.prototype.calculateRepelForce = function(p) {
// Oblicz kierunek siły
var dir = PVector.sub(this.position, p.position);
// Oblicz odległość pomiędzy obiektami
var d = dir.mag();
// Zadbaj, by odległość mieściła się w rozsądnych granicach
d = constrain(d, 1, 100);
// Siła odpychania jest odwrotnie proporcjonalna do odległości do kwadratu
var force = -1 * this.power/ (d * d);
// Oblicz wektor siły --> wartość * kierunek
dir.mult(force);
return dir;
};
Zauważcie, że przez cały proces dodawania odpychacza do środowiska, ani razu nie wspomnieliśmy o edycji samego obiektu
Particle
. Cząstka nie musi wiedzieć nic na temat swojego środowiska, musi po prostu być w stanie zarządzać swoim położeniem, prędkością i przyspieszeniem, oraz mieć możliwość odbierania zewnętrznych sił i działać zgodnie z nimi.Możemy teraz zobaczyć przykład w całej okazałości. Spróbuj zmieniać siłę działającą na cząsteczki - grawitację i odpychacze - i zobacz, jaki wpływ na nie mają:
Chcesz dołączyć do dyskusji?
Na razie brak głosów w dyskusji