¿Cómo determinar qué celdas de una cuadrícula se cruzan con un triángulo dado?

10

Actualmente estoy escribiendo una simulación 2D AI, pero no estoy completamente seguro de cómo verificar si la posición de un agente está dentro del campo de visión de otro.

Actualmente, mi partición mundial es una partición simple de espacio celular (una cuadrícula). Quiero usar un triángulo para representar el campo de visión, pero ¿cómo puedo calcular las celdas que se cruzan con el triángulo?

Similar a esta imagen: ingrese la descripción de la imagen aquí

Las áreas rojas son las celdas que quiero calcular, verificando si el triángulo interseca esas celdas.

Gracias por adelantado.

EDITAR:

Solo para agregar a la confusión (o tal vez incluso hacerlo más fácil). Cada celda tiene un vector min y max donde min es la esquina inferior izquierda y max es la esquina superior derecha.

Ray Dey
fuente
¿No podrías dividir las celdas en triángulos y probar triángulo-triángulo?
El pato comunista
Las celdas no son polígonos físicos, solo una representación espacial, y aprovecha los tiempos de acceso O (1) de una matriz. Si tuviera un círculo vecinal alrededor del agente, para aproximar las celdas, podría crear un AABB usando el radio del círculo y encontrar fácilmente las intersecciones. El problema aquí es que solo quiero las celdas que están frente a mí. Estoy seguro de que hay algunas ecuaciones geométricas para ayudar, simplemente no puedo pensar en ninguna para mi vida.
Ray Dey
No me gusta ninguna de las respuestas aquí; sin embargo, esta pregunta tiene algunas respuestas realmente buenas: gamedev.stackexchange.com/q/81267/63053
Andrew hace

Respuestas:

6

Calcule las tres esquinas de su triángulo fov, gírelas para que estén orientadas de la manera correcta y tal, y luego haga una de:

1) haz una prueba de punto en triángulo para todos los objetivos potenciales

2) calcule el cuadro delimitador de este triángulo y realice una prueba de punto en triángulo para todos los objetivos potenciales en las celdas en este cuadro delimitador; este será un código muy sencillo para depurar

Un enfoque similar es usar un quadtree en lugar de una cuadrícula y hacer las intersecciones en eso. Si el acceso al mosaico O (1) lo está acelerando, simplemente probar todas las celdas en los límites del triángulo fov en el triángulo debería ser tan rápido como instantáneo. Como está buscando otras opciones, supongo que no lo es y que el O (1) en realidad está tomando un costo masivo de pérdida de caché mientras agota su caché. Por supuesto, podría mirar las instrucciones de captación previa para anotar su paseo de caja delimitador con ...

3) 'rasterice' este triángulo y verifique las celdas que 'pinta', probablemente el código más eficiente, pero tal vez solo marginalmente, ya que especularía que todo está dominado por los tiempos de pérdida de caché y depende de cuán complejas sean sus celdas y cuán ocupadas son.

Un enfoque alternativo es representar el FOV en un mapa de bits fuera de la pantalla y luego leer el valor de píxel para cada uno de sus objetos; no se puede 'desmezclar pintura' pero con un número limitado de objetos y al elegir cuidadosamente su pintura se puede deducir quién vio a quién. Este enfoque es similar a la cantidad de juegos en los que el usuario hizo clic: dibujan la escena fuera de la pantalla usando colores sólidos para las áreas de impacto. Las GPU son muy rápidas en el llenado de triángulos ...

Será
fuente
+1 gracias por esto, he usado un cuadro delimitador para el triángulo para seleccionar rápidamente las celdas apropiadas y he usado una prueba de punto en triángulo para determinar qué miembros de esas celdas están dentro del Campo de visión :)
Ray Dey
3

La solución canónica en los renderizadores de software (que deben hacer este algoritmo exacto cada vez que rasterizan un triángulo) es, creo, escanear el triángulo una fila de píxeles a la vez. Los bordes izquierdo y derecho de cada fila se calculan caminando por los lados del triángulo con Bresenham , y luego se completa la fila entre ellos.

Estoy pasando por alto muchos detalles, pero esa es la idea básica. Si busca "renderizado de software" y "rasterización de triángulo", probablemente encontrará más detalles. Este es un problema bien resuelto. Su tarjeta gráfica está haciendo esto millones de veces por marco.

Si desea una solución más específica para roguelike, así es como implementé FOV en la mía. Parece que funciona bastante rápido. Es esencialmente un simple lanzador de sombras, trabajando en octantes a la vez, escaneando hacia afuera desde el reproductor.

munificente
fuente
1
Ese es un enfoque genial.
Notabene
2

Estoy usando una variación del algoritmo scanline para resolver exactamente el mismo problema. Comencé clasificando los tres puntos triangulares por su altura. Luego, básicamente verifico si dos bordes están a la izquierda o a la derecha. Para el lado con dos bordes, debe marcar la fila donde cambia qué borde delimita sus filas. Para el lado con un borde, siempre puedes usar eso.

Entonces, para cada fila, sé qué dos bordes la delimitan, y puedo calcular los límites superior e inferior en la dirección x. Suena bastante complicado, pero se condensa en solo unas pocas líneas de código. ¡Asegúrese de manejar el caso especial donde un borde está completamente horizontal!

ltjax
fuente
2

¿Qué tal mantener un rango de columnas para cada fila que están en el triángulo? Lo que puede hacer es establecer la columna mínima y máxima para cada fila donde se encuentra cada punto, y donde cada línea de triángulo cruza una línea de separación de fila 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]);
    }
}

Salida:

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

Hay un trabajo de algoritmo realizado en la comunidad roguelike que se ocupa de FOV / LOS / iluminación en un mundo basado en mosaicos.

Quizás pueda encontrar algo en esta página que pueda usar para ayudar a resolver su problema: http://roguebasin.roguelikedevelopment.org/index.php?title=Field_of_Vision

Específicamente, el "FOV permisivo" podría ser el más aplicable a su situación.

Tétrada
fuente