Como determinar quais células em uma grade se cruzam com um determinado triângulo?

10

Atualmente, estou escrevendo uma simulação 2D de IA, mas não tenho certeza de como verificar se a posição de um agente está dentro do campo de visão de outro.

Atualmente, meu particionamento mundial é simples particionamento de espaço celular (uma grade). Quero usar um triângulo para representar o campo de visão, mas como posso calcular as células que se cruzam com o triângulo?

Semelhante a esta imagem: insira a descrição da imagem aqui

As áreas vermelhas são as células que quero calcular, verificando se o triângulo cruza essas células.

Desde já, obrigado.

EDITAR:

Apenas para aumentar a confusão (ou talvez até facilitar). Cada célula possui um vetor min e max, onde min é o canto inferior esquerdo e max é o canto superior direito.

Ray Dey
fonte
Você não poderia dividir as células em triângulos e testar triângulo-triângulo?
The Duck Comunista
As células não são polígonos físicos, apenas uma representação espacial, e aproveitam os tempos de acesso O (1) de uma matriz. Se eu tivesse um círculo de vizinhança ao redor do agente, para aproximar as células, eu poderia criar um AABB usando o raio do círculo e encontrar facilmente as interseções. O problema aqui é que só quero as células que estão na minha frente. Tenho certeza de que há algumas equações geométricas para ajudar, não consigo pensar em nenhuma para a vida de mim.
precisa saber é o seguinte
Não gosto de nenhuma das respostas aqui; no entanto, esta pergunta tem algumas respostas realmente boas: gamedev.stackexchange.com/q/81267/63053
Andrew

Respostas:

6

Calcule os três cantos do seu triângulo fov, gire-os para que estejam voltados para o caminho correto e faça um dos seguintes:

1) faça um teste de ponto no triângulo para todos os alvos em potencial

2) calcule a caixa delimitadora deste triângulo e faça um teste ponto a triângulo para todos os alvos em potencial nas células nessa caixa delimitadora - este será um código muito simples para depurar

Uma abordagem semelhante é usar um quadtree em vez de uma grade e fazer as interseções nele. Se o acesso ao bloco O (1) estiver acelerando, apenas o teste de todas as células nos limites do triângulo fov para triângulo deve ser tão rápido quanto instantâneo. Como você está olhando para outras opções, presumo que não, e que o O (1) está realmente tendo um enorme custo de perda de cache à medida que você troca seu cache. Obviamente, talvez você deva consultar as instruções de pré-busca para anotar sua caixa delimitadora com ...

3) 'rasterize' esse triângulo e verifique as células que ele 'pinta' - provavelmente o código mais eficiente, mas talvez apenas marginalmente, pois eu especularia que tudo é dominado por tempos de falha do cache e depende de quão complexas são suas células e quão ocupadas eles são.

Uma abordagem alternativa é renderizar o FOV em um bitmap fora da tela e ler o valor do pixel para cada um dos seus objetos; você não pode 'desmisturar tinta', mas com um número limitado de objetos e, escolhendo cuidadosamente sua tinta, pode deduzir quem viu quem. Essa abordagem é semelhante a quantos jogos funcionam no que o usuário clicou - eles desenham a cena fora da tela usando cores sólidas nas áreas atingidas. As GPUs são muito rápidas no preenchimento de triângulo ...

Vai
fonte
+1 graças a isso, usei uma caixa delimitadora para o triângulo para selecionar rapidamente as células apropriadas e usei um teste de ponto no triângulo para determinar quais membros dessas células estão dentro do campo de visão :)
Ray Dey
3

A solução canônica nos renderizadores de software (que devem executar exatamente esse algoritmo toda vez que rasterizam um triângulo) é, acredito, digitalizar o triângulo uma linha de pixels por vez. As bordas esquerda e direita de cada linha e calculadas andando pelas laterais do triângulo usando Bresenham e, em seguida, você preenche a linha entre elas.

Estou encobrindo muitos detalhes, mas essa é a idéia básica. Se você procurar "renderização de software" e "rasterização de triângulo", provavelmente encontrará mais alguns detalhes. Este é um problema bem resolvido. Sua placa de vídeo está fazendo isso milhões de vezes em um quadro.

Se você quer uma solução mais específico-roguelike, aqui está como eu implementado FOV na minha. Parece funcionar muito rapidamente. É essencialmente um lançador de sombras simples, trabalhando em octant de cada vez, digitalizando para fora do player.

maravilhoso
fonte
11
Essa é uma abordagem bem legal.
Notabene 27/01
2

Estou usando uma variação do algoritmo scanline para resolver exatamente o mesmo problema. Comecei classificando os três pontos do triângulo pela altura. Então, basicamente, verifico se duas arestas estão do lado esquerdo ou direito. Para o lado com duas arestas, é necessário marcar as linhas onde você altera a aresta que delimita suas linhas. Para o lado com uma borda, você sempre pode usar isso.

Então, para cada linha, eu sei quais são as duas arestas que a delimitam e posso calcular os limites superior e inferior na direção x. Parece bastante complicado, mas condensa-se a apenas algumas linhas de código. Certifique-se de lidar com o caso especial em que uma borda é completamente horizontal!

ltjax
fonte
2

Que tal manter um intervalo de colunas para cada linha que está no triângulo? O que você pode fazer é definir a coluna min e max para cada linha em que cada ponto está e onde cada linha do triângulo cruza uma linha separadora de linha horizontal.

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]);
    }
}

Resultado:

Row 0:  2 - 2
Row 1:  1 - 3
Row 2:  1 - 3
Row 3:  2147483647 - -2147483648
Jason Goemaat
fonte
1

Há algum trabalho de algoritmo feito na comunidade roguelike que lida com FOV / LOS / iluminação em um mundo baseado em blocos.

Talvez você possa encontrar algo nesta página que possa ser usado para ajudar a resolver seu problema: http://roguebasin.roguelikedevelopment.org/index.php?title=Field_of_Vision

Especificamente "FOV permissivo" pode ser o mais aplicável à sua situação.

Tetrad
fonte