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

Nếu bạn đang sử dụng bộ lọc web, vui lòng kiểm tra lại xem bộ lọc có chặn hai tên miền *.kastatic.org*.kasandbox.org hay không.

Nội dung chính

Hệ thống hạt kết hợp với lực

Trong các nội dung của bài học về hệ thống hạt, chúng ta đã thực hành sử dụng các kỹ thuật lập trình hướng đối tượng để quản lý một tập hợp các hạt. Trong đó, chúng ta đã lặp lại một vài bước từ những bài học trước của chương mô phỏng tự nhiên. Ta cùng xem lại đoạn mã ta đã có, bắt đầu từ lệnh khởi tạo kiểu đối tượng Particle đơn giản của chúng ta:
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;
};
Bây giờ, hãy cùng nhìn vào phương thức update()
Particle.prototype.update = function(){
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
  this.timeToLive -= 2;
};
Ở đây, ta đang sử dụng gia tốc không đổi (không thay đổi sau khi được khởi tạo). Tuy nhiên, điều này có thể không phản ánh chính xác chuyển động của hạt trong thực tế, do hạt còn chịu tác động của nhiều lực. Do đó, ta có thể cải tiến câu lệnh bằng cách sử dụng định luật II Newton (F=MA) và áp dụng thuật toán tích lũy lực mà chúng ta đã được học rất kĩ trong bài Lực.
Đầu tiên, ta sẽ thêm phương thức applyForce() (Lưu ý: chúng ta cần tạo một bản sao của PVector trước khi chia đối tượng này cho khối lượng).
Particle.prototype.applyForce = function(force) {
  var f = force.get();
  f.div(this.mass);
  this.acceleration.add(f);
};
Sau khi hoàn thành xong, chúng ta có thể thêm vào một dòng lệnh nữa trong phương thức update() để xóa gia tốc ở cuối mỗi lần cập nhật.
Particle.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
  this.acceleration.mult(0);
  this.timeToLive -= 2.0;
};
Như vậy, ta đã tạo được một đối tượng Particle và có thể tác động lực lên đối tượng này. Câu hỏi đặt ra bây giờ là ta nên đặt hàm applyForce() ở đâu? Vị trí nào trong đoạn mã là thích hợp để ta biểu diễn quá trình tác động lực lên một hạt? Thực tế là ta không có đáp án chính xác cho câu hỏi này. Câu trả lời phụ thuộc vào chức năng và mục tiêu cụ thể của chương trình. Tuy nhiên, ta nên tạo ra một tình huống chung mà ở đó lực được áp dụng cho từng hạt trong hệ thống, phù hợp với hầu hết các trường hợp sử dụng về sau.

Thêm lực gió

Giả sử ta áp dụng một lực cho tất cả các hạt bằng hàm draw(), ví dụ như lực gió đẩy các hạt về phía bên phải:
var wind = new PVector(0.4, 0);
Do chúng ta áp dụng lực lên tất cả các hạt thông qua mỗi lần chạy hàm draw(), hãy cùng xem lại hàm draw() hiện tại của chúng ta.
draw = function() {
  background(168, 255, 156);
  particleSystem.addParticle();
  particleSystem.run();
};
Ở đây, ta gặp phải một vấn đề, đó là phương thức applyForce() được viết bên trong kiểu đối tượng Particle, nhưng chúng ta không có bất kỳ tham chiếu nào đến các hạt particle đơn lẻ, mà chỉ có tham chiếu đến toàn bộ hệ thống hạt ParticleSystem thông qua biến particleSystem.
Do ta đang muốn tác động lực lên tất cả các hạt, nên ta có thể tận dụng luôn hệ thống hạt đã tạo từ trước. Cụ thể, ta có thể áp dụng lực vào toàn bộ hệ thống hạt (ParticleSystem) và để hệ thống này quản lý việc áp dụng lực đó cho tất cả các hạt riêng lẻ.
draw = function() {
  background(168, 255, 156);
  particleSystem.applyForce(wind);
  particleSystem.addParticle();
  particleSystem.run();
};
Khi chúng ta muốn gọi một phương thức mới trên đối tượng ParticleSystem trong hàm draw(), ta phải khai báo phương thức đó trên đối tượng ParticleSystem trước. Cụ thể, ta sẽ mô tả điều mà phương thức này cần thực hiện, đó là nhận một lực dưới dạng PVector và tác động lực đó lên tất cả các hạt.
Ta có đoạn mã dưới đây:
ParticleSystem.prototype.applyForce = function(f){
  for(var i = 0; i < this.particles.length; i++){
    this.particles[i].applyForce(f);
  }
};
Mặc dù có vẻ khá kỳ quặc khi ta viết một hàm chỉ để yêu cầu "tác động một lực vào một hệ thống hạt để hệ thống tác động lại lực đó lên tất cá các hạt đơn lẻ". Tuy nhiên, đây lại là cách tiếp cận hợp lý, vì thực tế, đối tượng ParticleSystem chịu trách nhiệm quản lý các hạt, nên nếu chúng ta muốn tác động đến các hạt, ta nên tác động thông qua lớp quản lý để quá trình xử lý được diễn ra một cách nhất quán và hiệu quả.
Kết hợp tất cả lại, ta có đoạn mã dưới đây. Hãy thử nghiệm với lực gió xem lực này tác động thế nào đến sự di chuyển của các hạt, cũng như chú ý xem các hạt có khối lượng khác nhau thì sẽ phản ứng khác nhau thế nào với lực gió.

Thêm trọng lực

Bây giờ, chúng ta sẽ thử áp dụng một lực phức tạp hơn, đó là trọng lực. Trọng lực khác với lực gió vì trọng lực sẽ thay đổi theo khối lượng của vật mà nó tác dụng lên.
Hãy nhớ lại công thức tổng quát tính lực hấp dẫn giữa hai vật: Fg=Gm1m2|r|2r^
Lưu ý rằng khi chúng ta mô phỏng lực hấp dẫn, lực hấp dẫn giữa Trái Đất và một vật sẽ lớn hơn các lực hấp dẫn khác tác dụng lên vật đó. Vì vậy, phương trình duy nhất chúng ta sẽ xử lý là phương trình tính lực hấp dẫn giữa Trái Đất và vật thể. Trong đó, với hệ thống hạt ở đây, Gm1 là như nhau đối với mọi hạt và r (bán kính Trái Đất) về cơ bản là không đổi (vì bán kính Trái Đất lớn hơn rất nhiều so với khoảng cách giữa mỗi hạt với mặt đất). Khi đó, phương trình lực hấp dẫn thường được đơn giản hóa chỉ còn lại là g, hằng số hấp dẫn trên Trái Đất:
g=Gm1|r|2
Tiếp theo, trọng lực sẽ bằng hằng số g, nhân với khối lượng của các hạt, nhân thêm với một vector đơn vị theo hướng của lực (luôn luôn hướng xuống):
Fg=gm2r^
Khi biểu diễn lại bằng mã lập trình, ta cần mô phỏng sao cho mỗi hạt chịu tác dụng của một lực hấp dẫn riêng tùy thuộc vào khối lượng của hạt. Như vậy, chúng ta không thể sử dụng hàm applyForce hiện có, vì hàm này sẽ tác động cùng một lực cho mỗi hạt. Thay vào đó, chúng ta thêm một tham số để yêu cầu hàm applyForce nhân thêm với khối lượng. Tuy nhiên, để tối ưu hơn, ta nên tạo ra hẳn một hàm mới, chẳng hạn như applyGravity, để tính lực hấp dẫn cho mỗi hạt dựa trên dựa trên khối lượng và vectơ lực hấp dẫn tổng quát.
// Mot vecto tong quat huong thang dung xuong duoi, khai bao o dau
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);
    }
};
Lúc này, nếu chúng ta đã làm đúng, tất cả các hạt sẽ rơi với cùng một tốc độ trong trình mô phỏng bên dưới. Đó là vì lực hấp dẫn được tính bằng cách nhân hằng số với khối lượng, nhưng gia tốc lại được tính bằng cách chia cho khối lượng, nên kết quả là khối lượng của hạt không ảnh hưởng đến tốc độ rơi của hạt. Tuy nhiên, hãy lưu ý rằng cách làm trên có vai trò rất quan trọng khi chúng ta kết hợp thêm các lực khác nữa.

Thêm lực đẩy

Giả sử ta tiếp tục nâng cấp trình mô phỏng hệ thống hạt và bổ sung thêm một vật thể, sao cho khi các hạt đến gần vật thể này, các hạt sẽ bị đẩy ra xa. Vật thể này sẽ đóng vai trò là đối tượng đẩy (repeller), tương tự với đối tượng hút (attractor) chúng ta đã tạo ra ở bài trước, chỉ khác là lực đẩy sẽ theo hướng ngược lại. Như vậy, giống như trọng lực, chúng ta phải tính toán từng lực đẩy khác nhau lên mỗi hạt. Tuy nhiên, thay vì thay đổi theo khối lượng như trọng lực, lực đẩy sẽ thay đổi theo khoảng cách. Đối với trọng lực, tất cả các vectơ lực của chúng ta đều có cùng hướng, nhưng đối với lực đẩy, các vectơ lực sẽ có hướng khác nhau:
Trọng lực: tất cả các vectơ đều có cùng hướng
Lực đẩy: tất cả các vectơ đều có hướng khác nhau
Do phép tính lực đẩy có phần phức tạp hơn phép tính trọng lực và chúng ta có thể muốn tạo ra nhiều đối tượng đẩy khác nhau, chúng ta nên tạo ta một kiểu đối tượng Repeller (đối tượng đẩy) và tích hợp vào trong hệ thống hạt đã được áp dụng trọng lực trước đó. Chúng ta sẽ cần thêm vào đoạn mã hai thành phần chính như sau:
  1. Một kiểu đối tượng Repeller (được khai báo, khởi tạo và hiển thị).
  2. Một hàm chuyển kiểu đối tượng Repeller vào ParticleSystem để từ đó tác động lực lên từng đối tượng hạt.
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();
};
Để tạo và hiển thị một đối tượng đẩy Repeller, ta làm tương tự như với đối tượng hút Attractor trong bài trước.
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);
};
Tiếp theo, ta đến với một nhiệm vụ khó hơn làm tạo phương thức applyRepeller() để áp dụng lực đẩy cho hệ thống hạt. Ở đây, thay vì truyền một PVector vào hàm như chúng ta đã làm với phương thức applyForce() của trọng lực, chúng ta sẽ chuyển một đối tượng Repeller vào phương thức applyRepeller() và yêu cầu thực hiện việc tính toán lực đẩy giữa đối tượng đẩy và các hạt:
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);
  }
};
Tiếp theo, ta cần lưu ý một điểm quan trọng là lực đẩy tác dụng lên mỗi hạt là khác nhau, vì như đã đề cập trước đó, lực đẩy phụ thuộc vào mối tương quan giữa đối tượng đẩy và các đặc điểm của mỗi hạt. Chúng ta sẽ tính lực đẩy lên mỗi hạt bằng cách sử dụng hàm calculateRepelForce. Đây là hàm ngược của hàm calculateAttractionForce từ các đối tượng hút Attractor trong bài trước ta đã học.
Repeller.prototype.calculateRepelForce = function(p) {
  // Calculate vector for force between objects
  var dir = PVector.sub(this.position, p.position); 
  // Calculate distance between objects
  var dist = dir.mag();
  // Keep distance within a reasonable range
  dist = constrain(dist, 1, 100);    
  // Calculate repelling force,
  // inversely proportional to distance squared
  var force = -1 * this.power/ (dist * dist);     
  // Normalize direction vector
  // (discarding distance information)
  dir.normalize();
  // Calculate force vector: direction * magnitude
  dir.mult(force);                                  
  return dir;
};
Bạn có thể thấy rằng trong quá trình thêm đối tượng đẩy vào trong chương trình, ta chỉ thêm và chỉnh sửa các câu lệnh liên quan đến đối tượng đẩy, chứ không chỉnh sửa hạt Particle sẵn có. Đó là vì bản chất, mỗi hạt không có vai trò điều khiển môi trường xung quanh hạt đó. Mỗi hạt chỉ cần làm chủ được vị trí, vận tốc và gia tốc của chính hạt đó, cũng như có khả năng tiếp nhận ngoại lực và phản ứng lại ngoại lực đó.
Sau khi kết hợp lại toàn bộ các lực chúng ta phân tích ở trên, ta có trình mô phỏng dưới đây. Hãy thử thay đổi độ lớn của những lực tác động lên các hạt (trọng lực và lực đẩy) và quan sát sự thay đổi của các hạt:

Tham gia cuộc thảo luận?

Chưa có bài đăng nào.
Bạn có hiểu Tiếng Anh không? Bấm vào đây để thấy thêm các thảo luận trên trang Khan Academy Tiếng Anh.