Как определить, какие ячейки в сетке пересекаются с данным треугольником?

10

В настоящее время я пишу симуляцию 2D AI, но я не совсем уверен, как проверить, находится ли положение агента в поле зрения другого.

В настоящее время разделение моего мира - это простое разбиение пространства ячеек (сетка). Я хочу использовать треугольник для представления поля зрения, но как я могу вычислить ячейки, которые пересекаются с треугольником?

Похож на эту картину: введите описание изображения здесь

Красные области - это ячейки, которые я хочу вычислить, проверяя, пересекает ли треугольник эти ячейки.

Заранее спасибо.

РЕДАКТИРОВАТЬ:

Просто чтобы добавить к путанице (или, возможно, даже сделать это проще). Каждая ячейка имеет вектор min и max, где min - это нижний левый угол, а max - верхний правый угол.

Рэй Дей
источник
Не могли бы вы разбить клетки на треугольники и проверить треугольник-треугольник?
Коммунистическая утка
Ячейки - это не физические полигоны, а просто пространственное представление, и оно использует время доступа O (1) массива. Если бы у меня был круг вокруг агента, чтобы приблизить ячейки, я мог бы создать AABB, используя радиус круга, и легко найти пересечения. Проблема здесь в том, что нужны только те клетки, которые передо мной. Я уверен, что есть какие-то геометрические уравнения, чтобы помочь, я просто не могу придумать ни одного для моей жизни.
Рэй Дей
Не нравится ни один из ответов здесь; однако на этот вопрос есть несколько действительно хороших ответов: gamedev.stackexchange.com/q/81267/63053
Эндрю

Ответы:

6

Вычислите три угла вашего fov-треугольника, поверните их так, чтобы они были повернуты правильно, и затем выполните одно из следующих действий:

1) выполнить тест точка-треугольник для всех потенциальных целей

2) вычислите ограничивающий прямоугольник этого треугольника и выполните тест «точка-треугольник» для всех потенциальных целей в ячейках в этом ограничивающем прямоугольнике - это будет очень простой код для отладки

Аналогичный подход заключается в использовании квадродерева, а не сетки и пересечений. Если доступ к тайлу O (1) ускоряет вас, то просто тестирование всех ячеек в границах fov-треугольника для in-треугольника должно быть настолько быстрым, чтобы быть мгновенным. Поскольку вы смотрите на другие варианты, я предполагаю, что это не так, и что O (1) на самом деле берет на себя огромную стоимость промаха кэша, когда вы перебираете его. Вы, конечно, могли бы, возможно, взглянуть на инструкции по предварительной загрузке, чтобы аннотировать свой ограничивающий переход ...

3) «растеризовать» этот треугольник и проверить ячейки, которые он «рисует» - вероятно, самый эффективный код, но, возможно, лишь незначительно, так как я бы предположил, что в нем преобладает время пропуска кэша, и это зависит от того, насколько сложны ваши ячейки и насколько они заняты они есть.

Альтернативный подход состоит в том, чтобы визуализировать FOV в закадровом растровом изображении и затем прочитать значение пикселя для каждого из ваших объектов; Вы не можете «размешать краску», но с ограниченным количеством объектов и, тщательно выбирая свою краску, вы можете определить, кто видел кого. Этот подход аналогичен тому, как много игр отрабатывают то, на что нажимал пользователь - они рисуют сцену вне экрана, используя сплошные цвета для областей попадания. GPU очень быстро заполняют треугольник ...

Будет
источник
+1 спасибо за это, я использовал ограничивающую рамку для треугольника, чтобы быстро выбрать подходящие ячейки, и использовал тест точки в треугольнике, чтобы определить, какие члены этих ячеек находятся в поле зрения :)
Рэй Дей
3

Я полагаю, что каноническое решение в программных средствах визуализации (которые должны выполнять этот точный алгоритм каждый раз, когда они растеризуют треугольник) состоит в том, чтобы сканировать треугольник по одной строке пикселей за раз. Левый и правый края каждого ряда рассчитываются путем спуска по сторонам треугольника с помощью Брезенхэма , а затем вы заполняете ряд между ними.

Я замалчиваю многие детали, но это основная идея. Если вы ищите «рендеринг программного обеспечения» и «растеризацию треугольника», вы, вероятно, найдете более подробную информацию. Это хорошо решенная проблема. Ваша видеокарта делает это миллионы раз за кадр.

Если вы хотите более специфичное для roguelike решение, вот как я реализовал FOV в своей. Кажется, работает довольно быстро. По сути, это простой заклинатель теней, работающий с октантом одновременно, сканирующий наружу от игрока.

необычайно щедрый
источник
1
Это довольно крутой подход.
Notabene
2

Я использую разновидность алгоритма сканирования, чтобы решить ту же проблему. Я начал с сортировки трех точек треугольника по их высоте. Затем я проверяю, находятся ли два ребра слева или справа. Для стороны с двумя ребрами, вы должны отметить их ряд, где вы изменяете, какое ребро разделяет ваши ряды. Для стороны с одним краем, вы всегда можете использовать это.

Итак, для каждой строки я знаю, какие два ребра ограничивают его, и я могу вычислить верхнюю и нижнюю границы в направлении x. Это звучит довольно сложно, но сокращается до нескольких строк кода. Убедитесь, что вы обрабатываете специальный случай, когда один край полностью горизонтален!

ltjax
источник
2

Как насчет поддержания диапазона столбцов для каждой строки, которые находятся в треугольнике? Что вы можете сделать, это установить минимальный и максимальный столбец для каждой строки, где каждая точка находится, и где каждая треугольная линия пересекает горизонтальную линию разделителя строк.

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

Вывод:

Row 0:  2 - 2
Row 1:  1 - 3
Row 2:  1 - 3
Row 3:  2147483647 - -2147483648
Джейсон Гомаат
источник
1

В сообществе roguelike, которое занимается FOV / LOS / освещением в мире на основе тайлов, есть некоторая работа с алгоритмами.

Возможно, на этой странице вы найдете что-то, что поможет решить вашу проблему: http://roguebasin.roguelikedevelopment.org/index.php?title=Field_of_Vision

В частности, «Permissive FOV» может быть наиболее подходящим для вашей ситуации.

тетрада
источник