如何生成彼此不相交的正方形(随机定位、大小相等、随机旋转)?

2024-01-25

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

我的一个随机正方形的示例 https://i.stack.imgur.com/HXIrh.png

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 背景(我是环境工程专业),而且我对编码非常缺乏经验。请给我任何建议,让我尝试解决这个问题!


数学背景

1. 代数

  • 1st degree function (or [Wikipedia]: Linear function https://en.wikipedia.org/wiki/Linear_function) is a function ([Wikipedia]: function https://en.wikipedia.org/wiki/Function_(mathematics)) whose:
    • 表达式可以写为:f(x) = a * x + b (a and b常数,x多变的)
    • 图形表示是一条直线xOy plane ([维基百科]:笛卡尔坐标系 https://en.wikipedia.org/wiki/Cartesian_coordinate_system)

2. 平面几何

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

    • abscissa or 横坐标或(简单地)x
    • ordinate or 纵坐标或(简单地)y
  • 平面上的点分布在二维空间中

  • 平面上的每个点都可以通过其唯一标识x and y

  • 平面上的一些点可能具有一些共同的特征:例如直线上的一堆点...直线上的点满足直线equation(这个表达式通常定义为${function(来自上一段)结果} = ${值})

  • So, in short: for a point P0(x0, y0), if y0 == f(x0), the point is located on that straight line (and more: depending on y0 being greater / lower than f(x0), P0 is located above / below the straight line in the xOy plane). Again, I want to state that for non linear functions, y = f(x) still applies (as it's the general equation formula), but other operators (e.g. <, >) don't

    • !重要的提示 !:这里讨论的一切都适用于各种函数(不想说all),但我仅限于线性函数,为了清楚起见
  • A straight line is determined by 2 distinct points (e.g. P0(x0, y0), P1(x1, y1)) - the equation for that straight line would be y = a * x + b (in our example: y = ((y0 - y1) / (x0 - x1)) * x + (y0 - x0 * ((y0 - y1) / (x0 - x1)))); !! Of course it worth mentioning the "vertical" (parallel to Oy) line which is !! not a function !!

  • Example: having 2 distinct parallel (non Bolyai :) ) lines: f0(x) = a * x + b0 and f1(x) = a * x + b1 (a is the same for both lines - this is the condition for them to be parallel) and an external point P0(x0, y0) (that obviously doesn't belong to any of the lines). How to determine if P0 is between the 2 lines? Well, the point must be above (the lower) one and below the other (the higher one). Translated into math (considering f0 being the lower one):

    • y0 > f0(x0) (y0 - f0(x0) > 0)
    • y0 < f1(x0) (y0 - f1(x0) < 0)

    From the above observations (and there may be more wisdom), this is the condition that the point coordinates should satisfy: (y0 - f0(x0)) * (y0 - f1(x0)) < 0

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

代码00.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()

Notes:

  • 我没有与绘图库之前(其实我pip install为此任务编辑了它)

  • 普通的留言:

    • 点由表示其坐标的元组表示:(x, y)
    • 正方形是由 4 个点组成的元组(p0、p1、p2、p3)
  • 获取函数deg1:

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

    • 确定一个点是否在正方形内
    • 使用上面解释的逻辑,除了
    • 对于方形边缘平行于的特殊情况Ox and Oy当它使用一些简单的算术运算时
  • 方块重叠:

    • 确定两个方块是否重叠(我确信有更快的“算法”)
    • Checks if any of the 1st square corners are inside the 2nd one
    • The other way around: checks if any of the 2nd square corners are inside the 1st one
    • Since the above 2 checks are not enough (imagine a regular [Wikipedia]: Octagon https://en.wikipedia.org/wiki/Octagon and unifying its vertices every 2nd one: there will be 2 squares neither having its corners in the other one, but sharing their "central" areas), also check that one square's center is inside the other one
  • 生成随机点:

    • 在给定的边界框中生成一个点
    • 安全裕度指定生成的点距任何边界框边的(最小)距离,以便任何以此点为角的正方形都完全适合边界框
  • 生成随机角度:

    • 生成 0 和 ( 之间的随机角度π / 2)
  • 生成随机方:

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

    • Converts a square (from a tuple of points) to matplotlib format (2 tuples consisting of xs and ys with the 1st element duplicated as the last)
  • main:

    • 主要功能
  • __generation_monitor (0):

    • 用于分析的内部函数
  • 为了改变方块的数量,修改最大平方数

Output:

[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, 生成值输出中将匹配方块的数量(最大平方数)
  • 如果增加最大平方数假设值高于50,生成值的数量将呈指数增长(生成它们所需的时间也会呈指数增长),并且程序将进入无限循环的机会(因为它将无法生成不与另一个正方形重叠的正方形)一)也会成长
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何生成彼此不相交的正方形(随机定位、大小相等、随机旋转)? 的相关文章

随机推荐