Як визначити, які комірки в сітці перетинаються із заданим трикутником?


10

В даний час я пишу 2D-симулятор AI, але я не зовсім впевнений, як перевірити, чи знаходиться агент у полі зору іншого.

Наразі мій світовий розділ - це простий поділ коміркового простору (сітка). Я хочу використовувати трикутник для подання поля зору, але як я можу обчислити комірки, які перетинаються з трикутником?

Подібна до цієї картинки: введіть тут опис зображення

Червоні області - це клітини, які я хочу обчислити, перевіривши, чи перетинається трикутник із цими клітинками.

Заздалегідь спасибі.

Редагувати:

Просто додати до плутанини (а може, навіть полегшити). Кожна комірка має вектор min та max, де min - нижній лівий кут, а max - верхній правий кут.


Не могли б ви розділити клітинки на трикутники та перевірити трикутник-трикутник?
Качка комуніста

Клітини не є фізичними багатокутниками, а просторовим поданням, і вона використовує переваги часу доступу до масиву O (1). Якби у мене навколо агента було коло сусідства, щоб наблизити комірки, я міг би створити AABB, використовуючи радіус кола і легко знайти перехрестя. Проблема тут полягає в тому, що хочуть лише ті клітини, які переді мною. Я впевнений, що деякі геометричні рівняння можуть допомогти, я просто не можу придумати жодного життя.
Рей Дей

Не подобається жодна відповідь тут; однак на це питання є кілька справді хороших відповідей: gamedev.stackexchange.com/q/81267/63053
Андрій

Відповіді:


6

Обчисліть три кути трикутника, оберніть їх так, щоб виходили правильним способом і так, а потім виконайте один із:

1) зробити тест у трикутнику для всіх потенційних цілей

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

Аналогічний підхід полягає у використанні квадрату, а не сітки, та перетинах на цьому. Якщо доступ до плитки O (1) прискорює вас, то просто тестування всіх комірок у межах трикутника fov на трикутник повинно бути таким швидким, щоб миттєво. Коли ви дивитесь на інші варіанти, я припускаю, що це не так, і що O (1) насправді приймає величезну вартість пропуску кешу, коли ви обробляєте кеш. Ви, звичайно, можете переглянути інструкції попереднього вибору, щоб анотувати ваш прогулок по обмеженій коробці ...

3) "розпустіть" цей трикутник і перевірте клітинки, які він "малює" - ймовірно, найефективніший код, але, можливо, лише незначно, оскільки я б міркував, що його всі домінуючі часи пропуску кешу залежать від того, наскільки складні ваші клітини та як зайняті. вони є.

Альтернативний підхід - перевести FOV на позаекранну растрову карту, а потім прочитати значення пікселя для кожного з ваших об'єктів; ви не можете «змішувати фарбу», але з обмеженою кількістю предметів і ретельно вибираючи фарбу, ви можете зрозуміти, хто бачив кого. Цей підхід схожий на те, скільки ігор розробити те, на що користувач натиснув - вони малюють сцену поза екраном, використовуючи суцільні кольори для постраждалих областей. Графічні процесори дуже швидкі при заповненні трикутника ...


+1 спасибі за це, я використав обмежувальне поле для трикутника для швидкого вибору відповідних комірок і використав тест «в трикутнику», щоб визначити, які члени цих комірок знаходяться в полі «Огляд поля»
Рей Дей

3

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

Я переглядаю багато деталей, але це основна ідея. Якщо ви поцікавитеся на "візуалізація програмного забезпечення" та "растеризація трикутника", ви, ймовірно, знайдете ще кілька деталей. Це добре вирішена проблема. Ваша відеокарта робить це кадром мільйони разів.

Якщо ви хочете більш конкретного рішення, ось як я реалізував FOV в моєму. Здається, працює досить швидко. Це по суті простий тінь заклинань, який працює над октантом і сканує назовні від гравця.


1
Це досить крутий підхід.
Нотабене

2

Я використовую варіант алгоритму сканування, щоб вирішити ту саму проблему. Я почав з сортування трьох точок трикутника за їх висотою. Потім я в основному перевіряю, чи є два ребра зліва чи справа. Для сторони з двома краями потрібно позначити рядок, де ви змінюєте, який край обмежує ваші рядки. Для сторони, що має один край, ви завжди можете використовувати це.

Тоді для кожного ряду я знаю, які два краї обмежують його, і я можу обчислити верхню та нижню межі в напрямку x. Це звучить досить складно, але воно стискається лише до кількох рядків коду. Переконайтеся, що ви обробляєте спеціальний чохол, коли один край повністю горизонтальний!


2

Як щодо збереження діапазону стовпців для кожного ряду, який знаходиться в трикутнику? Що ви можете зробити, це встановити стовпчик min та max для кожного рядка, де знаходиться кожна точка, і де кожна лінія трикутника перетинає горизонтальну лінію роздільника рядків.

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

Існує деяка робота з алгоритмом, виконана в спільноті, що займається недоброзичливим середовищем, яка займається FOV / LOS / освітленням у світі, що базується на плитках.

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

Зокрема, "Дозволений FOV" може бути найбільш застосовним до вашої ситуації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.