用特定规则细分矩形

时间:2019-02-12 16:26:46

标签: algorithm logic

大家。 首先,我是一名业余程序员。我正在尝试使用C ++和SFML制作一个简单的城市建筑应用程序进行学习。它并不是真正的游戏,因为它只会建造城市街区和建筑物,并向用户展示。 现在,我能够创建城市街区。我的问题是如何将街区细分为建筑物。我对如何做到没有真正的想法。 可能的解决方案是(我没有足够的声誉来发布图像,但是有链接): https://i.postimg.cc/630GKGW7/bitmap.png

唯一的规则是: (1)每座建筑物必须符合已知的最小和最大尺寸; (2)每栋建筑物必须至少有一个面接触任何块体边缘; (3)不得保留任何空白空间。

我已经为此困扰了好几天。谁能给我一个想法呢?伪代码也很棒。

提前谢谢!

1 个答案:

答案 0 :(得分:1)

请注意,我将使用OOP语法简化此操作,但这不是有效的代码。首先,我们创建一个接口来定义所需的行为:

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)

现在我们有了有效的细分数量,我们需要在确保建筑物不太小的同时随机分配它们的空间。为此,我们将可用空间分成两部分:最小空间和 free 空间。每个细分区域都需要有最小的空间,但是也有等于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) },并且我们细分了城市街区。

P.S。。再次,此代码并不意味着有效,这也是一篇很长的文章。如果此处的某些内容无法正常工作,请对此答案进行编辑/评论。我希望这对您可以采用的方法有所了解。

编辑:为清楚起见,您应该在递归的每个级别上在水平和垂直细分之间切换。