Как я могу случайным образом разместить несколько не сталкивающихся ритов? - PullRequest
9 голосов
/ 07 декабря 2010

Я работаю над некоторыми 2D-играми с Pygame.Мне нужно разместить несколько объектов одновременно , не пересекая .Я попробовал несколько очевидных методов, но они не сработали.

Далее следуют очевидные методы (псевдо):

create list of objects
for object in list:
    for other object in list:
        if object collides with other object:
            create new list of objects

Этот метод занял вечность.

Другой метод, который я попробовал:

create list of objects
for object in list:
    for other object in list:
        if object collides with other object:
             remove object from list

Этот метод возвратился около пустых списков.

Я имею дело со списком, который имеет размер от 2 до 20 объектов.Любые предложения?

РЕДАКТИРОВАТЬ: Все прямоугольники случайных разных размеров.

Ответы [ 7 ]

11 голосов
/ 08 декабря 2010

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

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

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

Большая часть действия выполняется в функции quadsect().Выбор внутренней точки имеет решающее значение при определении того, как выглядит результат.Вы можете ограничить его любым удобным для вас способом, например, выбрать только тот, который приведет к тому, что под прямоугольники будут иметь минимальную ширину или высоту или не больше некоторой величины.В примере кода в моем ответе он определяется как центральная точка ± 1 / 3 от ширины и высоты внешнего прямоугольника, но в основном любая внутренняя точка будет работать до некоторой степени.

Поскольку этот алгоритм очень быстро генерирует под прямоугольники, можно потратить некоторое вычислительное время на определение внутренней точки деления.

Чтобы помочь визуализировать результаты этого подхода, есть некоторые несущественныекод в самом конце, который использует модуль PIL (Python Imaging Library) для создания файла изображения, отображающего прямоугольники, сгенерированные во время некоторых тестовых прогонов, которые я сделал.

В любом случае, вот последняя версия кода и выводобразцы:

import random
from random import randint
random.seed()

NUM_RECTS = 20
REGION = Rect(0, 0, 640, 480)

class Point(object):
    def __init__(self, x, y):
        self.x, self.y = x, y

    @staticmethod
    def from_point(other):
        return Point(other.x, other.y)

class Rect(object):
    def __init__(self, x1, y1, x2, y2):
        minx, maxx = (x1,x2) if x1 < x2 else (x2,x1)
        miny, maxy = (y1,y2) if y1 < y2 else (y2,y1)
        self.min, self.max = Point(minx, miny), Point(maxx, maxy)

    @staticmethod
    def from_points(p1, p2):
        return Rect(p1.x, p1.y, p2.x, p2.y)

    width  = property(lambda self: self.max.x - self.min.x)
    height = property(lambda self: self.max.y - self.min.y)

plus_or_minus = lambda v: v * [-1, 1][(randint(0, 100) % 2)]  # equal chance +/-1

def quadsect(rect, factor):
    """ Subdivide given rectangle into four non-overlapping rectangles.
        'factor' is an integer representing the proportion of the width or
        height the deviatation from the center of the rectangle allowed.
    """
    # pick a point in the interior of given rectangle
    w, h = rect.width, rect.height  # cache properties
    center = Point(rect.min.x + (w // 2), rect.min.y + (h // 2))
    delta_x = plus_or_minus(randint(0, w // factor))
    delta_y = plus_or_minus(randint(0, h // factor))
    interior = Point(center.x + delta_x, center.y + delta_y)

    # create rectangles from the interior point and the corners of the outer one
    return [Rect(interior.x, interior.y, rect.min.x, rect.min.y),
            Rect(interior.x, interior.y, rect.max.x, rect.min.y),
            Rect(interior.x, interior.y, rect.max.x, rect.max.y),
            Rect(interior.x, interior.y, rect.min.x, rect.max.y)]

def square_subregion(rect):
    """ Return a square rectangle centered within the given rectangle """
    w, h = rect.width, rect.height  # cache properties
    if w < h:
        offset = (h - w) // 2
        return Rect(rect.min.x, rect.min.y+offset,
                    rect.max.x, rect.min.y+offset+w)
    else:
        offset = (w - h) // 2
        return Rect(rect.min.x+offset, rect.min.y,
                    rect.min.x+offset+h, rect.max.y)

# call quadsect() until at least the number of rects wanted has been generated
rects = [REGION]   # seed output list
while len(rects) <= NUM_RECTS:
    rects = [subrect for rect in rects
                        for subrect in quadsect(rect, 3)]

random.shuffle(rects)  # mix them up
sample = random.sample(rects, NUM_RECTS)  # select the desired number
print '%d out of the %d rectangles selected' % (NUM_RECTS, len(rects))

#################################################
# extra credit - create an image file showing results

from PIL import Image, ImageDraw

def gray(v): return tuple(int(v*255) for _ in range(3))

BLACK, DARK_GRAY, GRAY = gray(0), gray(.25), gray(.5)
LIGHT_GRAY, WHITE = gray(.75), gray(1)
RED, GREEN, BLUE = (255, 0, 0), (0, 255, 0), (0, 0, 255)
CYAN, MAGENTA, YELLOW = (0, 255, 255), (255, 0, 255), (255, 255, 0)
BACKGR, SQUARE_COLOR, RECT_COLOR = (245, 245, 87), (255, 73, 73), (37, 182, 249)

imgx, imgy = REGION.max.x + 1, REGION.max.y + 1
image = Image.new("RGB", (imgx, imgy), BACKGR)  # create color image
draw = ImageDraw.Draw(image)

def draw_rect(rect, fill=None, outline=WHITE):
    draw.rectangle([(rect.min.x, rect.min.y), (rect.max.x, rect.max.y)],
                   fill=fill, outline=outline)

# first draw outlines of all the non-overlapping rectanges generated
for rect in rects:
    draw_rect(rect, outline=LIGHT_GRAY)

# then draw the random sample of them selected
for rect in sample:
    draw_rect(rect, fill=RECT_COLOR, outline=WHITE)

# and lastly convert those into squares and re-draw them in another color
for rect in sample:
    draw_rect(square_subregion(rect), fill=SQUARE_COLOR, outline=WHITE)

filename = 'square_quadsections.png'
image.save(filename, "PNG")
print repr(filename), 'output image saved'

Выходной образец 1

first sample output image

Выходной образец 2

second sample output image

1 голос
/ 09 сентября 2011

Существует очень простое приближение к вашей проблеме, которое отлично сработало для меня:

  • Определить сетку. Например, сетка 100 пикселей записывает (x, y) -> (int (x / 100), int (y / 100)). Элементы сетки не перекрываются.
  • Либо поместите каждый объект в отдельную сетку (случайным образом внутри сетки будет выглядеть красивее), либо поместите случайным образом несколько объектов в каждой сетке, если вы можете позволить нескольким объектам перекрываться.

Я использовал это для случайной генерации 2D-карты (как у Zelda). Изображения моих объектов меньше, чем <100 * 100>, поэтому я использовал сетку размером <500 * 500> и допустил 1-6 объектов в каждой сетке.

1 голос
/ 07 декабря 2010

Три идеи:

Уменьшение размера ваших объектов

Первый метод не удался, потому что попадание в случайный массив из 20 непересекающихся объектов крайне маловероятно (на самом деле (1-p)^20, где 0<p<1 - вероятность столкновения двух объектов).Если бы вы могли драматически (драмы на несколько порядков) уменьшить их размер, это могло бы помочь.

Выберите их по одному

Наиболее очевидное улучшение:

while len(rectangles)<N:
    new_rectangle=get_random_rectangle()
    for rectangle in rectangles:
        if not any(intersects (rectangle, new_rectangle) for rectangle in rectangles)
            rectangles.add(new_rectangle)

Это значительно улучшит вашу производительность, так как наличие одного пересечения не заставит вас создавать целый новый набор, просто выберет другой отдельный прямоугольник.

Предварительный расчет

Какчасто вы будете использовать эти наборы в своей игре?Использование другого набора каждую секунду - это другой сценарий, чем использование набора один раз в час.Если вы не используете эти наборы слишком часто, предварительно рассчитайте достаточно большой набор, чтобы игрок, вероятно, никогда не увидел один и тот же набор дважды.При предварительном расчете вы не слишком заботитесь о затраченном времени (так что вы даже можете использовать свой неэффективный первый алгоритм).

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

Во время выполнения просто выберите набор случайным образом.Это, вероятно, лучший подход для игр в реальном времени.

Примечание: В этом решении предполагается, что ваши прямоугольники сохраняются в компактном виде, например, пары (x, y) координат.Эти пары занимают очень мало места, и вы можете сэкономить тысячи и даже миллионы в файле разумного размера.

Полезные ссылки:

0 голосов
/ 17 октября 2017

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

Я использовал жадный подход:

  • Прямоугольная форма общего (глобального) прямоугольника: создайте сетку из отсортированного x & sortedу координаты всех прямоугольников до сих пор.Так что это даст вам неправильную (но прямоугольную) сетку.
  • Для каждой ячейки сетки рассчитайте площадь, это даст вам матрицу областей.
  • Используйте Kadanes 2D алгоритм поиска подматрицы, которая дает вам максимальную площадь (= самый большой свободный прямоугольник)
  • Поместите случайный прямоугольник в это свободное пространство
  • Повторите

Этотребуется преобразование из вашего исходного координатного пространства в / из пространства сетки, но это сделать несложно.

(Обратите внимание, что запуск Кадена непосредственно в исходном глобальном прямоугольнике занимает много времени. Переход к приближению сетки достаточно быстр длямое заявление)

0 голосов
/ 07 декабря 2010

альтернативный псевдокод, к уже упомянутым:

while not enough objects:
  place object randomly
  if overlaps with anything else:
    reduce size until it fits or has zero size
  if zero size: 
    remove

Или что-то в этом роде.

Но это имеет то преимущество, что возможно создание объектов меньшего размера, чем вы предполагали, и создание объектов, которые почти пересекаются (т.е. касаются).

Если игрок должен пройти карту, он все равно не сможет пройти ее, потому что его путь может быть заблокирован.

0 голосов
/ 07 декабря 2010

Вы пробовали:

Until there are enough objects:
    create new object
    if it doesn't collide with anything in the list:
        add it to the list

Нет смысла воссоздавать весь список или убирать все, что вовлечено в столкновение.

Другая идея состоит в том, чтобы "исправить" столкновения любым из следующих подходов:

1) Найдите центр области пересечения и отрегулируйте соответствующий угол каждого пересекающегося прямоугольника до этой точки так, чтобы они теперь касались угла / края, а не пересекались.

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

0 голосов
/ 07 декабря 2010
list_of_objects = []
for i in range(20):
    while True:
        new_object = create_object()
        if not any(collides(new_object, x) for x in list_of_objects):
            break
    list_of_objects.append(new_object)

Полагаю, у вас уже есть функции create_object() и collides()

Вам также может понадобиться уменьшить размер ритов, если это повторяется слишком много раз

...