разделить прямоугольник с конкретными правилами - PullRequest
0 голосов
/ 12 февраля 2019

все.Прежде всего, я программист-любитель.Я пытаюсь сделать простое приложение для строительства города, используя C ++ и SFML, для обучения.на самом деле это не игра, так как она будет просто строить городские кварталы и здания и показывать их пользователю.сейчас я могу создавать городские кварталы.Моя проблема в том, как разделить блоки на здания.У меня нет реальной идеи о том, как это сделать.Возможные решения: (у меня недостаточно репутации для публикации изображений, но есть ссылка): https://i.postimg.cc/630GKGW7/bitmap.png

единственными правилами являются: (1) каждое здание должно соответствовать минимальному и максимальному известному размеру;(2) каждое здание должно иметь хотя бы одну грань, касающуюся любого края блока;(3) пустых мест не должно быть.

Я боролся с этим уже несколько дней.Кто-нибудь может дать мне представление о том, как это сделать?псевдокод тоже будет здорово.

заранее спасибо!

1 Ответ

0 голосов
/ 13 февраля 2019

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

class CityBlock {
  Building[] buildings // should initially contain one building taking up the whole CityBlock

  double width
  double height
  double maxBuildingSize
  double minBuildingSize

  splitBuilding(horizontal/vertical, coord) // This will split a building horizontally/vertically
  createRandomBuildings() // this is what we want to create!
}

class Building {
  Point position // position of top-left corner
  Building[] subBuildings // buildings created by subdivision

  double width
  double height

  double size() { return width * height }
}

Теперь самое интересное!Давайте попробуем сделать метод createRandomBuildings().Подход, который я выберу, состоит в том, чтобы многократно подразделять здания, пока они не будут между 2 * minBuildingSize (меньше, чем это означает, что ни одно подразделение не может создать два действительных здания) и maxBuildingSize.

ВАЖНОЕ ПРИМЕЧАНИЕ: Этот подход гарантирует действительные здания, только если maxBuildingSize >= 2 * minBuildingSize, даже если допустимое подразделение возможно.Учитывая ваш вариант использования, я полагал, что ограничение размера не будет создавать никаких проблем, и более «случайное» решение будет лучше, чем более детерминированное.

Давайте доберемся до этого!Мы создадим рекурсивную функцию с именем subdivide для выполнения тяжелой работы.

Building[] subdivide(Building b, horizontal/vertical) {} // Subdivides b into a random number of other buildings

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

Initial Building

Из этого

Split Building

К этому

ПРИМЕЧАНИЕ: Чтобы упростить дело, я собираюсь рассмотреть это, рассматривая подразделение как вертикальное, как на рисунке выше.Для горизонтального подразделения просто поменяйте местами ширину / высоту.

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

minSubdivisionWidth = minSize / b.height // ensures that subdivisionWidth * b.height >= minSize
maxSubdivisions = floor(b.width / minSubdivisionWidth)

subdivisions = randomInt(2, maxSubdivisions)

Теперь, когда у нас есть действительное количество подразделений, нам нужно расположить их случайным образом, обеспечивая при этом здания.не слишком маленький.Для этого давайте разделим имеющееся у нас пространство на две части: минимум пробел и свободный пробел.Каждое подразделение должно иметь минимальное пространство, но есть также свободное (или оставшееся) пространство, равное b.size() - minBuildingSize * subdivisions.Это свободное пространство - это то, что мы хотим случайным образом распределить среди наших подразделенных прямоугольников.

Free Space

Синий - это минимальное пространство, а розовый - этосвободное пространство

Давайте выделим это пространство:

widths[] // This will be the widths of our subdivided buildings
freeWidth = b.width - minSubdivisionWidth * subdivisions
weights[] // randomly assigned weight for free space
sumWeight

for i = 1 to subdivisions {
  randWeight = random()
  weights[i] = randWeight
  sumWeight += randWeight
}

for i = 1 to subdivisions {
  widths[i] = minSubdivisionWidth + (weights[i] / sumWeight) * freeWidth
}

И теперь мы можем сделать фактическое подразделение:

// transform individual widths into coordinates for building split
cumulativeWidth = 0

for i = 1 to (subdivisions - 1) {
  cumulativeWidth += widths[i]
  splitBuilding(vertical, cumulativeWidth)
}

Мы почтитам!Теперь нам просто нужен фрагмент, чтобы случайно не подразделить, если здание ниже максимума:

probToNotSubdivide = .3 // obviously change this to whatever
if b.size() < maxBuildingSize and randomDouble(0, 1) <= probToNotSubdivide { return }

Один, чтобы не подразделить, если здание слишком маленькое:

if b.size() < minBuildingSize * 2 { return }

Один, чтобы неподразделите, если это отрежет здание от края блока:

/* 
If the building is touching a horizontal edge, vertical subdivisions 
will not cut anything off. If the building is touching both 
vertical edges, one subdivision can be made.
*/
if not (b.position.y == 0 or (b.position.y + b.height) == cityBlock.height) {
  if b.width == cityBlock.width {
    // do one subdivision and recurse
    splitBuilding(vertical, randomDouble(minSubdivisionWidth, cityBlock.width - minSubdivisionWidth)
    for subBuilding in b.subBuildings {
      subdivide(horizontal, subBuilding)
    }
    return
  } else { return }
}

Добавьте немного рекурсии в конце и ...

Building[] subdivide(Building b, horizontal/vertical) {
  // exit conditions
  if b.size() < maxBuildingSize and randomDouble(0, 1) <= probToNotSubdivide { return }
  if b.size() < minBuildingSize * 2 { return }

  /* 
  If the building is touching a horizontal edge, vertical subdivisions 
  will not cut anything off. If the building is touching both 
  vertical edges, one subdivision can be made.
  */
  if not (b.position.y == 0 or (b.position.y + b.height) == cityBlock.height) {
    if b.width == cityBlock.width {
      // do one subdivision and recurse
      splitBuilding(vertical, randomDouble(minSubdivisionWidth, cityBlock.width - minSubdivisionWidth)
      for subBuilding in b.subBuildings {
        subdivide(horizontal, subBuilding)
      }
      return
    } else { return }
  }

  // get # subdivisions
  minSubdivisionWidth = minSize / b.height // ensures that subdivisionWidth * b.height <= minSize
  maxSubdivisions = floor(b.width / minSubdivisionWidth)
  subdivisions = randomInt(2, maxSubdivisions)

  // get subdivision widths
  widths[] // This will be the widths of our subdivided buildings
  freeWidth = b.width - minSubdivisionWidth * subdivisions
  weights[] // randomly assigned weight for free space
  sumWeight

  for i = 1 to subdivisions {
    randWeight = random()
    weights[i] = randWeight
    sumWeight += randWeight
  }

  for i = 1 to subdivisions {
    widths[i] = minSubdivisionWidth + (weights[i] / sumWeight) * freeWidth
  }

  // transform individual widths into coordinates for building split
  cumulativeWidth = 0

  for i = 1 to (subdivisions - 1) {
    cumulativeWidth += widths[i]
    splitBuilding(vertical, cumulativeWidth)
  }

  // recurse
  for subBuilding in b.subBuildings {
    subdivide(horizontal, subBuilding)
  }
}

И это все!Теперь у нас есть createRandomBuildings() { subdivide(vertical, initialBuilding) }, и мы разделили наш городской квартал.

PS Опять же, этот код не должен быть действительным, и это также очень длинный пост.Если что-то здесь не работает правильно, отредактируйте / прокомментируйте этот ответ.Я надеюсь, что это дает некоторое представление о подходе, который вы могли бы использовать.

РЕДАКТИРОВАТЬ: Чтобы уточнить, вы должны переключаться между горизонтальным и вертикальным подразделениями на каждом уровне рекурсии.

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