Come posizionare casualmente entità che non si sovrappongono?


11

Sto creando un ambiente generato casualmente per un gioco che sto sviluppando. Sto usando OpenGLe codificando Java.

Sto cercando di posizionare casualmente alberi nel mio mondo (per creare una foresta), ma non voglio che i modelli si sovrappongano (cosa che accade quando due alberi sono posizionati troppo vicini l'uno all'altro). Ecco una foto di ciò di cui sto parlando:

inserisci qui la descrizione dell'immagine

Posso fornire più codice se necessario, ma ecco i frammenti essenziali. Sto conservando i miei oggetti in un ArrayListcon List<Entity> entities = new ArrayList<Entity>();. Aggiungo quindi a tale elenco utilizzando:

Random random = new Random();
for (int i = 0; i < 500; i++) {
    entities.add(new Entity(tree, new Vector3f(random.nextFloat() * 800 - 400, 
    0, random.nextFloat() * -600), 0, random.nextFloat() * 360, 0, 3, 3, 3);
}

dove ciascuno Entitysegue la seguente sintassi:

new Entity(modelName, positionVector(x, y, z), rotX, rotY, rotZ, scaleX, scaleY, scaleZ);

rotXè la rotazione lungo l'asse x, ed scaleXè la scala nella direzione x, ecc.

Si può vedere che sto mettendo questi alberi in modo casuale con random.nextFloat()le xe zposizioni, e che delimita il campo in modo appariranno gli alberi nella posizione desiderata. Tuttavia, vorrei in qualche modo controllare queste posizioni, in modo che se provi a posizionare un albero troppo vicino a un albero precedentemente posizionato, ricalcolerà una nuova posizione casuale. Stavo pensando che ogni albero Entitypotrebbe avere un altro campo, come treeTrunkGirth, e se un albero viene posizionato in lontananza tra la posizione di un altro albero ed è treeTrunkGirth, allora ricalcolerà una nuova posizione. C'è un modo per raggiungere questo obiettivo?

Sono felice di aggiungere ulteriori frammenti di codice e dettagli se necessario.


3
Il campionamento del disco di Poisson dovrebbe funzionare. Non so se sia meglio per questo e non lo abbia mai realmente implementato / usato, ma sembra almeno un buon inizio. Controlla questo articolo: devmag.org.za/2009/05/03/poisson-disk-sampling
Mars

@Mars Wow, quel link è molto utile, grazie. Vedrò cosa posso fare e forse tornerò con una mia risposta.
Wcarhart,

@Pikalek Sì, penso che la domanda che hai collegato sia un duplicato. Userei semplicemente il piano xz come area per la "mappa stellare", come nell'altra domanda?
Wcarhart,

Sì, usa il piano xz nel tuo caso. Inoltre, utilizzare treeTrunkGirthinvece di una costante per determinare la distanza minima per posizionare un albero se è necessario variare.
Pikalek,

@Pikalek Se aggiungi tutto ciò in una risposta, lo selezionerò come il migliore. Grazie per l'aiuto.
Wcarhart,

Risposte:


15

Una distribuzione di campionamento di Poisson-Disk ti permetterà di selezionare punti casuali a una distanza minima. La tua situazione è simile a questa domanda , ma poiché i tuoi alberi non sono punti idealizzati dovrai cambiare il controllo della distanza come segue: la distanza tra un nuovo albero potenziale e un albero esistente, deve essere inferiore alla somma dei loro raggi .

L'algoritmo di Bridson può risolvere efficacemente il problema in O (n), ma può essere un po 'complicato modificarlo per distanze variabili. Se i tuoi parametri sono bassi e / o stai precompilando il tuo terreno, anche una soluzione di forza bruta può esserti utile. Ecco un po 'di codice di esempio che bruta forza il problema controllando ogni potenziale nuovo posizionamento dell'albero rispetto a tutti gli alberi precedentemente posizionati:

public static class SimpleTree{
    float x;
    float z;
    float r;

    public SimpleTree(Random rng, float xMax, float zMax, float rMin, float rMax){
        x = rng.nextFloat() * xMax;
        z = rng.nextFloat() * zMax;
        r = rng.nextFloat() * (rMax-rMin) + rMin;
    }
}

private static ArrayList<SimpleTree> buildTreeList(float xMax, float zMax, 
        float rMin, float rMax, int maxAttempts, Random rng) {
    ArrayList<SimpleTree> result = new ArrayList<>();

    SimpleTree currentTree = new SimpleTree(rng, xMax, zMax, rMin, rMax);
    result.add(currentTree);

    boolean done = false;
    while(!done){
        int attemptCount = 0;
        boolean placedTree = false;
        Point nextPoint = new Point();
        SimpleTree nextTree = null;
        while(attemptCount < maxAttempts && !placedTree){
            attemptCount++;
            nextTree = new SimpleTree(rng, xMax, zMax, rMin, rMax);
            if(!tooClose(nextTree, result)){
                placedTree = true;
            }
        }
        if(placedTree){
            result.add(nextTree);
        }
        else{
            done = true;
        }
    }

    return result;
}

private static boolean tooClose(SimpleTree tree, ArrayList<SimpleTree> treeList) {
    for(SimpleTree otherTree : treeList) {
        float xDiff = tree.x - otherTree.x;
        float zDiff = tree.z - otherTree.z;

        float dist = (float)Math.sqrt((xDiff * xDiff) + (zDiff * zDiff));
        if(dist < tree.r + otherTree.r){
            return true;
        }
    }        
    return false;
}

Con i seguenti parametri:

 maxAttempts = 500;
 width = 300;
 height = 200;
 minSize = 2;
 maxSize = 15;

Sono stato in grado di posizionare e renderizzare casualmente tra 400-450 alberi in meno di un secondo. Ecco un esempio: inserisci qui la descrizione dell'immagine


Questo utilizza il campionamento del disco di Poisson?
Wcarhart,

Sì, ho modificato per renderlo esplicito.
Pikalek,

Prova a usare math.pow su tree.r + other tree.r, 2, invece di math.sqrt, sqrt è generalmente più lento di pow
Ferrybig,

1
@Ferrybig Il confronto delle distanze al quadrato è più veloce, ma ciò non cambia il fatto che si tratta di un algoritmo di forza bruta e sarà comunque O (n ^ 2). Se è necessaria una soluzione più veloce, utilizzare l'algoritmo di Bridson. Inoltre, l'utilizzo Math.pow(x,2)non è necessariamente migliore / diverso dall'uso x*xcome discusso qui .
Pikalek,

1
Ho avuto un problema simile con il terreno e assicurando una discreta diffusione e nessuna sovrapposizione. Avevo effettivamente fatto una variazione, nella mia versione gli alberi / pennello sono sparsi casualmente nell'area del terreno. Quindi eseguo una funzione post per verificare le distanze di ogni elemento l'una contro l'altra, dove erano troppo vicine le ho separate. Ciò, tuttavia, potrebbe influire su altri alberi nell'area. L'ho ripetuto fino a quando non ho avuto collisioni. è più lento, ma ciò che ho avuto anche come bonus erano cose come radure (non ovunque è coperto!) e la densità degli alberi sembrava più "interessante".
ErnieDingo,
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.