Come determinare quali celle in una griglia si intersecano con un dato triangolo?


10

Attualmente sto scrivendo una simulazione AI 2D, ma non sono del tutto sicuro di come verificare se la posizione di un agente rientra nel campo visivo di un altro.

Attualmente, il mio partizionamento mondiale è un semplice partizionamento spazio-cella (una griglia). Voglio usare un triangolo per rappresentare il campo visivo, ma come posso calcolare le celle che si intersecano con il triangolo?

Simile a questa immagine: inserisci qui la descrizione dell'immagine

Le aree rosse sono le celle che voglio calcolare, controllando se il triangolo interseca quelle celle.

Grazie in anticipo.

MODIFICARE:

Solo per aggiungere alla confusione (o forse anche renderlo più facile). Ogni cella ha un vettore minimo e massimo in cui il minimo è l'angolo in basso a sinistra e il massimo è l'angolo in alto a destra.


Non potresti dividere le celle in triangoli e testare triangolo-triangolo?
Il comunista Duck il

Le celle non sono poligoni fisici, solo una rappresentazione spaziale e sfrutta i tempi di accesso O (1) di un array. Se avessi un cerchio di quartiere attorno all'agente, per approssimare le celle, potrei creare un AABB usando il raggio del cerchio e trovare facilmente le intersezioni. Il problema qui è che vogliono solo le celle che sono davanti a me. Sono sicuro che ci sono alcune equazioni geometriche da dare una mano, non riesco a pensare a nessuna per la mia vita.
Ray Dey,

Non mi piace nessuna delle risposte qui; tuttavia, questa domanda ha delle risposte davvero buone: gamedev.stackexchange.com/q/81267/63053
Andrew

Risposte:


6

Calcola i tre angoli del tuo triangolo fov, ruotali in modo che siano rivolti nella direzione corretta e così via, quindi esegui una di:

1) fai un test point-in-triangle per tutti i potenziali target

2) calcola il riquadro di delimitazione di questo triangolo ed esegui un test point-in-triangle per tutti i potenziali bersagli nelle celle di questo riquadro di delimitazione - questo sarà un codice molto semplice per il debug

Un approccio simile è usare un quadrifoglio piuttosto che una griglia e fare le intersezioni su quello. Se l'accesso alla piastrella O (1) ti sta accelerando, allora solo testare tutte le celle nei limiti del triangolo fov per il triangolo dovrebbe essere così veloce da essere istantaneo. Dal momento che stai osservando altre opzioni, presumo che non lo sia e che O (1) stia effettivamente sostenendo un enorme costo per la perdita della cache mentre si blocca la cache. Potresti ovviamente guardare le istruzioni di precaricamento per annotare la passeggiata del rettangolo di selezione con ...

3) "rasterizza" questo triangolo e controlla le celle che "dipinge" - probabilmente il codice più efficiente, ma forse solo marginalmente, dato che speculerei tutto dominato dai tempi di cache cache e dipende da quanto sono complesse le tue celle e da quanto occupate loro sono.

Un approccio alternativo consiste nel rendere il FOV su una bitmap fuori dallo schermo e quindi leggere il valore in pixel per ciascuno dei tuoi oggetti; non puoi "unmix paint" ma con un numero limitato di oggetti e scegliendo con cura la tua vernice puoi dedurre chi ha visto chi. Questo approccio è simile a quanti giochi risolvono ciò su cui l'utente ha fatto clic: disegnano la scena fuori dallo schermo usando colori solidi per le aree colpite. Le GPU sono molto veloci al riempimento del triangolo ...


+1 grazie per questo, ho usato un rettangolo di selezione per il triangolo per selezionare rapidamente le celle appropriate e ho usato un test point-in-triangle per determinare quali membri di quelle celle si trovano all'interno del campo visivo :)
Ray Dey,

3

La soluzione canonica nei renderer software (che devono fare questo algoritmo esatto ogni volta che rasterizzano un triangolo) è, credo, scansionare il triangolo una fila di pixel alla volta. I bordi sinistro e destro di ogni riga e calcolati camminando lungo i lati del triangolo usando Bresenham , quindi riempi la riga tra di loro.

Sto analizzando molti dettagli, ma questa è l'idea di base. Se cerchi "rendering software" e "rasterizzazione triangolare", probabilmente troverai altri dettagli. Questo è un problema ben risolto. La tua scheda grafica lo fa milioni di volte in una cornice.

Se vuoi una soluzione più specifica per roguelike, ecco come ho implementato FOV nel mio. Sembra funzionare abbastanza rapidamente. È essenzialmente un semplice lanciatore di ombre, che lavora all'ottante alla volta, scansionando verso l'esterno dal giocatore.


1
È un approccio piuttosto interessante.
Notabene,

2

Sto usando una variante dell'algoritmo scanline per risolvere esattamente lo stesso problema. Ho iniziato ordinando i tre punti del triangolo in base alla loro altezza. Fondamentalmente quindi controllo se due bordi sono sul lato sinistro o destro. Per il lato con due bordi, è necessario contrassegnare la riga in cui si modifica il bordo che delimita le righe. Per il lato con un bordo, puoi sempre usarlo.

Quindi, per ogni riga, so quali due bordi lo delimitano e posso calcolare i limiti superiore e inferiore in direzione x. Sembra piuttosto complicato, ma si riduce a poche righe di codice. Assicurati di maneggiare la custodia speciale in cui un bordo è completamente orizzontale!


2

Che ne dici di mantenere un intervallo di colonne per ogni riga che si trova nel triangolo? Quello che puoi fare è impostare la colonna min e max per ogni riga in cui si trova ogni punto e dove ogni linea di triangolo attraversa una linea di separazione di riga orizzontale.

public class Point
{
    public float X;
    public float Y;
    public Point(float x, float y) { this.X = x; this.Y = y; }
}

public class Line
{
    float ROW_SIZE = 100f;
    float COL_SIZE = 100f;

    public Point P1, P2; // P1 has the lowest Y
    public float Slope, Intercept; // set in constructor
    public bool IsVertical;

    public Line(Point p1, Point p2)
    {
        if (p1.Y > p2.Y) { P1 = p2; P2 = p1; } // p1 has lowest Y
        else { P1 = p1; P2 = p2; }
        IsVertical = (p1.X == p2.X);
        if (!IsVertical) { Slope = (p2.Y - p1.Y) / (p2.X - p1.X); Intercept = p1.Y - Slope * p1.X; }
    }

    public void ExpandRanges(int[] minCol, int[] maxCol)
    {
        // start out at row, col where P1 is, which has lowest Y
        int row = (int)(P1.Y / ROW_SIZE);
        int col = (int)(P1.X / COL_SIZE);
        int lastRow = (int)(P2.Y / ROW_SIZE);
        int lastCol = (int)(P2.X / COL_SIZE);

        // expand row to include P1
        minCol[row] = Math.Min(col, minCol[row]); maxCol[row] = Math.Max(col, maxCol[row]);

        // now we find where our line intercepts each horizontal line up to P2
        float currY = P1.Y;
        float currX = P1.X;
        while (row < lastRow)
        {
            row = row + 1;
            float rowY = row * ROW_SIZE;
            float diffY = rowY - currY;
            float diffX = IsVertical ? 0f : diffY / Slope;
            currY = currY + diffY;
            currX = currX + diffX;
            col = (int)(currX / COL_SIZE);

            // expand rows above and below dividing line to include point
            minCol[row - 1] = Math.Min(col, minCol[row - 1]);
            maxCol[row - 1] = Math.Max(col, maxCol[row - 1]);
            minCol[row] = Math.Min(col, minCol[row]);
            maxCol[row] = Math.Max(col, maxCol[row]);
        }

        // expand last row to include P2
        minCol[lastRow] = Math.Min(lastCol, minCol[lastRow]);
        maxCol[lastRow] = Math.Max(lastCol, maxCol[lastRow]);
    }

    public static void Test()
    {
        Point p1 = new Point(160, 250);
        Point p2 = new Point(340, 250);
        Point p3 = new Point(250, 40);
        Line l1 = new Line(p1, p2);
        Line l2 = new Line(p2, p3);
        Line l3 = new Line(p3, p1);

        Line[] lines = { l1, l2, l3 };

        int rowCount = 4;
        int[] minCol = new int[rowCount];
        int[] maxCol = new int[rowCount];
        for (int i = 0; i < rowCount; i++)
        {
            minCol[i] = int.MaxValue;
            maxCol[i] = int.MinValue;
        }

        for (int i = 0; i < lines.Length; i++)
            lines[i].ExpandRanges(minCol, maxCol);

        for (int i = 0; i < rowCount; i++)
            Console.WriteLine("Row {0}:  {1} - {2}", i, minCol[i], maxCol[i]);
    }
}

Produzione:

Row 0:  2 - 2
Row 1:  1 - 3
Row 2:  1 - 3
Row 3:  2147483647 - -2147483648

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.