如何生成随机凸多边形?

时间:2011-07-20 06:54:00

标签: random geometry

我正在尝试设计一种生成随机2D凸多边形的方法。它必须具有以下属性:

  • 坐标应为整数;
  • 多边形应位于带角(0,0)和(C,C)的正方形内,其中给出C;
  • 多边形应具有接近给定数字N的顶点数。

例如,生成具有10个顶点并位于square [0..100] x [0..100]内的随机多边形。

使这项任务变得困难的原因是坐标应该是整数。

我尝试的方法是在给定方格中生成随机点集并计算这些点的凸包。但是与N相比,合成的凸包是非常小的顶点。

有什么想法吗?

7 个答案:

答案 0 :(得分:2)

这不完整,但它可能会给你一些想法。

如果N< 3.生成具有N个顶点的单位圆,并随机旋转[0..90]度。

从原点向外随机挤出每个顶点,并使用每对相邻顶点和原点之间的叉积符号来确定凸度。这是在速度和质量之间进行权衡的步骤。

设置顶点后,找到原点最大的顶点。将每个顶点除以该幅度以对多边形进行标准化,然后将其向上缩放(C / 2)。转换为(C / 2,C / 2)并转回整数。

答案 1 :(得分:1)

一个简单的算法是:

  1. 以随机线(两个顶点和两个边多边形)开始
  2. 获取多边形的随机边缘
  3. 在此边缘创建新的随机点
  4. 取一条垂直于E的线L通过点P.通过计算线T与由E相邻的两条边定义的线之间的交点,计算凸度未破坏时P的最大偏移量。
  5. 在该范围内随机偏移P点。
  6. 如果积分不足,请从2开始重复。

答案 2 :(得分:1)

如果有人对它的Python端口感兴趣,在@Mangara answer之后有JAVA implementation

import random
from math import atan2


def to_convex_contour(vertices_count,
                      x_generator=random.random,
                      y_generator=random.random):
    """
    Port of Valtr algorithm by Sander Verdonschot.

    Reference:
        http://cglab.ca/~sander/misc/ConvexGeneration/ValtrAlgorithm.java

    >>> contour = to_convex_contour(20)
    >>> len(contour) == 20
    True
    """
    xs = [x_generator() for _ in range(vertices_count)]
    ys = [y_generator() for _ in range(vertices_count)]
    xs = sorted(xs)
    ys = sorted(ys)
    min_x, *xs, max_x = xs
    min_y, *ys, max_y = ys
    vectors_xs = _to_vectors_coordinates(xs, min_x, max_x)
    vectors_ys = _to_vectors_coordinates(ys, min_y, max_y)
    random.shuffle(vectors_ys)

    def to_vector_angle(vector):
        x, y = vector
        return atan2(y, x)

    vectors = sorted(zip(vectors_xs, vectors_ys),
                     key=to_vector_angle)
    point_x = point_y = 0
    min_polygon_x = min_polygon_y = 0
    points = []
    for vector_x, vector_y in vectors:
        points.append((point_x, point_y))
        point_x += vector_x
        point_y += vector_y
        min_polygon_x = min(min_polygon_x, point_x)
        min_polygon_y = min(min_polygon_y, point_y)
    shift_x, shift_y = min_x - min_polygon_x, min_y - min_polygon_y
    return [(point_x + shift_x, point_y + shift_y)
            for point_x, point_y in points]


def _to_vectors_coordinates(coordinates, min_coordinate, max_coordinate):
    last_min = last_max = min_coordinate
    result = []
    for coordinate in coordinates:
        if _to_random_boolean():
            result.append(coordinate - last_min)
            last_min = coordinate
        else:
            result.append(last_max - coordinate)
            last_max = coordinate
    result.extend((max_coordinate - last_min,
                   last_max - max_coordinate))
    return result


def _to_random_boolean():
    return random.getrandbits(1)

答案 3 :(得分:0)

您的初始方法是正确的 - 计算凸包是您满足随机性,凸度和整数的唯一方法。

我能想到优化算法以获得“更多分数”的唯一方法是将它们围绕一个圆而不是完全随机地组织。您的点应该更可能靠近广场的“边缘”而不是靠近中心。在中心,概率应为〜0,因为多边形必须是凸的。

一个简单的选择是为您的点设置最小半径 - 可能是C / 2或C * 0.75。计算C方格的中心,如果一个点太近,则将其移离中心,直到达到最小距离。

答案 4 :(得分:0)

这是我所知道的最快的算法,它以相同的概率生成每个凸多边形。输出正好有N个顶点,运行时间为O(N log N),因此它可以非常快速地生成大型多边形。

  • 生成两个列表XY,其中包含0到C之间的N个随机整数。确保没有重复项。
  • 排序XY并存储其最大和最小元素。
  • 将其他(不是最多或最小)元素随机分为两组:X1X2,以及Y1Y2
  • 在这些列表的开头和结尾重新插入最小和最大元素(minXX1X2maxX结尾,等)。
  • 查找连续差异(X1[i + 1] - X1[i]),撤消第二组(X2[i] - X2[i + 1])的顺序。将它们存储在列表XVecYVec
  • 随机化(随机播放)YVec并将每对XVec[i]YVec[i]视为2D向量。
  • 按角度对这些矢量进行排序,然后将它们端对端放置以形成多边形。
  • 将多边形移动到原始的最小和最大坐标。

此处提供动画和Java实现:Generating Random Convex Polygons

该算法基于Pavel Valtr的一篇论文:“Probability that n random points are in convex position。”离散& Computational Geometry 13.1(1995):637-643。

答案 5 :(得分:0)

由于@Mangara's answer@Azat's answer的帮助,我也建立了ruby端口:

#!/usr/bin/env ruby
# frozen_string_literal: true

module ValtrAlgorithm
  module_function def random_polygon(length)
    raise ArgumentError, "length should be > 2" unless length > 2

    min_x, *xs, max_x = Array.new(length) { rand }.sort
    min_y, *ys, max_y = Array.new(length) { rand }.sort
    # Divide the interior points into two chains and
    # extract the vector components.
    vec_xs = to_random_vectors(xs, min_x, max_x)
    vec_ys = to_random_vectors(ys, min_y, max_y).
      # Randomly pair up the X- and Y-components
      shuffle
    # Combine the paired up components into vectors
    vecs = vec_xs.zip(vec_ys).
      # Sort the vectors by angle, in a counter clockwise fashion. Remove the
      # `-` to make it clockwise.
      sort_by { |x, y| - Math.atan2(y, x) }

    # Lay them end-to-end
    point_x = point_y = 0
    min_polygon_x = min_polygon_y = 0
    points = []
    vecs.each do |vec_x, vec_y|
      points.append([vec_x, vec_y])
      point_x += vec_x
      point_y += vec_y
      min_polygon_x = [min_polygon_x, point_x].min
      min_polygon_y = [min_polygon_y, point_y].min
    end
    shift_x = min_x - min_polygon_x
    shift_y = min_y - min_polygon_y
    result = points.map { |point_x, point_y| [point_x + shift_x, point_y + shift_y] }
    # Append first point to make it a valid linear ring
    result << result.first
  end

  private def to_random_vectors(coordinates, min, max)
    last_min = last_max = min
    ary = []
    coordinates.each do |coordinate|
      if rand > 0.5
        ary << coordinate - last_min
        last_min = coordinate
      else
        ary << last_max - coordinate
        last_max = coordinate
      end
    end
    ary << max - last_min << last_max - max
  end
end

答案 6 :(得分:0)

这是另一个使用 numpy 的 Valtr 算法版本。 :)

import numpy as np
import numpy.typing and npt
import random


def generateConvex(n: int) -> npt.NDArray[np.float64]:
    '''
    Generate convex shappes according to Pavel Valtr's 1995 alogrithm. Ported from
    Sander Verdonschot's Java version, found here:
    https://cglab.ca/~sander/misc/ConvexGeneration/ValtrAlgorithm.java
    '''
    # initialise random coordinates
    X_rand = np.sort(np.random.random(n))
    Y_rand = np.sort(np.random.random(n))
    X_new = np.zeros(n)
    Y_new = np.zeros(n)

    # divide the interior points into two chains
    lastTop = lastBot = X_rand[0]
    lastLeft = lastRight = Y_rand[0]
    for i in range(1, n - 1):
        if random.getrandbits(1):
            X_new[i] = X_rand[i] - lastTop
            lastTop = X_rand[i]
            Y_new[i] = Y_rand[i] - lastLeft
            lastLeft = Y_rand[i]
        else:
            X_new[i] = lastBot - X_rand[i]
            lastBot = X_rand[i]
            Y_new[i] = lastRight - Y_rand[i]
            lastRight = Y_rand[i]
    X_new[0] = X_rand[n - 1] - lastTop
    X_new[n - 1] = lastBot - X_rand[n - 1]
    Y_new[0] = Y_rand[n - 1] - lastLeft
    Y_new[n - 1] = lastRight - Y_rand[n - 1]

    # randomly combine x and y, and sort by polar angle
    np.random.shuffle(Y_new)
    vertices = np.stack((X_new, Y_new), axis=-1)
    vertices = vertices[np.argsort(np.arctan2(vertices[:, 1], vertices[:, 0]))]

    # arrange the points end to end to form a polygon
    x_accum = y_accum = 0
    for i, [x, y] in enumerate(vertices):
        vertices[i] = [x_accum, y_accum]
        x_accum += x
        y_accum += y

    # move the polygon to the original min and max coordinates
    vertices[:, 0] += X_rand[0] - np.min(vertices[:, 0] 
    vertices[:, 1] += Y_rand[0] - np.min(vertices[:, 1]

    return vertices