Як у 2D я можу ефективно знайти найближчий об’єкт до точки?


35

У мене є великий ігровий движок, і я хотів би функцію пошуку найближчого до списку пунктів.

Я міг би просто використати теорему Піфагора, щоб знайти кожну відстань і вибрати мінімальну, але для цього потрібно повторити їх.

У мене також є система зіткнень, де по суті я перетворюю об’єкти на більш дрібні об'єкти на меншій сітці (на зразок міні-карти), і лише якщо об'єкти існують в одній і тій же сітці, я перевіряю наявність колізій. Я міг би це зробити, лише збільшивши відстань у сітці, щоб перевірити наявність герметичності. (Замість перевірки кожного окремого об'єкта.) Однак це займе додаткове налаштування в моєму базовому класі і захаращує вже захаращений об’єкт. Чи варто того?

Чи є щось ефективне та точне, що я міг би використати для визначення того, який об’єкт є найближчим на основі списку точок та розмірів?


Зберігайте квадратичні версії позицій x і y, щоб ви могли робити теорему піфагора без необхідності робити дорогий sqrt в кінці.
Джонатан Коннелл

3
Це називається пошук найближчого сусіда . В Інтернеті про це багато написано. Звичайне рішення - використовувати якесь дерево з розділенням простору .
BlueRaja - Danny Pflughoeft

Відповіді:


38

Проблема з квадратом / октрі в пошуках найближчого сусіда полягає в тому, що найближчий об'єкт може сидіти прямо навпроти поділу між вузлами. Для зіткнень це нормально, тому що якщо він не знаходиться у вузлі, нас це не хвилює. Але розглянемо цей двовимірний приклад з квадратом:

Приклад квадратури

Тут, незважаючи на те, що чорний і зелений елемент знаходяться в одному вузлі, чорний елемент найближчий до синього. Відповідь ultifinitus може гарантувати лише найближчому сусідові, тільки кожен предмет на вашому дереві розміщується в найменшому можливому вузлі, який міг би його містити, або в унікальний вузол - це призводить до більш неефективних четвертин. (Зауважте, що існує багато різних способів реалізації структури, яку можна назвати квадрокод / ​​октре - більш суворі реалізації можуть працювати краще в цій програмі.)

Кращим варіантом буде kd-дерево . Kd-дерева мають дуже ефективний алгоритм пошуку найближчого сусіда , який ви можете реалізувати, і він може містити будь-яку кількість вимірів (отже, "k" розміри.)

Чудова та інформативна анімація з Вікіпедії: пошук найближчого сусіда kd-tree

Найбільша проблема використання kd-дерев, якщо я правильно пам'ятаю, полягає в тому, що їм складніше вставляти / вилучати елементи із збереження балансу. Тому я б рекомендував використовувати одне kd-дерево для статичних об'єктів, таких як будинки та дерева, яке є високо збалансованим, а інше, яке містить плеєри та транспортні засоби, що потребує регулярного балансування. Знайдіть найближчий статичний об’єкт та найближчий мобільний об’єкт та порівняйте ці два.

Нарешті, kd-дерева досить просто здійснити, і я впевнений, що ви можете знайти безліч бібліотек C ++ з ними. Як я пам’ятаю, R-дерева набагато складніші, і, ймовірно, надмірні, якщо все, що вам потрібно, - це простий пошук найближчого сусіда.


1
Чудова відповідь, невелика деталь: "єдина гарантія, що найближчий сусід лише кожен предмет у вашому дереві розміщений у найменшому можливому вузлі". Я мав на увазі у своїй відповіді ітерацію над усіма елементами в одних і тих самих і сусідських вузлах, тож ви заміряєте більше 10 замість 10 000.
Рой Т.

1
Дуже вірно - я вважаю, що "тільки" було досить суворим словом. Однозначно є способи прив язати чотиривірші до пошуку найближчих сусідів залежно від того, як ви їх реалізуєте, але якщо ви вже не використовуєте їх з інших причин (наприклад, виявлення зіткнень), я б дотримувався більш оптимізованого дерева kd.
dlras2

Я хотів зазначити, що я зробив реалізацію, яка стосується проблеми чорно-зеленого синього. Перевірте дно.
clankill3r

18

sqrt() є монотонним або збереженням порядку для негативних аргументів:

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

І навпаки.

Тож якщо ви хочете лише порівняти дві відстані, але вас не цікавлять їхні фактичні значення, ви можете просто вирізати sqrt()-шарок із ваших матеріалів Піфагора:

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!
}

Це не настільки ефективно, як річ з oct-tree, але це легше здійснити, і прискорити швидкість хоч трохи


1
Цей показник також називають евклідовою дистанцією у квадраті .
moooeeeep

10

Вам потрібно зробити просторовий розподіл, в цьому випадку ви робите ефективну структуру даних (зазвичай це октрис). У цьому випадку кожен об'єкт знаходиться в одному або декількох просторах (кубиках). І якщо ви знаєте, в яких просторах ви можете шукати O (1), які простори є вашими сусідами.

У цьому випадку найближчий об’єкт можна знайти, попередньо переглянувши всі об’єкти у вашому власному просторі, шукаючи, який із них найближчий. Якщо там немає нікого, ви можете перевірити своїх перших сусідів, якщо немає нікого, ви можете перевірити їх сусідів тощо.

Таким чином ви зможете легко знайти найближчий об’єкт, не потребуючи перегляду всіх об'єктів у вашому світі. Як правило, для цього збільшення швидкості потрібно трохи вести бухгалтерський облік, але це дійсно корисно для будь-яких речей, тому якщо у вас великий світ, то, безумовно, варто застосувати просторовий розподіл та октрис.

Як завжди, дивіться також статтю у Вікіпедії: http://en.wikipedia.org/wiki/Octree


7
@ultifinitus Щоб додати до цього: Якщо ваша гра 2D, ви можете використовувати QuadTrees замість Octrees.
TravisG

1

Можливо, спробуйте впорядкувати свої просторові дані в RTree, який схожий на btree для речей у просторі та дозволяє запити на зразок "найближчих N сусідів" тощо ... http://en.wikipedia.org/wiki/Rtree


0

Ось моя реалізація Java, щоб отримати найближчий з quadTree. Він стосується проблеми dlras2 описує:

введіть тут опис зображення

Я думаю, що операція дійсно ефективна. Він заснований на відстані до квадратика, щоб уникнути пошуку в квадратиках далі від поточного найближчого.

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

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 Я все ще думаю, що краще використовувати kd-дерево або щось подібне, але це може допомогти людям.
clankill3r

також дивіться на це: bl.ocks.org/llb4ll/8709363
clankill3r
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.