【问题标题】:How to generate squares (randomly located, equally sized, randomly rotated) that don't intersect each other?如何生成不相交的正方形(随机定位、大小相等、随机旋转)?
【发布时间】:2018-02-15 07:33:32
【问题描述】:

我一直致力于在 1x1 网格上生成一层随机旋转和放置的正方形。我已经能够生成一个随机放置并在网格上旋转的正方形,但我不确定如何改进代码以生成更多不相互交叉的随机正方形。当前代码如下:

Example of my One Randomized Square

from math import cos, pi, sin
from random import randint
from matplotlib.mlab import frange
from matplotlib.pyplot import plot, axis, show

def flake_position_layer1(): #Determines the initial position of one corner of the square
    x0 = randint(0, 100) / 100
    y0 = randint(0, 100) / 100
    theta = randint(0, 90) * pi / 180 #Angle of rotation for the square
    return x0, y0, theta


def flake_shape(): #generates the other 3 corners of the square
    x0, y0, z, theta = flake_position_layer1()
    x1 = x0 + (0.1 * cos(theta))
    x2 = x1 + (0.1 * cos((90 * pi/180) + theta))
    x3 = x2 + (0.1 * cos((180 * pi/180) + theta))
    y1 = y0 + (0.1 * sin(theta))
    y2 = y1 + (0.1 * sin((90 * pi/180) + theta))
    y3 = y2 + (0.1 * sin((180 * pi/180) + theta))
    return x0, x1, x2, x3, y0, y1, y2, y3


def display(): #connects the 4 corners on a plot
    x0, x1, x2, x3, y0, y1, y2, y3 = flake_shape()
    return plot([x0, x1, x2, x3, x0], [y0, y1, y2, y3, y0])


display()
axis([0,1,0,1]) #1x1 grid
show()

我没有 CS 背景(我是环境工程专业的学生),而且我对编码非常缺乏经验。请给我任何建议,让我尝试解决这个问题!

【问题讨论】:

  • 是否可以考虑正方形周围的圆形信封?在这种情况下,您可以保存每个正方形的中心并验证两个中心之间的距离是否 > 2*z。
  • 使用拒绝抽样来生成更多的正方形:if(current square overlaps) reject(); else add_square_to_list();
  • 正方形需要多大?如果您可以使它们小于~70% (1/√2) 网格大小,它们将永远不会重叠。

标签: python python-2.7 python-3.x


【解决方案1】:

数学背景

1。代数

2。平面几何

  • 一个平面由(无限)个点组成:让我们通过它的坐标来引用一个点,它可以被称为:

    • 横坐标水平坐标或(简单)x
    • 纵坐标纵坐标或(简单)y
  • 平面中的点分布在二维上

  • 在平面上,每个点都可以通过它的xy

    唯一标识
  • 平面上的一些点可能有一些共同的特征:例如直线上的一堆点... 直线上的一个点满足直线 方程 (这是一个表达式,通常定义为 ${函数(来自上一段)结果} = ${value}

  • 所以,简而言之:对于一个点 P0(x0, y0),如果<strong>y<sub>0</sub> == f(x<sub>0</sub>)</strong>,点位于那条直线上(以及更多:取决于y0 大于 / 低于 f(x0), P0 位于 xOy 平面中的直线上方 / 下方 )。再次声明,对于非线性函数,y = f(x) 仍然适用(因为它是一般方程公式),但其他运算符(例如 >)不要

    • !重要提示!:这里讨论的所有内容都适用于各种函数(不想说全部),但我仅限于线性函数,为了清楚起见
  • 一条直线由2个不同点确定(例如P0(x0, y0), P1(x1, y1)) - 方程因为那条直线是y = a * x + b(在我们的例子中:<strong>y = ((y<sub>0</sub> - y<sub>1</sub>) / (x<sub>0</sub> - x<sub>1</sub>)) * x + (y<sub>0</sub> - x<sub>0</sub> * ((y<sub>0</sub> - y<sub>1</sub>) / (x<sub>0</sub> - x<sub>1</sub>)))</strong>); !! 当然值得一提的是“垂直”(平行于Oy)线,即!!不是函数!!

  • 示例:有 2 个不同平行(非 Bolyai :))行:f<sub>0</sub>(x) = a * x + b<sub>0</sub>f<sub>1</sub>(x) = a * x + b<sub>1</sub>a 对于两条线是相同的 - 这是它们平行的条件)和外部点 P0(x0, y0) (显然不属于任何行)。如何确定 P0 是否在两行之间?好吧,该点必须高于(较低的)一个,低于另一个(较高的)。翻译成数学(考虑到 f0 是较低的):

    • <strong>y<sub>0</sub> &gt; f<sub>0</sub>(x<sub>0</sub>)</strong> (y<sub>0</sub> - f<sub>0</sub>(x<sub>0</sub>) &gt; 0)
    • <strong>y<sub>0</sub> &lt; f<sub>1</sub>(x<sub>0</sub>)</strong> (y<sub>0</sub> - f<sub>1</sub>(x<sub>0</sub>) &lt; 0)

    从以上观察(可能还有更多智慧),这是点坐标应该满足的条件:<strong>(y<sub>0</sub> - f<sub>0</sub>(x<sub>0</sub>)) * (y<sub>0</sub> - f<sub>1</sub>(x<sub>0</sub>)) &lt; 0</strong>

  • 更进一步:一个正方形由 2 组平行线(它的边)组成;如果一个点在每对线对之间,则该点在正方形中。

code00.py

#!/usr/bin/env python3

import sys
from random import random, seed
from math import pi, sin, cos, sqrt
import matplotlib.pyplot as plt

pi_2 = pi / 2

MINX = MINY = 0
MAXX = MAXY = 1
DEFAULT_SIDE = 0.1
DEFAULT_SAFETY_MARGIN = DEFAULT_SIDE * sqrt(2)
MAX_SQUARES = 30

__global_generation_counter = 0


def get_func_deg1(p0, p1):
    (x0, y0), (x1, y1) = p0, p1
    if x0 == x1:
        return None
    a = (y0 - y1)/(x0 - x1)
    b = y0 - x0 * a
    return lambda x: a * x + b


def is_point_in_square(p, sq):
    x, y = p
    p0, p1, p2, p3 = sq
    side_func0 = get_func_deg1(p0, p1)
    side_func1 = get_func_deg1(p1, p2)
    side_func2 = get_func_deg1(p2, p3)
    side_func3 = get_func_deg1(p3, p0)
    if not side_func0 or not side_func1 or not side_func2 or not side_func3:
        xmin = min(p0[0], p2[0])
        xmax = max(p0[0], p2[0])
        ymin = min(p0[1], p2[1])
        ymax = max(p0[1], p2[1])
        return xmin <= x <= xmax and ymin <= y <= ymax
    return ((y - side_func0(x)) * (y - side_func2(x))) <= 0 and \
           ((y - side_func1(x)) * (y - side_func3(x))) <= 0


def squares_overlap(square0, square1):
    for p0 in square0:
        if is_point_in_square(p0, square1):
            return True
    for p1 in square1:
        if is_point_in_square(p1, square0):
            return True
    xc0 = (square0[0][0] + square0[2][0]) / 2
    yc0 = (square0[0][1] + square0[2][1]) / 2
    if is_point_in_square((xc0, yc0), square1):
        return True
    # The "reverse center check" not needed, since squares are congruent
    """
    xc1 = (square1[0][0] + square1[2][0]) / 2
    yc1 = (square1[0][1] + square1[2][1]) / 2
    if is_point_in_square((xc1, yc1), square0):
        return True
    """
    return False


def __generation_monitor():
    global __global_generation_counter
    __global_generation_counter += 1


def generate_random_point(minx=MINX, miny=MINY, maxx=MAXX, maxy=MAXY, safety_margin=DEFAULT_SAFETY_MARGIN):
    if maxx - minx < 2 * safety_margin or maxy - miny < 2 * safety_margin:
        print("MUEEE")
        safety_margin = 0
    x = safety_margin + random() * (maxx - minx - 2 * safety_margin)
    y = safety_margin + random() * (maxy - miny - 2 * safety_margin)
    __generation_monitor()
    return x, y


def generate_random_angle(max_val=pi_2):
    return random() * max_val


def generate_random_square(side=DEFAULT_SIDE, squares_to_avoid=()):
    while 1:
        restart = False
        x0, y0 = generate_random_point()

        angle = generate_random_angle()
        x1 = x0 + side * cos(angle)
        y1 = y0 + side * sin(angle)

        angle += pi_2
        x2 = x1 + side * cos(angle)
        y2 = y1 + side * sin(angle)

        angle += pi_2
        x3 = x2 + side * cos(angle)
        y3 = y2 + side * sin(angle)

        ret = (x0, y0), (x1, y1), (x2, y2), (x3, y3)
        for square in squares_to_avoid:
            if squares_overlap(ret, square):
                restart = True
        if restart:
            continue
        return ret


def square_to_plot(square):
    xs, ys = zip(square[0], square[1], square[2], square[3])
    return xs + (xs[0],), ys + (ys[0],)


def main():
    seed()
    squares = list()
    allow_overlapping = False # CHANGE to True to allow square to overlap
    for _ in range(MAX_SQUARES):
        #print("Generating:", _)
        if allow_overlapping:
            square = generate_random_square()
        else:
            square = generate_random_square(squares_to_avoid=squares)
        squares.append(square)
    plot_squares = tuple()
    for sq in squares:
        plot_squares += square_to_plot(sq)
    print("STATS:\n    Squares: {}\n    Allow  overlapping: {}\n    Generated values: {}".format(MAX_SQUARES, allow_overlapping, __global_generation_counter))
    plt.plot(*plot_squares)
    plt.axis([MINX, MAXX, MINY, MAXY])
    plt.show()


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

注意事项

  • 我之前没有使用过 matplotlib(实际上,我 pip installed 它是为了这个任务)

  • 一般cmets:

    • 一个点由一个表示其坐标的元组表示:(x, y)
    • 正方形是由 4 个点组成的元组 (p0, p1, p2, p3)
  • get_func_deg1

    • 返回描述包含作为参数给出的 2 个点的行的函数
    • 如果 2 个点在平行于 Oy 的线上(没有“正常”函数来描述它),只需返回 None
  • is_point_in_square

    • 判断一个点是否在正方形内
    • 使用上述逻辑,除了
    • 当使用一些简单的算术运算时,方形边平行于 OxOy 的特殊情况
  • squares_overlap

    • 确定 2 个正方形是否重叠(我确信有更快的“算法”)
    • 检查第 1st 个方角是否在第 2nd 个方角内
    • 反过来:检查第 2nd 个方角中的任何一个是否在第一个st 个内
    • 由于上述 2 次检查还不够(想象一个常规的 [Wikipedia]: Octagon 并每隔 2nd 一个统一它的顶点:将有 2 个正方形既没有角在另一个正方形中,但共享他们的“中心”区域),还要检查一个方格的中心是否在另一个方格内
  • generate_random_point

    • 在给定的边界框中生成一个点
    • safety_margin 指定生成的点应该远离任何边界框边的(最小)距离,以便任何以该点为角的正方形都完全适合边界框
  • generate_random_angle

    • 生成一个介于 0 和 (π / 2) 之间的随机角度
  • generate_random_square

    • 生成一个随机点,一个随机角度,并从那里开始,使用指定的边构造一个正方形
    • squares_to_avoid 是一个正方形列表。生成正方形后,将对照该列表中的每个正方形对其进行检查。如果 2 个方块重叠,则重新生成方块
  • square_to_plot

    • 将正方形(从点的元组)转换为 matplotlib 格式(由 xs 和 ys 组成的 2 个元组,其中 1 st 元素作为最后一个重复)
  • 主要

    • 主要功能
  • __generation_monitor (0)

    • 用于分析的内部函数
  • 为了改变正方形的数量,修改MAX_SQUARES

输出

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q046081491]> "e:\Work\Dev\VEnvs\py_064_03.05.04_test0\Scripts\python.exe" code00.py
STATS:
    Squares: 30
    Allow  overlapping: False
    Generated values: 1135

关于正方形生成的几句话

  • 从输出中可以看出,对于 30 个显示的正方形,生成了 1135(在本次运行中)。那是因为它们重叠
  • 如果从 main allow_overlapping = True 更改,输出中的生成值 将匹配正方形的数量 (MAX_SQUARES)
  • 如果将 MAX_SQUARES 增加到高于 50 的值,则生成的值的数量将呈指数增长(生成它们所需的时间也会增加),并且机会程序将进入无限循环(因为它无法生成不与另一个正方形重叠的正方形)也会增长

【讨论】:

    【解决方案2】:

    好的,这是我在 shapely 包的帮助下得出的结论。安装帮助在底部。最终结果:

    代码演练

    • distance 是一个辅助函数,它使用 shapely 中的 Point 类来查找两个坐标之间的距离。只是一个辅助函数供以后使用。
    • Square 实例化一个新的多边形。它有 4 个角,每个角是 (x,y) 对,一个坐标作为其中心,一个标量值等于其对角线距离的一半。
    • test_overlap 的标题非常不言自明。但从逻辑上讲,它的作用是:找到两个形状之间从中心到中心的距离。然后找到每个形状的半对角线之和。如果中心到中心的距离大于总和,则正方形不能重叠。
    • Squares 从一个空容器(空列表)开始,并尝试向其中添加正方形。但对于每个可能的新添加,它首先测试与现有方块没有重叠。

    代码

    import math
    import random
    
    from shapely.geometry import Polygon, Point
    
    
    def distance(a, b):
        return Point(a).distance(Point(b))
    
    
    class Square(object):
        def __init__(self):
            self.x0, self.y0 = random.random(), random.random()
            theta = random.randint(0, 90) * math.pi / 180  # Angle of rotation
            self.x1 = self.x0 + (0.1 * math.cos(theta))
            self.x2 = self.x1 + (0.1 * math.cos((90 * math.pi/180) + theta))
            self.x3 = self.x2 + (0.1 * math.cos((180 * math.pi/180) + theta))
            self.y1 = self.y0 + (0.1 * math.sin(theta))
            self.y2 = self.y1 + (0.1 * math.sin((90 * math.pi/180) + theta))
            self.y3 = self.y2 + (0.1 * math.sin((180 * math.pi/180) + theta))
            self.corners = ((self.x0, self.y0), (self.x1, self.y1), 
                            (self.x2, self.y2), (self.x3, self.y3))
    
        @property
        def center(self):
            """(x, y) of the center of the polygon."""
            return Polygon(self.corners).centroid.coords[0]
    
        @property
        def half_diag(self):
            """The distance of 1/2 the shape's diagonal (center-to-corner)."""
            p0, p1, p2, p3 = self.corners
            return 0.5 * distance(p0, p1) * math.sqrt(2)
    
    
    def test_overlap(square1, square2):
        """Do two shapes overlap?  
    
        Note this is a 'conservative' test.  May return True if they do not
        (false positive), but will never return False if they do (false negative).
        """
    
        # Distance between two centers
        ctc = distance(square1.center, square2.center)
        # Sum of half-diagonals
        halfdiags = square1.half_diag + square2.half_diag
        res = ctc < halfdiags
        return res
    
    
    class Squares(object):
        def __init__(self):
            self.squares = []
    
        def add_square(self):
            new_square = Square()
            if not self.squares:
                # Initial empty list/container - just add without any tests
                self.squares.append(new_square)
            else:
                while True:
                # Test that new_square overlaps with existing
                    res = [test_overlap(square, new_square) for square in self.squares]
                    if any(res):
                        # We have at least 1 case of overlap (1 True)
                        new_square = Square()
                    else:
                        # Safe to add
                        self.squares.append(new_square)
                        break
    
        def plot_squares(self):
            for square in self.squares:
                (x0, y0), (x1, y1), (x2, y2), (x3, y3) = square.corners
                plt.plot([x0, x1, x2, x3, x0], [y0, y1, y2, y3, y0])  
    

    示例

    import itertools
    %matplotlib inline
    for _ in itertools.repeat(None, 10):
        # Add 10 squares; you could also just build this into the class
        sqs.add_square()
    sqs.plot_squares()
    

    安装shapely

    如果您还没有安装 Anaconda 发行版,请安装。然后只需使用conda-forge 安装即可。来自cmd 运行:

    conda config --add channels conda-forge
    conda install shapely
    

    明显不足

    在某个时候,您的正方形容器会被填满,剩下的空间很小,可以添加新的形状。即使有可用空间,该功能也基本上是反复试验,因此在高形状计数时需要很长时间。目前,这种情况发生在大约 20-25 个方格(在 1.0x1.0 的盒子中)。

    【讨论】:

      【解决方案3】:

      你需要一个函数来判断两个立方体是否相交。

      from math import cos, pi, sin
      from random import random
      from matplotlib.mlab import frange
      from matplotlib.pyplot import plot, axis, show,axes
      
      LEN = 0.1
      
      
      def rotate(point, theta):
          x = point[0]
          y = point[1]
          x_ = x * cos(theta) + y * sin(theta)
          y_ = - x * sin(theta) + y * cos(theta)
          return x_, y_
      
      
      class CUBE(object):
          def __init__(self, x, y, theta):
              self.corner = [(LEN / 2, LEN / 2),
                             (-LEN / 2, LEN / 2),
                             (-LEN / 2, -LEN / 2),
                             (LEN / 2, -LEN / 2)
                             ]
              self.theta = theta
              self.x = x
              self.y = y
              for i in range(4):
                  self.corner[i] = rotate(self.corner[i], theta)
                  self.corner[i] = (self.corner[i][0] + x,
                                    self.corner[i][1] + y)
      
      
      def is_include(cube, point):
          point = [point[0] - cube.x, point[1] - cube.y]
          point = rotate(point, -cube.theta)
          if (point[0] < -LEN / 2
                  or point[0] > LEN / 2
                  or point[1] < -LEN / 2
                  or point[1] > LEN / 2
                  ):
              return False
          else:
              return True
      
      
      def is_intersect(cube1, cube2):
          if (any([is_include(cube1, point) for point in cube2.corner])
                  or any([is_include(cube2, point) for point in cube1.corner])
                  or is_include(cube1, (cube2.x, cube2.y))):
              return True
          else:
              return False
      
      
      def plot_cube(cube,n):
          plot(
              [cube.corner[i][0] for i in [0, 1, 2, 3, 0]],
              [cube.corner[i][1] for i in [0, 1, 2, 3, 0]])
          ax = axes()
          ax.text(cube.x,cube.y,str(n))
      
      
      def display(cubelist):  # connects the 4 corners on a plot
          for i,cube in enumerate(cubelist):
              plot_cube(cube,i)
          axis([0, 1, 0, 1])  # 1x1 grid
          show()
      
      
      cubelist = []
      
      for i in range(100):
          x0 = random()
          y0 = random()
          theta = random() * pi
          cube = CUBE(x0, y0, theta)
          if any(is_intersect(cube,cb) for cb in cubelist):
              continue
          else:
      
              cubelist.append(cube)
      
      display(cubelist)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-02-20
        • 1970-01-01
        • 2019-08-17
        • 1970-01-01
        • 1970-01-01
        • 2012-10-11
        • 1970-01-01
        相关资源
        最近更新 更多