In 2D, come posso trovare in modo efficiente l'oggetto più vicino a un punto?


35

Ho un motore di gioco considerevole e vorrei una funzione per trovare il più vicino di un elenco di punti.

Potrei semplicemente usare il teorema di Pitagora per trovare ogni distanza e scegliere quella minima, ma ciò richiede l'iterazione attraverso tutte.

Ho anche un sistema di collisione, in cui essenzialmente trasformo gli oggetti in oggetti più piccoli su una griglia più piccola (un po 'come una minimappa) e solo se esistono oggetti nella stessa griglia, posso controllare le collisioni. Potrei farlo, solo allargare la spaziatura della griglia per verificare la vicinanza. (Invece di controllare ogni singolo oggetto.) Tuttavia, ciò richiederebbe un'installazione aggiuntiva nella mia classe di base e ingombrerebbe l'oggetto già ingombra. Ne vale la pena?

C'è qualcosa di efficiente e preciso che potrei usare per rilevare quale oggetto è il più vicino, sulla base di un elenco di punti e dimensioni?


Memorizza le versioni quadrate delle posizioni xey in modo da poter eseguire il teorema di pitagora senza dover fare il costoso sqrt alla fine.
Jonathan Connell,

3
Questa è chiamata ricerca del vicino più vicino . Ci sono molte scritte su Internet al riguardo. La solita soluzione è utilizzare una sorta di albero di partizionamento dello spazio .
BlueRaja - Danny Pflughoeft,

Risposte:


38

Il problema con un quad / octree nelle ricerche sul vicino più vicino è che l'oggetto più vicino potrebbe trovarsi proprio attraverso la divisione tra i nodi. Per le collisioni, va bene, perché se non è nel nodo, non ci interessa. Ma considera questo esempio 2D con un quadrifoglio:

Esempio Quadtree

Qui, anche se l'elemento nero e l'elemento verde si trovano nello stesso nodo, l'elemento nero è il più vicino all'elemento blu. La risposta di ultifinitus può solo garantire al vicino più vicino solo ogni elemento nella tua struttura viene inserito nel nodo più piccolo possibile che potrebbe contenerlo, o in un nodo unico - questo porta a quadricre più inefficienti. (Nota che ci sono molti modi diversi per implementare una struttura che potrebbe essere chiamata quad / octree - implementazioni più rigide potrebbero funzionare meglio in questa applicazione.)

Un'opzione migliore sarebbe un kd-tree . Gli alberi Kd hanno un algoritmo di ricerca del vicino più vicino molto efficiente che puoi implementare e possono contenere qualsiasi numero di dimensioni (quindi dimensioni "k").

Un'animazione fantastica e istruttiva di Wikipedia: ricerca kd-tree vicino più vicino

Il problema più grande con l'utilizzo di kd-tree, se ricordo bene, è che sono più difficili da inserire / rimuovere elementi mantenendo l'equilibrio. Pertanto, consiglierei di usare un kd-tree per oggetti statici come case e alberi che è altamente bilanciato, e un altro che contiene giocatori e veicoli, che devono essere bilanciati regolarmente. Trova l'oggetto statico più vicino e l'oggetto mobile più vicino e confronta questi due.

Infine, i kd-tree sono relativamente semplici da implementare e sono sicuro che puoi trovare una moltitudine di librerie C ++ con loro. Da quello che ricordo, gli alberi a R sono molto più complicati e probabilmente eccessivi se tutto ciò che serve è una semplice ricerca del vicino più vicino.


1
Ottima risposta, piccolo dettaglio "solo una garanzia per il vicino più vicino, solo ogni elemento nella tua struttura è collocato nel nodo più piccolo possibile". 10.000.
Roy T.

1
Verissimo - suppongo che "solo" fosse una parola piuttosto aspra. Esistono sicuramente modi per convincere i quadricipiti nelle ricerche del vicino più vicino a seconda di come li implementi, ma se non li stai già utilizzando per altri motivi (come il rilevamento delle collisioni), starei con il kd-tree più ottimizzato.
dlras2,

Volevo notare che ho realizzato un'implementazione che si occupa del problema nero verde blu. Controlla il fondo.
clankill3r

18

sqrt() è monotonico, o preservare l'ordine, per argomenti non negativi quindi:

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

E viceversa.

Quindi, se vuoi solo confrontare due distanze ma non sei interessato ai loro valori reali, puoi semplicemente ritagliare il sqrt()passo dalla tua roba di Pitagora:

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

Non è efficiente come l'ottetto, ma è più facile da implementare e aumenta la velocità almeno un po '


1
Tale metrica è anche definita distanza euclidea quadrata .
Moooeeeep

10

Devi fare il partizionamento spaziale, in questo caso crei una struttura dati efficiente (di solito un ottetto). In questo caso ogni oggetto è all'interno di uno o più spazi (cubi) E se sai in quali spazi sei puoi cercare O (1) quali spazi sono i tuoi vicini.

In questo caso, l'oggetto più vicino può essere trovato eseguendo prima una iterazione su tutti gli oggetti nel proprio spazio, cercando quale sia il più vicino. Se non c'è nessuno lì puoi controllare i tuoi primi vicini, se nessuno è lì puoi controllare i loro vicini, ecc ...

In questo modo puoi facilmente trovare l'oggetto più vicino senza dover scorrere tutti gli oggetti nel tuo mondo. Come al solito questo aumento di velocità richiede un po 'di contabilità, ma è davvero utile per tutti i tipi di cose, quindi se hai un grande mondo vale sicuramente la pena implementare il partizionamento spaziale e un octree.

Come al solito, vedi anche l'articolo di Wikipedia: http://it.wikipedia.org/wiki/Octree


7
@ultifinitus Per aggiungere questo: se il tuo gioco è in 2D puoi usare QuadTrees invece di Octrees.
TravisG,


0

Ecco la mia implementazione Java per ottenere il più vicino da un quadTree. Si occupa del problema che dlras2 sta descrivendo:

inserisci qui la descrizione dell'immagine

Penso che l'operazione sia davvero efficiente. Si basa sulla distanza da un quad per evitare di cercare in quad oltre la corrente più vicina.

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

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 Continuo a pensare che sia meglio usare un kd-tree o qualcosa del genere, ma questo potrebbe aiutare le persone.
clankill3r

guarda anche questo: bl.ocks.org/llb4ll/8709363
clankill3r
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.