If you're seeing this message, it means we're having trouble loading external resources on our website.

Jeżeli jesteś za filtrem sieci web, prosimy, upewnij się, że domeny *.kastatic.org i *.kasandbox.org są odblokowane.

Główna zawartość

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(F, with, vector, on top, equals, M, A, with, vector, on top) 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: F, start subscript, g, end subscript, with, vector, on top, equals, start fraction, G, m, start subscript, 1, end subscript, m, start subscript, 2, end subscript, divided by, vertical bar, vertical bar, r, vertical bar, vertical bar, squared, end fraction, r, with, hat, on top
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. G i m, start subscript, 1, end subscript są identyczne dla każdej cząstki, a r(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:
g, equals, start fraction, G, m, start subscript, 1, end subscript, divided by, vertical bar, vertical bar, r, vertical bar, vertical bar, squared, end fraction
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ół):
F, start subscript, g, end subscript, with, vector, on top, equals, g, m, start subscript, 2, end subscript, r, with, hat, on top
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:
Siła grawitacji: wszystkie wektory mają ten sam kierunek
Siła odpychacza: wszystkie wektory mają 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:
  1. Obiekt typu Repeller (zadeklarowany, zainicjowany i wyświetlony).
  2. Funkcję która przekazuje obiekt Repeller do obiektu ParticleSystem, 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ą: