Có cách nào để tăng hiệu quả kiểm tra va chạm của một hệ thống n đối tượng không?


9

Tôi đang làm một trò chơi bao gồm nhiều đối tượng trên màn hình, một trong số đó là người chơi. Tôi cần phải biết những đối tượng đang va chạm mỗi lần lặp.

Tôi đã làm một cái gì đó như thế này:

for (o in objects)
{
   o.stuff();
   for (other in objects)
      if (collision(o, other))
          doStuff();

   bla.draw();
}

Cái này có O (n ^ 2), mà tôi nói là xấu. Làm thế nào để tôi làm điều này hiệu quả hơn, thậm chí là có thể? Tôi đang làm điều này trong Javascript và n thường sẽ thấp hơn 30, liệu có vấn đề gì không?


3
Bạn đã thử chạy mã để xem nó hoạt động như thế nào chưa?
thedaian

Không, tôi không biết, tôi chỉ cho rằng nó tệ vì chữ O (n ^ 2).
jcora

1
Chỉ có 30 đối tượng? Tôi đã đề nghị phân vùng không gian, nhưng nó sẽ không có kết quả chỉ với 30 đối tượng. Có một số tối ưu hóa nhỏ mà những người khác đã chỉ ra, nhưng chúng đều là những tối ưu hóa nhỏ trên quy mô mà bạn đang nói đến.
John McDonald

Câu trả lời:


16

Chỉ với tối đa 30 đối tượng, bạn không cần tối ưu hóa nhiều thứ khác ngoài việc không kiểm tra hai cặp giống nhau hơn một lần trên mỗi khung. Mà mẫu mã dưới đây sẽ bao gồm. Nhưng nếu bạn thấy thú vị trong các tối ưu hóa khác nhau mà một công cụ vật lý sẽ sử dụng thì hãy tiếp tục đọc qua phần còn lại của bài đăng này.

Những gì bạn sẽ cần là một triển khai phân vùng không gian , chẳng hạn như Octree (cho trò chơi 3D) hoặc Quadtree (cho trò chơi 2D). Các phân vùng này thế giới thành các phần phụ, và sau đó mỗi phần phụ được phân vùng hơn nữa trong cùng một trang viên, cho đến khi chúng được chia thành một kích thước tối thiểu. Điều này cho phép bạn kiểm tra rất nhanh những vật thể khác trong cùng khu vực với thế giới khác, điều này hạn chế số lượng va chạm mà bạn phải kiểm tra.

Ngoài phân vùng không gian, tôi khuyên bạn nên tạo AABB ( hộp giới hạn căn chỉnh trục ) cho mỗi đối tượng vật lý của bạn. Điều này cho phép bạn kiểm tra AABB của một đối tượng so với đối tượng khác, nhanh hơn nhiều so với kiểm tra per-poly chi tiết giữa các đối tượng.

Điều này có thể tiến thêm một bước nữa đối với các đối tượng vật lý phức tạp hoặc lớn, trong đó bạn có thể tự phân chia lưới vật lý, tạo cho mỗi hình dạng AABB của chính nó mà bạn chỉ có thể kiểm tra nếu hai AABB của đối tượng bị chồng chéo.

Hầu hết các động cơ vật lý sẽ hủy kích hoạt mô phỏng vật lý hoạt động trên cơ thể vật lý một khi chúng nghỉ ngơi. Khi một cơ thể vật lý bị vô hiệu hóa, nó chỉ cần kiểm tra va chạm với AABB của nó từng khung và nếu có bất cứ điều gì va chạm với AABB thì nó sẽ kích hoạt lại và thực hiện kiểm tra va chạm chi tiết hơn. Điều này giữ cho thời gian mô phỏng xuống.

Ngoài ra, nhiều động cơ vật lý sử dụng 'đảo mô phỏng', đó là nơi một nhóm các cơ thể vật lý gần nhau được nhóm lại với nhau. Nếu tất cả mọi thứ trong đảo mô phỏng đều dừng lại thì đảo mô phỏng tự hủy. Lợi ích của đảo mô phỏng là tất cả các cơ thể bên trong nó có thể ngừng kiểm tra va chạm một khi hòn đảo không hoạt động, và việc kiểm tra duy nhất mỗi khung là xem có thứ gì vào AABB của đảo không. Chỉ khi một cái gì đó đi vào AABB của hòn đảo, mỗi cơ quan trong đảo sẽ cần kiểm tra va chạm. Đảo mô phỏng cũng kích hoạt lại nếu bất kỳ cơ thể nào bên trong nó bắt đầu tự di chuyển trở lại. Nếu một cơ thể di chuyển đủ xa từ trung tâm của nhóm, nó sẽ bị xóa khỏi đảo.

Cuối cùng, bạn còn lại một cái gì đó như thế này (bằng mã giả):

// Go through each leaf node in the octree. This could be more efficient
// by keeping a list of leaf nodes with objects in it.
for ( node in octreeLeafNodes )
{
    // We only need to check for collision if more than one object
    // or island is in the bounds of this octree node.
    if ( node.numAABBsInBounds > 1)
    {
        for ( int i = 0; i < AABBNodes.size(); ++i )
        {
           // Using i+1 here allows us to skip duplicate checks between AABBS
           // e.g (If there are 5 bodies, and i = 0, we only check i against
           //      indexes 1,2,3,4. Once i = 1, we only check i against indexes
           //      2,3,4)
           for ( int j = i + 1; j < AABBNodes.size(); ++j )
           {
               if ( AABBOverlaps( AABBNodes[i], AABBNodes[j] ) )
               {
                   // If the AABB we checked against was a simulation island
                   // then we now check against the nodes in the simulation island

                   // Once you find overlaps between two actual object AABBs
                   // you can now check sub-nodes with each object, if you went
                   // that far in optimizing physics meshes.
               {
           }
        }
    }
}

Tôi cũng khuyên bạn không nên có quá nhiều vòng lặp trong các vòng lặp như thế này, mẫu ở trên chỉ để bạn có ý tưởng, tôi sẽ chia nó thành nhiều chức năng cung cấp cho bạn chức năng giống như những gì được hiển thị ở trên.

Ngoài ra, đảm bảo không thay đổi bộ chứa AABBNodes trong khi lặp qua nó, vì điều đó có thể có nghĩa là kiểm tra va chạm bị bỏ lỡ. Điều này nghe có vẻ như lẽ thường, nhưng bạn sẽ ngạc nhiên khi mọi thứ phản ứng với va chạm dễ dàng gây ra những thay đổi mà bạn không lường trước được. Ví dụ: nếu một vụ va chạm khiến một trong các đối tượng va chạm thay đổi vị trí đủ để loại bỏ chúng khỏi AABB của nút Octree mà bạn đang kiểm tra thì nó có thể thay đổi vùng chứa đó. Để giải quyết vấn đề này, tôi khuyên bạn nên giữ một danh sách tất cả các sự kiện va chạm xảy ra trong quá trình kiểm tra, và sau đó sau khi tất cả các kiểm tra hoàn tất, hãy chạy qua danh sách và gửi bất kỳ sự kiện va chạm nào.


4
Câu trả lời rất phù hợp với các kỹ thuật tốt đẹp và hữu ích để mở mang đầu óc cho các phương pháp hiện có. +1
Valkea

Nếu tôi cần loại bỏ vật thể va chạm thì sao? Tôi có thể thay đổi container không? Tôi có nghĩa là loại bỏ nó khỏi container vì tôi không cần đối tượng nữa vì nó bị "phá hủy". Tôi cần thêm một vòng lặp để chạy qua các sự kiện va chạm nếu tôi không xóa nó trong quá trình phát hiện va chạm.
newguy ngày

Loại bỏ đối tượng va chạm là tốt nhưng tôi khuyên bạn nên chờ đợi để làm điều đó cho đến khi vượt qua được va chạm trên toàn bộ mô phỏng. Thông thường, bạn chỉ gắn cờ các đối tượng cần xóa hoặc tạo danh sách các đối tượng cần xóa, và sau khi hoàn thành mô phỏng va chạm, bạn áp dụng các thay đổi đó.
Nic Foster

4

Ví dụ của bạn kiểm tra từng cặp đối tượng nhiều lần.

Hãy lấy một ví dụ rất đơn giản với một mảng chứa 0,1,2,3

Với mã của bạn, bạn nhận được điều này:

  • Tại vòng 0, bạn kiểm tra 1, 2 và 3
  • Tại vòng 1, bạn kiểm tra 0, 2 và 3 ===> (0-1 đã được kiểm tra)
  • Tại vòng 2, bạn kiểm tra 0, 1 và 3 ===> (0-2 / 1-2 đã được kiểm tra)
  • Tại vòng 3, bạn kiểm tra 0, 1 và 2 ===> (0-3 / 1-3 / 2-3 đã được kiểm tra)

Bây giờ hãy xem đoạn mã sau:

for(i=0;i<=objects.length;i++)
{
    objects[i].stuff();

    for(j=i+1;j<=objects.length;j++)
    {
        if (collision(objects[i], objects[j]))
        doStuff();
    }

    bla.draw();
}

Nếu chúng ta sử dụng mảng chứa 0,1,2,3 một lần nữa, chúng ta có hành vi sau:

  • Tại vòng 0, bạn kiểm tra 1, 2, 3
  • Tại vòng 1, bạn kiểm tra lại 2, 3
  • Tại vòng 2, bạn kiểm tra 3
  • Ở vòng 3 bạn kiểm tra không có gì

Với thuật toán thứ hai, chúng tôi đã có 6 bài kiểm tra va chạm trong khi bài kiểm tra trước yêu cầu 12 bài kiểm tra va chạm.


Thuật toán này thực hiện N(N-1)/2so sánh vẫn là hiệu năng O (N ^ 2).
Kai

1
Vâng, với 30 đối tượng theo yêu cầu, có nghĩa là thử nghiệm va chạm với 4670 so với 870 ... nó có thể giống với quan điểm của bạn, nhưng không phải từ quan điểm của tôi. Hơn nữa, giải pháp được đưa ra trong câu trả lời khác là cùng một thuật toán :)
Valkea

1
@Valkea: Chà, một phần của nó là. :)
Nic Foster

@NicFoster: vâng, bạn đúng;) Tôi đã nói đúng về thử nghiệm va chạm giữa các đối tượng được chọn, không phải về phần phân vùng của thuật toán rõ ràng là một bổ sung rất có giá trị mà tôi thậm chí không nghĩ đến để thêm vào ví dụ của mình khi Tôi đã viết nó.
Valkea

Đây có được gọi là khấu hao? Dù sao cũng cảm ơn!
jcora

3

Thiết kế thuật toán của bạn xung quanh nhu cầu của bạn, nhưng giữ cho chi tiết ẩn được gói gọn. Ngay cả trong Javascript, các khái niệm OOP cơ bản được áp dụng.

Đối với N =~ 30, O(N*N)không phải là một mối quan tâm, và tìm kiếm tuyến tính của bạn có thể sẽ nhanh như bất kỳ thay thế ngoài kia. Nhưng bạn không muốn giả định mã cứng vào mã của mình. Trong mã giả, bạn sẽ có một giao diện

interface itemContainer { 
    add(BoundingBox);
    remove(BoundingBox);
    BoundingBox[] getIntersections();
}

Điều đó mô tả những gì danh sách của bạn có thể làm. Sau đó, bạn có thể viết một lớp ArrayContainer thực hiện giao diện này. Trong Javascript, mã sẽ trông như thế này:

function ArrayContainer() { ... } // this uses an array to store my objects
ArrayContainer.prototype.add = function(box) { ... };
ArrayContainer.prototype.remove = function(box) { ... };
ArrayContainer.prototype.getIntersections = function() { ... };

function QuadTreeContainer { ... } // this uses a quadtree to store my objects
... and implement in the add/remove/getIntersections for QuadTreeContainer too

Và đây là mã ví dụ tạo ra 300 hộp giới hạn và nhận được tất cả các giao điểm. Nếu bạn đã triển khai ArrayContainer và QuadTreeContainer một cách chính xác, điều duy nhất bạn cần thay đổi trong mã của mình là thay đổi var allMyObjects = new ArrayContainer()thành var allMyObjects = QuadTreeContainer().

var r = Math.random;
var allMyObjects = new ArrayContainer();
for(var i=0; i<300; i++)
    allMyObjects.add(new BoundingBox(r(), r()));
var intersections = allMyObjects.getIntersections();

Tôi đã tiếp tục và hoàn thành việc triển khai cho ArrayContainer tiêu chuẩn ở đây:

http://jsfiddle.net/SKkN5/1/


Lưu ý: Câu trả lời này được thúc đẩy bởi khiếu nại của Bane rằng cơ sở mã của anh ta đang trở nên quá lớn, lộn xộn và khó quản lý. Mặc dù nó không bổ sung nhiều vào cuộc thảo luận về việc sử dụng Mảng so với Cây, nhưng tôi hy vọng đó là một câu trả lời có liên quan như cách cụ thể anh ta có thể tổ chức mã của mình tốt hơn.
Jimmy

2

Bạn cũng nên xem xét các loại đối tượng hơn có thể va chạm hợp lý.

Ví dụ, người chơi có thể cần được kiểm tra va chạm với mọi thứ trừ đạn của chính mình. Tuy nhiên, kẻ thù có thể chỉ cần kiểm tra chống lại đạn của người chơi. Đạn gần như chắc chắn không cần phải va chạm với nhau.

Để thực hiện điều này một cách hiệu quả, có lẽ bạn muốn giữ các danh sách riêng biệt của từng đối tượng, mỗi loại đối tượng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.