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

თუ ვებფილტრს იყენებთ, დარწმუნდით, რომ *.kastatic.org და *.kasandbox.org დომენები არ არის დაბლოკილი.

ძირითადი მასალა

კურსი: კომპიუტერული პროგრამირება > თემა 5

გაკვეთილი 8: ნაწილაკთა სისტემები

ნაწილაკთა სისტემები და ძალები

ამ სექციაში აქამდე ყურადღებას ვამახვილებდით ჩვენი კოდის სტრუქტურის ობიექტზე ორიენტირებული გზით შექმნაზე ნაწილაკების კოლექციის სამართავად. შეიძლება, შეამჩნიეთ, შეიძლება, ვერა, მაგრამ ამ პროცესის განმავლობაში ჩვენ უნებლიეთ წინა სექციებიდან უკან დავიხიეთ. მოდით, გამოვიკვლიოთ ჩვენი მარტივი 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;
};
და ახლა შევხედოთ update() მეთოდს:
Particle.prototype.update = function(){
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
  this.timeToLive -= 2;
};
აღვნიშნოთ, რომ ჩვენი აჩქარება მუდმივია, მას არასოდეს ენიჭება მნიშვნელობა კონსტრუქტორის გარეთ. გაცილებით უკეთესი იქნებოდა, მივყოლოდით ნიუტონის მეორე კანონს (F=MA) და შეგვეთავსებინა ძალის დაგროვების ალგორითმი, რომელზეც ბევრი ვიმუშავეთ ძალების სექციაში.
პირველი ნაბიჯია applyForce() მეთოდის დამატება (გახსოვდეთ, ჯერ PVector-ის ასლი უნდა შევქმნათ, სანამ მას გავყოფთ მასაზე).
Particle.prototype.applyForce = function(force) {
  var f = force.get();
  f.div(this.mass);
  this.acceleration.add(f);
};
ამის შემდეგ შეგვიძლია, დავამატოთ კოდის კიდევ ერთი ხაზი, რათა გავასუფთავოთ აჩქარება update()-ის ბოლოს.
Particle.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
  this.acceleration.mult(0);
  this.timeToLive -= 2{,}0;
};
და, შესაბამისად, გვაქვს Particle ობიექტი, რომელზეც შესაძლებელია ძალის გამოყენება. ახლა ვიფიქროთ, სად უნდა გამოვიძახოთ applyForce() ფუნქცია? სად არის კოდში ლოგიკური ძალის გამოყენება ნაწილაკზე? ამ კითხვებზე არ არსებობს ერთი სწორი და არასწორი პასუხი; გააჩნია კონკრეტულ ფუნქციონალსა და კონკრეტული პროგრამის მიზნებს. ამის მიუხედავად, შეგვიძლია, შევქმნათ სტანდარტული სიტუაცია, რომელიც, დიდი ალბათობით, მოერგება შემთხვევების უმრავლესობას, და შევქმნათ მოდელი სისტემაში ძალების ცალკეულ ნაწილაკებზე გამოსაყენებლად.

ქარის დამატება

განვიხილოთ შემდეგი მიზანი: გლობალურად გამოვიყენოთ ძალა ყოველ ჯერზე draw()-ს გამოყენებით ყველა ნაწილაკზე. დავიწყებთ მარტივი ქარის მსგავსი ძალით, რომელიც ნაწილაკს მარჯვენა მხარეს აძლევს ბიძგს:
var wind = new PVector(0,4, 0);
ჩვენ ვთქვით, რომ ის ყოველთვის უნდა გამოვიყენოთ, მაგალითად, draw()-ში, ასე რომ, შევხედოთ ჩვენს draw() ფუნქციას ახლანდელ მდგომარეობაში.
draw = function() {
  background(168, 255, 156);
  particleSystem.addParticle();
  particleSystem.run();
};
როგორც ჩანს, პატარა პრობლემა გვაქვს. applyForce() არის Particle ობიექტის შიგნით ჩაწერილი მეთოდი, მაგრამ ჩვენ არ გვაქვს არანაირი წვდომა ცალკეულ ნაწილაკებზე, წვდომა მხოლოდ ParticleSystem ობეიქტზე გვაქვს: ცვლადი particleSystem.
ვინაიდან გვინდა, რომ ყველა ნაწილაკმა მიიღოს ძალა, შეგვიძლია, ნაწილაკების სისტემაზე გამოვიყენოთ ის და ამ სისტემას მივანდოთ ყველა ნაწილაკზე მისი გამოყენება.
draw = function() {
  background(168, 255, 156);
  particleSystem.applyForce(wind);
  particleSystem.addParticle();
  particleSystem.run();
};
რა თქმა უნდა, თუ გამოვიძახებთ ახალ ფუნქციას ParticleSystem ობიექტზე draw()-ში, ეს ფუნქცია უნდა ჩავწეროთ ParticleSystem ობიექტში. აღვწეროთ სამუშაო, რაც ფუნქციამ უნდა შეასრულოს: მან უნდა მიიღოს ძალა PVector-ის სახით და გამოიყენოს ის ყველა ნაწილაკზე.
ახლა კოდში:
ParticleSystem.prototype.applyForce = function(f){
  for(var i = 0; i < this.particles.length; i++){
    this.particles[i].applyForce(f);
  }
};
სულელურადაც კი ჟღერს ამ ფუნქციის დაწერა. ჩვენ ვამბობთ: „გამოვიყენოთ ძალა ნაწილაკების სისტემაზე, რათა ამ სისტემამ შეძლოს ამ ძალის გამოყენება თითოეულ ნაწილაკზე“. თუმცა, ეს საკმაოდ ლოგიკურია. ParticleSystem ობიექტი ხელმძღვანელობს ნაწილაკებს, ასე რომ, თუ ნაწილაკებთან ლაპარაკი გვინდა, მათ მისი ხელმძღვანელის მეშვეობით უნდა ველაპარაკოთ.
აი, ყველაფერი ერთად. სცადეთ ქარის ძალა და ნახეთ, როგორი გავლენა აქვს ნაწილაკების მოძრაობაზე, დააკვირდით, რომ სხვადასხვა მასის მქონე ნაწილაკები სხვადასხვანაირად რეაგირებენ. დაფიქრდით, რატომ ხდება ასე.

გრავიტაციის დამატება

ახლა გამოვიყენოთ უფრო რთული ძალა, გრავიტაცია, რომელიც განსხვავდება ქარისგან, რადგან ის სხვადასხვანაირი შეიძლება იყოს იმ ობიექტის მასის მიხედვით, რომელზეც მას ვიყენებთ.
გავიხსენოთ გრავიტაციის ძალის გამოთვლის განტოლება ორ მასას შორის: Fg=Gm1m2||r||2r^
გახსოვდეთ, რომ, როცა ძალის დედამიწაზე მოდელირებას ვაკეთებთ, ძალა, რომლითაც დედამიწა მოქმედებს, დიდად აჭარბებს ყველა სხვა გრავიტაციულ ძალას, ასე რომ, ერთადერთი განტოლება, რომელთანაც გვაქვს შეხება, არის დედამიწასა და ობიექტს შორის არსებული გრავიტაციის ძალის გამოთვლა. G და m1 არის ერთი და იგივე ყველა ნაწილაკისთვის, ხოლო r(რადიუსი დედამიწიდან) პრაქტიკულად იგივეა (რადგან დედამიწის რადიუსი ბევრად უფრო დიდია იმ მანძილთან შედარებით, რითაც ნაწილაკები შორდებიან მას), ასე რომ, მათ, ძირითადად, ვამარტივებთ g-მდე, რაც დედამიწის გრავიტაციის მუდმივაა:
g=Gm1||r||2
ახლა გრავიტაციის ძალა არის რაღაც მუდმივა g, გამრავლებული ნაწილაკების მასაზე, გამრავლებული ერთეულოვან ვექტორზე ძალის მიმართულებით (რომელიც ყოველთვის იქნება მიმართული ქვემოთ):
Fg=gm2r^
კოდის ენაზე ეს ნიშნავს, რომ მოგვიწევს თითოეულ ნაწილაკზე მისი მასის მიხედვით სხვადასხვა გრავიტაციის გამოყენება. როგორ გავაკეთოთ ეს? ჩვენ არ შეგვიძლია applyForce ფუნქციის ხელახლა გამოყენება, რადგან ის თითოეული ნაწილაკისათვის იმავე ძალას ელის. შეიძლება, განვიხილოთ მისთვის ისეთი პარამეტრის გადაცემა, რომელიც აუხსნის applyForce-ს, რომ მასაზე გაამრავლოს, მაგრამ, მოდით, ამ ფუნქციას თავი დავანებოთ და შევქმნათ ახალი ფუნქცია, applyGravity, რომელიც გამოითვლის ძალას გლობალური მუდმივი ვექტორის მიხედვით:
// მუდმივად ქვემოთ მიმართული ვექტორი, რომელიც გამოცხადებულია დასაწყისში
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);
    }
};
ახლა, თუ ეს სწორად გვაქვს გაკეთებული, ჩვენი ყველა ნაწილაკი ერთითა და იმავე ტემპით უნდა დაეშვას ქვემოთ მოცემულ სიმულაციაში. ეს იმიტომ, რომ გრავიტაციის ძალა დაფუძნებულია მასაზე გამრავლებაზე, მაგრამ აჩქარება ეფუძნება მასაზე გაყოფას, ასე რომ, საბოლოოდ მასას არანაირი ეფექტი არ აქვს. შეიძლება, სულელურად ჟღერს ამდენი რამის გაკეთება შედეგის ქონის გარეშე, მაგრამ ეს მნიშვნელოვანი გახდება მას შემდეგ, რაც რამდენიმე სხვადასხვა ძალის გაერთიანებას დავიწყებთ.

განმზიდავების დამატება

ერთი ნაბიჯით წინ ხომ არ წავიდეთ ამ მაგალითით და დავამატოთ განმზიდავი ობიექტი, რომელიც ნაწილაკებს განიზიდავს, როდესაც ისინი ახლოვდებიან? ეს ანალოგიური იქნებოდა მიმზიდველი ობიექტისა, რომელიც ადრე შევქმენით, უბრალოდ, საპირისპირო მიმართულებით მიაწვება. კიდევ ერთხელ, როგორც გრავიტაციის შემთხვევაში, თითოეული ნაწილაკისათვის უნდა გამოვთვალოთ განსხვავებული ძალა, მაგრამ განმზიდავის შემთხვევაში განსხვავება ისაა, რომ გამოთვლა არ ეფუძნება მასას - ის ეფუძნება მანძილს. გრავიტაციის შემთხვევაში ძალის ჩვენს ყველა ვექტორს ჰქონდა ერთი და იგივე მიმართულება, მაგრამ განმზიდავის შემთხვევაში ძალის ყველა ვექტორს ექნება განსხვავებული მიმართულება:
გრავიტაციის ძალა: ყველა ვექტორს ერთი მიმართულება აქვს
განმზიდავის ძალა: მიმართულების ყველა ვექტორი განსხვავებულია
ვინაიდან განმზიდავის ძალის გამოთვლა ოდნავ უფრო რთულია, ვიდრე გრავიტაციის გამოთვლა (და საბოლოოდ შეიძლება, ბევრი განმზიდავი მოგვინდეს!), ამ ამოცანას ამოვხსნით ჩვენი მარტივი ნაწილაკების სისტემისა და გრავიტაციის მაგალითში ახალი Repeller (განმზიდავი) ობიექტის შემოღებით. დაგვჭირდება ორი დიდი დამატება ჩვენს კოდში:
  1. Repeller ობიექტი (გამოცხადებული, ინიციალიზებული და ეკრანზე ნაჩვენები).
  2. ფუნქცია, რომელიც გადასცემს Repeller ობიექტს ParticleSystem-ში, რათა მან შეძლოს ამ ძალის გამოყენება თითოეულ ნაწილაკის ობიექტზე.
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);

  // Apply gravity force to all Particles
  particleSystem.applyForce(gravity);
  particleSystem.applyRepeller(repeller);
  repeller.display();
  particleSystem.addParticle();
  particleSystem.run();
};
ეკრანზე ჩვენებადი Repeller ობიექტის გაკეთება იოლია; ის არის 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);
};
უფრო რთული კითხვა შემდეგია: როგორ უნდა დავწეროთ applyRepeller() მეთოდი? PVector-ის ფუნქციაში ისე გადაცემის ნაცვლად, როგორც applyForce()-ის შემთხვევაში ვაკეთებთ, applyRepeller()-ს გადავცემთ Repeller ობიექტს და ამ ფუნქციას ვთხოვთ, შეასრულოს განმზიდავსა და ყველა ნაწილაკს შორის ძალის გამოთვლის სამუშაო:
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);
  }
};
აქ დიდი განსხვავება ის არის, რომ ახალი ძალა გამოითვლება თითოეული ნაწილაკისათვის, რადგან, როგორც ადრე ვნახეთ, ძალა განსხვავდება თითოეული ნაწილაკის თვისებების მიხედვით განმზიდავის მიმართ. ამ ძალას გამოვთვლით calculateRepelForce ფუნქციის გამოყენებით, რომელიც არის calculateAttractionForce ფუნქციის შებრუნებული ჩვენი Attractor-ებიდან (მიმზიდველებიდან).
Repeller.prototype.calculateRepelForce = function(p) {
  // გამოთვალეთ ვექტორი ობიექტებს შორის ძალისთვის
  var dir = PVector.sub(this.position, p.position); 
  // გამოვთვალოთ მანძილი ობიექტებს შორის
  var dist = dir.mag();
  // მანძილი უნდა იყოს ზომიერ დიაპაზონში
  dist = constrain(dist, 1, 100);    
  // გამოვთვალოთ განზიდვის ძალა,
  // უკუპროპორციული მანძილის კვადრატისა
  var force = -1 * this.power/ (dist * dist);     
  // მიმართულების ვექტორს გავუკეთოთ ნორმალიზაცია
  // (მანძილის შესახებ ინფორმაციას უგულებელვყოფთ)
  dir.normalize();
  // გამოვთვალოთ ძალის ვექტორი: მიმართულება გამრავლებული სიდიდეზე
  dir.mult(force);                                  
  return dir;
};
აღვნიშნოთ, რომ გარემოში განმზიდავის დამატების მთელი პროცესის განმავლობაში თვითონ Particle ობიექტის რედაქტირება არც ერთხელ არ განგვიხილავს. არ არის აუცილებელი, ნაწილაკმა იცოდეს დეტალები მისი გარემოს შესახებ; მან, უბრალოდ, უნდა მართოს თავისი ადგილმდებარეობა, სიჩქარე და აჩქარება, აგრეთვე უნდა ჰქონდეს გარე ძალის მიღებისა და შესაბამისად მოქმედების უნარი.
ასე რომ, ახლა შეგვიძლია, ამ მაგალითს შევხედოთ მთლიანობაში. სცადეთ ნაწილაკებზე მოქმედი ძალების - გრავიტაციისა და განმზიდავის - სიდიდის შეცვლა და ნახეთ, როგორ შეცვლის ეს მათ:

გსურთ, შეუერთდეთ დისკუსიას?

პოსტები ჯერ არ არის.
გესმით ინგლისური? დააწკაპუნეთ აქ და გაეცანით განხილვას ხანის აკადემიის ინგლისურენოვან გვერდზე.