Чи існує алгоритм виявлення "материка" на двовимірній карті?


28

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

Я хотів би виявити материк і заповнити в ньому дірки. Я подумав про три речі:

  1. Шукайте в кожній неводній (темній клітині) комірці, якщо її можна підключити до центру карти за допомогою алгоритму пошуку шляху. Занадто дорого! Але це могло б працювати для островів.

  2. Материк заповнений відром зеленої фарби. Кожен отвір оточений фарбою ... тепер що? Якщо я перевіряю кожну точку води всередині материка на суміжність, я видалю деякі півострови та інші географічні особливості, що відображаються на береговій лінії.

  3. Якесь виявлення краю, щоб визначити материк. Тримайте все, що є всередині, наповнюйте його, якщо це вода, видаліть те, що знаходиться зовні. Складні?

Можливо, якийсь досвідчений розробник гри може допомогти мені у цьому, можливо, давши мені назву якогось відомого алгоритму чи техніки?


4
Мені просто цікаво, чи використовували ви якийсь алгоритм для створення цієї карти. І якщо так, то чим ви користувалися?
jgallant

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

@Jon так! алгоритм алмазного квадрата для створення карти висоти, тоді все нижче одне значення - вода, інше - земля. Я планую використовувати це як континентальну форму, потім ще один прохід, щоб додати деталі рельєфу.
Габріель А. Зоррілья

@ Mind Marching Squares Algorithm стане корисним для фарбування кордону континенту. Дякую приємну пропозицію!
Габріель А. Зоррілла

Відповіді:


32

Видалення островів

Я робив подібні речі раніше в одній зі своїх ігор. Щоб позбутися від зовнішніх островів, процес був в основному:

  1. По-перше, має бути гарантія, що центр карти завжди буде належати основній землі, і кожен піксель починається як "Земля", або "Вода" (тобто різних кольорів).
  2. Потім зробіть заливку в чотири напрямки, починаючи від центральної карти і розкладаючи по всій плитці "Земля". Позначте кожен піксель, відвіданий цією заливкою, як інший тип, наприклад, "MainLand".
  3. Нарешті перейдіть по всій карті та перетворіть будь-який залишився піксель "Суша" у "Вода, щоб позбутися інших островів.

Видалення озер

Що стосується позбавлення від дір (або озер) всередині острова, ви робите подібний процес, але починаючи з кутів карти та натомість поширюючи плитки "Вода". Це дозволить вам відрізнити «Море» від інших водних плиток, і тоді ви зможете позбутися від них так само, як ви позбулися островів раніше.

Приклад

Дозвольте мені розкопати мою реалізацію заливного потоку, який у мене є десь (відмова, я не піклувався про ефективність, тому я впевнений, що існує багато більш ефективних способів його реалізації):

private void GenerateSea()
{
    // Initialize visited tiles list
    visited.Clear();

    // Start generating sea from the four corners
    GenerateSeaRecursive(new Point(0, 0));
    GenerateSeaRecursive(new Point(size.Width - 1, 0));
    GenerateSeaRecursive(new Point(0, size.Height - 1));
    GenerateSeaRecursive(new Point(size.Width - 1, size.Height - 1));
}

private void GenerateSeaRecursive(Point point)
{
    // End recursion if point is outside bounds
    if (!WithinBounds(point)) return;

    // End recursion if the current spot is a land
    if (tiles[point.X, point.Y].Land) return;

    // End recursion if this spot has already been visited
    if (visited.Contains(point)) return;

    // Add point to visited points list
    visited.Add(point);

    // Calculate neighboring tiles coordinates
    Point right = new Point(point.X + 1, point.Y);
    Point left = new Point(point.X - 1, point.Y);
    Point up = new Point(point.X, point.Y - 1);
    Point down = new Point(point.X, point.Y + 1);

    // Mark neighbouring tiles as Sea if they're not Land
    if (WithinBounds(right) && tiles[right.X, right.Y].Empty)
        tiles[right.X, right.Y].Sea = true;
    if (WithinBounds(left) && tiles[left.X, left.Y].Empty)
        tiles[left.X, left.Y].Sea = true;
    if (WithinBounds(up) && tiles[up.X, up.Y].Empty)
        tiles[up.X, up.Y].Sea = true;
    if (WithinBounds(down) && tiles[down.X, down.Y].Empty)
        tiles[down.X, down.Y].Sea = true;

    // Call the function recursively for the neighboring tiles
    GenerateSeaRecursive(right);
    GenerateSeaRecursive(left);
    GenerateSeaRecursive(up);
    GenerateSeaRecursive(down);
}

Я використовував це як перший крок, щоб позбутися озер у своїй грі. Після зателефонувавши до цього, все, що мені потрібно було б зробити, було щось на кшталт:

private void RemoveLakes()
{
    // Now that sea is generated, any empty tile should be removed
    for (int j = 0; j != size.Height; j++)
        for (int i = 0; i != size.Width; i++)
            if (tiles[i, j].Empty) tiles[i, j].Land = true;
}

Редагувати

Додавання додаткової інформації на основі коментарів. Якщо ваш пошуковий простір занадто великий, ви можете відчути переповнення стека при використанні рекурсивної версії алгоритму. Ось посилання на stackoverflow (каламбур призначений :-)) на не рекурсивну версію алгоритму, використовуючи Stack<T>натомість (також у C #, щоб відповідати моїй відповіді, але слід легко адаптуватись до інших мов, і на цьому є інші реалізації. посилання теж).


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

Назустріч тому, що сказав GeorgeDuckett - ви можете спробувати алгоритми виявлення блог Googling: це поширена проблема в мультитач з використанням FTIR. Я пам'ятаю, що я придумав розумніший алгоритм, але не можу пригадати за життя, як це працювало.
Джонатан Дікінсон

Оскільки у мене виникли проблеми зі стеком у PHP, я реалізував заповнення LIFO, дивовижно працював.
Габріель А. Зоррілья

Це не заливка лише тоді, коли алгоритм DFS викликається з алгоритму BFS? Будь ласка, поясніть комусь.
jcora

@Bane Що ти маєш на увазі? Різниця між DFS або BFS - це лише порядок відвідування вузлів. Я не думаю, що алгоритм заливки затоплення не визначає порядок обходу - це не важливо, поки він заповнює всю область без перегляду вузлів. Порядок залежить від реалізації. Дивіться внизу запису у вікіпедії малюнок, який порівнює порядок обходу під час використання черги та використання стека. Рекурсивна реалізація також може розглядатися як стек (оскільки вона використовує стек викликів).
Девід Гувейя


5

Це стандартна операція з обробки зображень. Ви використовуєте двофазну операцію.

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

На другому етапі ви робите майже зворотне: перетворюєте на сухопутні пікселі всі морські пікселі, які межують з сушею, але лише якщо вони були наземними пікселями на оригінальній карті (Ось чому ви створили копію на фазі 1). Це відновлює острови до їх первісного вигляду, якщо тільки вони не були повністю ліквідовані в 1 фазі.


1

У мене було подібне питання, але не в розробці ігор. Мені довелося знаходити пікселі на зображенні, які були сусідні один з одним і мали однакове значення (з’єднані регіони). Я спробував використовувати рекурсивну заправку, але все-таки спричиняв переповнення стека (я початківець програміст: P). Тоді я спробував цей метод http://en.wikipedia.org/wiki/Connected-component_labeling, він насправді був набагато ефективнішим для моєї проблеми.


Ви мали б спробувати версію стека затоплення Algo і врятувати проблеми зі стеком. Вийшов набагато простішим, ніж CCL (над чим я працював останні 2 тижні без удачі.)
Габріель А. Зоррілья

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