Trong 2D, làm thế nào để tôi tìm thấy vật thể gần nhất một điểm một cách hiệu quả?


35

Tôi có một công cụ trò chơi khá lớn và tôi muốn có một tính năng để tìm danh sách điểm gần nhất.

Tôi chỉ có thể sử dụng định lý Pythagore để tìm từng khoảng cách và chọn khoảng cách tối thiểu, nhưng điều đó đòi hỏi phải lặp đi lặp lại qua tất cả chúng.

Tôi cũng có một hệ thống va chạm, trong đó về cơ bản tôi biến các vật thể thành các vật thể nhỏ hơn trên một lưới nhỏ hơn (giống như một bản đồ nhỏ) và chỉ khi các vật thể tồn tại trong cùng một không gian lưới thì tôi mới kiểm tra va chạm. Tôi có thể làm điều đó, chỉ làm cho khoảng cách lưới lớn hơn để kiểm tra sự gần gũi. (Thay vì kiểm tra từng đối tượng.) Tuy nhiên, điều đó sẽ thiết lập bổ sung trong lớp cơ sở của tôi và làm lộn xộn đối tượng đã lộn xộn. Nó có đáng không?

Có thứ gì đó hiệu quả và chính xác mà tôi có thể sử dụng để phát hiện đối tượng nào gần nhất không, dựa trên danh sách các điểm và kích cỡ?


Lưu trữ các phiên bản bình phương của các vị trí x và y để bạn có thể thực hiện định lý pythagoras mà không phải thực hiện sqrt đắt tiền ở cuối.
Jonathan Connell

3
Đây được gọi là một tìm kiếm hàng xóm gần nhất . Có rất nhiều bài viết trên internet về nó. Giải pháp thông thường là sử dụng một số loại cây phân vùng không gian .
BlueRaja - Daniel Pflughoeft

Câu trả lời:


38

Vấn đề với một quad / octree trong các tìm kiếm lân cận gần nhất là đối tượng gần nhất có thể đang ngồi ngay trên sự phân chia giữa các nút. Đối với các va chạm, điều này là ổn, bởi vì nếu nó không nằm trong nút, chúng tôi không quan tâm đến nó. Nhưng hãy xem xét ví dụ 2D này với một góc phần tư:

Ví dụ tứ phương

Ở đây, mặc dù vật phẩm màu đen và vật phẩm màu xanh lá cây nằm trong cùng một nút, vật phẩm màu đen gần nhất với vật phẩm màu xanh. Câu trả lời của ultifinitus chỉ có thể đảm bảo cho người hàng xóm gần nhất chỉ mỗi vật phẩm trong cây của bạn được đặt trong nút nhỏ nhất có thể chứa nó hoặc trong một nút duy nhất - điều này dẫn đến tứ giác kém hiệu quả hơn. (Lưu ý rằng có nhiều cách khác nhau để triển khai cấu trúc có thể được gọi là quad / octree - việc triển khai nghiêm ngặt hơn có thể hoạt động tốt hơn trong ứng dụng này.)

Một lựa chọn tốt hơn sẽ là một cây kd . Cây Kd có thuật toán tìm kiếm lân cận gần nhất rất hiệu quả mà bạn có thể triển khai và có thể chứa bất kỳ số lượng kích thước nào (do đó kích thước "k".)

Một hình ảnh động tuyệt vời và nhiều thông tin từ Wikipedia: cây tìm hàng xóm gần nhất

Vấn đề lớn nhất khi sử dụng cây kd, nếu tôi nhớ chính xác, là chúng khó khăn hơn trong việc chèn / xóa các mục khỏi trong khi duy trì sự cân bằng. Do đó, tôi khuyên bạn nên sử dụng một cây kd cho các đối tượng tĩnh như nhà và cây có độ cân bằng cao và một cây khác chứa người chơi và phương tiện cần cân bằng thường xuyên. Tìm đối tượng tĩnh gần nhất và đối tượng di động gần nhất và so sánh hai đối tượng đó.

Cuối cùng, kd-tree tương đối đơn giản để thực hiện và tôi chắc chắn rằng bạn có thể tìm thấy vô số thư viện C ++ cùng với chúng. Từ những gì tôi nhớ, cây R phức tạp hơn nhiều và có thể quá mức nếu tất cả những gì bạn cần là một tìm kiếm hàng xóm gần nhất đơn giản.


1
Câu trả lời tuyệt vời, chi tiết nhỏ "chỉ bảo đảm cho người hàng xóm gần nhất chỉ mỗi mục trong cây của bạn được đặt trong nút nhỏ nhất có thể" Tôi có nghĩa là trong câu trả lời của tôi lặp đi lặp lại trên tất cả các mục trong cùng một nút và hàng xóm, vì vậy bạn lặp lại trên 10 thay vì 10.000.
Roy T.

1
Rất đúng - tôi cho rằng "chỉ" là một từ khá khắc nghiệt. Chắc chắn có nhiều cách để dỗ tứ giác vào các tìm kiếm lân cận gần nhất tùy thuộc vào cách bạn triển khai chúng, nhưng nếu bạn không sử dụng chúng vì những lý do khác (như phát hiện va chạm,) tôi sẽ gắn bó với cây kd được tối ưu hóa hơn.
dlras2

Tôi muốn lưu ý rằng tôi đã thực hiện một vấn đề liên quan đến vấn đề xanh đen. Kiểm tra phía dưới.
clankill3r

18

sqrt() là đơn điệu, hoặc bảo toàn trật tự, cho các đối số không phủ định vì vậy:

sqrt(x) < sqrt(y) iff x < y

Và ngược lại.

Vì vậy, nếu bạn chỉ muốn so sánh hai khoảng cách nhưng không quan tâm đến giá trị thực của chúng, bạn có thể cắt bỏ sqrt()bước đi từ Pythagoras của bạn:

pseudoDistanceB = (A.x - B.x + (A.y - B.y
pseudoDistanceC = (A.x - C.x + (A.y - C.y
if (pseudoDistanceB < pseudoDistanceC)
{
    A is closest to B!
}
else
{
    A is closest to C!
}

Nó không hiệu quả như cây oct, nhưng nó dễ thực hiện hơn và tăng tốc ít nhất một chút


1
Số liệu đó cũng được gọi là khoảng cách euclide bình phương .
moooeeeep

10

Bạn phải thực hiện phân vùng không gian, trong trường hợp này bạn tạo một cấu trúc dữ liệu hiệu quả (thường là một octree). Trong trường hợp này, mỗi đối tượng nằm trong một hoặc nhiều khoảng trắng (hình khối) Và nếu bạn biết bạn đang ở trong không gian nào, bạn có thể tra cứu O (1) không gian nào là hàng xóm của bạn.

Trong trường hợp này, đối tượng gần nhất có thể được tìm thấy bằng cách lặp đầu tiên trên tất cả các đối tượng trong không gian của riêng bạn, tìm kiếm đối tượng gần nhất ở đó. Nếu không có ai ở đó, bạn có thể kiểm tra hàng xóm đầu tiên của mình, nếu không có ai ở đó, bạn có thể kiểm tra hàng xóm của họ, v.v ...

Bằng cách này bạn có thể dễ dàng tìm thấy đối tượng gần nhất mà không cần phải lặp qua tất cả các đối tượng trong thế giới của bạn. Như thường lệ, việc tăng tốc độ này đòi hỏi một chút kế toán, nhưng nó thực sự hữu ích cho tất cả các loại công cụ vì vậy nếu bạn có một thế giới rộng lớn thì chắc chắn nên thực hiện phân vùng không gian và một quãng tám.

Như thường lệ, xem thêm bài viết trên wikipedia: http://en.wikipedia.org/wiki/Octree


7
@ultifinitus Để thêm vào điều này: Nếu trò chơi của bạn là 2D, bạn có thể sử dụng QuadTrees thay vì Octrees.
TravisG


0

Đây là cách triển khai java của tôi để lấy cái gần nhất từ ​​một quadTree. Nó liên quan đến vấn đề dlras2 đang mô tả:

nhập mô tả hình ảnh ở đây

Tôi nghĩ rằng hoạt động là thực sự hiệu quả. Nó dựa trên khoảng cách đến một hình tứ giác để tránh tìm kiếm theo hình tứ giác cách xa hơn so với hiện tại gần nhất.

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

public T getClosest(float x, float y) {

    Closest closest = new Closest();
    getClosest(x, y, closest);

    return closest.item;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

protected void getClosest(float x, float y, Closest closestInfo) {


    if (hasQuads) {

        // we have no starting point yet
        // so get one
        if (closestInfo.item == null) {
            // check all 4 cause there could be a empty one
            for (int i = 0; i < 4; i++) {
                quads[i].getClosest(x, y, closestInfo);
                if (closestInfo.item != null) {
                    // now we have a starting point
                    getClosest(x, y, closestInfo);
                    return;
                }

            }
        }
        else {

            // we have a item set as closest
            // we should check if this quad is
            // closer then the current closest distance
            // let's start with the closest from index

            int closestIndex = getIndex(x, y);

            float d = quads[closestIndex].bounds.distToPointSQ(x, y);

            if (d < closestInfo.dist) {
                quads[closestIndex].getClosest(x, y, closestInfo);
            }

            // check the others
            for (int i = 0; i < 4; i++) {
                if (i == closestIndex) continue;

                d = quads[i].bounds.distToPointSQ(x, y);

                if (d < closestInfo.dist) {
                    quads[i].getClosest(x, y, closestInfo);
                }

            }

        }

    }
    else {

        for (int i = 0; i < items.size(); i++) {

            T item = items.get(i);

            float dist = distSQ(x, y, getXY.x(item), getXY.y(item));

            if (dist < closestInfo.dist) {
                closestInfo.dist = dist;
                closestInfo.item = item;
                closestInfo.tree = this;
            }

        }
    }

}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


class Closest {

    QuadTree<T> tree;
    T item;
    float dist = Float.MAX_VALUE;

}

ps Tôi vẫn nghĩ tốt hơn là sử dụng cây kd hoặc thứ gì đó, nhưng điều này có thể giúp mọi người.
clankill3r

cũng nhìn vào đây: bl.ocks.org/llb4ll/8709363
clankill3r
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.