组合按特定顺序的相邻 3D 多边形

Posted

技术标签:

【中文标题】组合按特定顺序的相邻 3D 多边形【英文标题】:Combine adjacent 3D polygons that are in certain order 【发布时间】:2020-09-02 07:55:02 【问题描述】:

给定两个具有相同缠绕顺序(逆时针或顺时针)的三维多边形:

poly1 = np.array([[120787.075999871, 491779.675000143, -2.0699999332428], [120784.319999829, 491781.831000042, 5.96999979019165], [120784.319999829, 491781.831000042, -2.0699999332428], [120787.075999871, 491779.675000143, -2.0699999332428]])
poly2 = np.array([[120787.075999871, 491779.675000143, -2.03999996185303], [120787.075999871, 491779.675000143, 5.90999984741211], [120784.319999829, 491781.831000042, 5.96999979019165], [120787.075999871, 491779.675000143, -2.03999996185303]])

如何在保持顺序的同时将这些相邻的多边形组合/合并成一个多边形(在 Python 中)。

这是绘制两个相邻多边形的代码:

import matplotlib.pyplot as plt, numpy as np
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot(poly1[:,0],poly1[:,1],poly1[:,2])
ax.plot(poly2[:,0],poly2[:,1],poly2[:,2])
ax.view_init(45, 45) 
plt.show()

【问题讨论】:

两个数组中的第一个点和最后一个点不应该完全相同以对多边形进行标记吗? 【参考方案1】:

poly1poly2 中点的坐标不完全匹配(尽管它们在图中看起来像同一个点)。 我猜你希望算法使用“足够接近”的值(即,如果点之间的距离小于给定的容差,例如0.1,它们被认为是同一个点)。

您可以通过找到公共边并将其移除来连接两个多边形。

为了找到公共边,我们首先确定多边形的哪些点对两个多边形是公共的。我查看了这个post 并选择了 cKDTree 方法来做到这一点。 它的工作原理是

    为一个多边形的每个点找到另一个多边形中最近的相邻点 比较这两个点之间的距离。如果距离小于我们设定的容差,我们会将它们视为同一个点,并且对两个多边形都是公共的。

一旦您确定了哪些点是共同的,您就可以验证它们是否相邻。如果是,则您已找到要删除的边缘。

生成的多边形将由以下组成

来自poly1的所有点 poly2 中不形成公共边的点

代码如下:

import matplotlib.pyplot as plt, numpy as np
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import cKDTree

poly1 = np.array([[120787.075999871, 491779.675000143, -2.0699999332428], [120784.319999829, 491781.831000042, 5.96999979019165], [120784.319999829, 491781.831000042, -2.0699999332428], [120787.075999871, 491779.675000143, -2.0699999332428]])
poly2 = np.array([[120787.075999871, 491779.675000143, -2.03999996185303], [120787.075999871, 491779.675000143, 5.90999984741211], [120784.319999829, 491781.831000042, 5.96999979019165], [120787.075999871, 491779.675000143, -2.03999996185303]])


def is_close(a, b, tolerance):
    # Get closest distances for each pt in a
    dist = cKDTree(b).query(a, k=1)[0] # k=1 selects closest one neighbor

    # Check the distances against the given tolerance value
    return dist <= tolerance

def find_consecutive_true_values(arr):
    i = 0
    while i < len(arr) - 1:
        if arr[i] and arr[i+1]:
            return i
        i+=1
    raise Exception('No common edge found')

# Find points in poly1, which are closer than given tolerance to any point in poly2
# and vice versa
tolerance = 0.1
points_in_poly1_close_to_poly2 = is_close(poly1, poly2, tolerance)
points_in_poly2_close_to_poly1 = is_close(poly2, poly1, tolerance)

# Scan each array for two adjacent true values (points at those two indices 
# form an edge which is common to both polygons and which we want to remove).
# Idx1 (resp. idx2) will contain the index of the first point of that common edge in poly1 (resp. poly2)
idx1 = find_consecutive_true_values(points_in_poly1_close_to_poly2)
idx2 = find_consecutive_true_values(points_in_poly2_close_to_poly1)

#Split poly1 into two parts:
#  first part contains points from the start up to the first point of the common edge (inclusive)
#  second part contains points from the second point of the common edge to the end
poly1_part1 = poly1[:idx1+1]
poly1_part2 = poly1[idx1+1:]

#Remove common edge from poly2, depending on where it is located, we end up with one or two parts
if idx2 == len(poly2) - 2:
    poly2_part1 = poly2[1:len(poly2) - 2]
    poly2_part2 = None
elif idx2 == 0:
    poly2_part1 = poly2[2:len(poly2) - 1]
    poly2_part2 = None
else:
    poly2_part1 = poly2[idx2+2:]
    poly2_part2 = poly2[1:idx2]

#Create the resulting polygon by concatenating the individual parts (poly2_part2 may be empty)
if(poly2_part2 is None):
    poly = np.concatenate((poly1_part1, poly2_part1, poly1_part2))
else:
    poly = np.concatenate((poly1_part1, poly2_part1, poly2_part2, poly1_part2))


fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot(poly[:,0], poly[:,1], poly[:,2])


ax.view_init(45, 45) 
plt.show()

(代码远非惯用,如果您了解您的 Python,请随时编辑它:))

【讨论】:

感谢您详细解释问题和代码。这真的很有帮助,解决了我的编程问题! 您好,代码不适用于这两个多边形。你知道为什么吗? Poly1 = NP.Array([[121364.688,485837.156,485837.156,1.342],[121364.656,485837.156,17.217],[121370.625],[121370.625,485836.688,17.529] [121364.688,485837.156,1.344])Poly2 = np.array([[121370.625, 485836.281, 1.259], [121364.688, 485837.156, 1.344], [121370.625, 485836.688, 17.529], [121370.625, 485836.281])]>] 在 poly1 中,前两个顶点代表同一个点(在一个小的公差内):[121364.688, 485837.156, 1.344] vs [121364.656, 485837.156, 1.342]【参考方案2】:

我刚刚做了一个简单的解决方案,它允许将两个多边形与至少 1 个公共点和可能创建的连续线组合起来。 现在,没有宽容(共同点必须相同),但在那些日子里,我会将它添加为函数的参数,因为我完全喜欢它。我将添加合并多边形一些常见线条的可能性,但我需要时间。大概,我会把它推送到 GitHub 上并在此处粘贴链接。

修改的多边形(poly2 中的第一个和最后一个元素与poly1 中的相同):

poly1 = np.array([[120787.075999871, 491779.675000143, -2.0699999332428], [120784.319999829, 491781.831000042, 5.96999979019165], [120784.319999829, 491781.831000042, -2.0699999332428], [120787.075999871, 491779.675000143, -2.0699999332428]])
poly2 = np.array([[120787.075999871, 491779.675000143, -2.0699999332428], [120787.075999871, 491779.675000143, 5.90999984741211], [120784.319999829, 491781.831000042, 5.96999979019165], [120787.075999871, 491779.675000143, -2.0699999332428]])

有点平庸的解决方案,由于这种情况,代码并不漂亮,但它可以工作:

def merge_polygons(p1, p2):
    """
    Simple function that allows to combine two polygons (as numpy arrays) with at least 1 common point
    and potential created continuous lines.
    :return: polygon (merged p1 and p2) as numpy array
    """
    poly1_l = list(p1)[1:]
    poly2_l = list(p2)[1:]
    common_i1 = []
    common_i2 = []

    # looking for common points
    for i, j in ((i, j) for i in range(len(poly1_l)) for j in range(len(poly2_l))):
        if np.all(poly1_l[i] == poly2_l[j]):
            common_i1.append(i)
            common_i2.append(j)
    if not common_i1:
        raise Exception("Can't merge the polygons - no common point!")

    # merging polygons with 1 common point
    if len(common_i1) == 1:
        poly1_l[common_i1[0]:common_i1[0]] = poly2_l[common_i2[0]:] + poly2_l[:common_i2[0]][::-1]
        poly1_l.append(poly1_l[0])
        return np.array(poly1_l)
    else:  # merging polygons with 2+ common points
        start = [None, None]
        end = [None, None]
        # checking, if the common points are creating continuous line
        for iterator, common_l in enumerate((common_i1, common_i2)):
            for i in common_l:
                if not (i - 1) % len(poly1_l) in common_l and not (i + 1) % len(poly1_l) in common_l:
                    raise Exception("Can't merge the polygons - the common part has to be continuous!")
                elif not (i - 1) % len(poly1_l) in common_l:  # marking the start and the end of common part
                    start[iterator] = i
                elif not (i + 1) % len(poly1_l) in common_l:
                    end[iterator] = i
        # merging polygons due to location of common part
        if isinstance(start[0], int) and isinstance(end[0], int):
            poly3_l = []
            if start[0] < end[0]:  # if the common part in the first polygon is not interrupted by the beginning and the end of list
                if start[1] < end[1]:  # if the common part in the second polygon is not interrupted by the beginning and the end of list
                    poly3_l.extend(poly1_l[:start[0]])
                    if np.all(poly1_l[start[0]] == poly2_l[start[1]]):  # if start of chain in first polygon corresponds to start of chain in second polygon
                        poly3_l.extend(poly2_l[:start[1]+1][::-1])
                        poly3_l.extend(poly2_l[end[1]:][::-1])
                    else:
                        poly3_l.extend(poly2_l[end[1]:])
                        poly3_l.extend(poly2_l[:start[1]+1])
                    poly3_l.extend(poly1_l[end[0]+1:])
                    poly3_l.append(poly3_l[0])
                else:
                    poly3_l.extend(poly1_l[:start[0]])
                    if np.all(poly1_l[start[0]] == poly2_l[start[1]]):
                        poly3_l.extend(poly2_l[end[1]:start[1]+1][::-1])
                    else:
                        poly3_l.extend(poly2_l[end[1]:start[1]+1])
                    poly3_l.extend(poly1_l[end[0]+1:])
                    poly3_l.append(poly3_l[0])
            else:
                if start[1] < end[1]:
                    poly3_l.extend(poly2_l[:start[1]+1])
                    if np.all(poly1_l[start[0]] == poly2_l[start[1]]):
                        poly3_l.extend(poly1_l[end[0]+1:start[0]][::-1])
                    else:
                        poly3_l.extend(poly1_l[end[0]+1:start[0]])
                    poly3_l.extend(poly2_l[end[1]:])
                    poly3_l.append(poly3_l[0])
                else:
                    poly3_l.extend(poly1_l[end[0]+1:start[0]])
                    if np.all(poly1_l[start[0]] == poly2_l[start[1]]):
                        poly3_l.extend(poly2_l[end[1]:start[1]+1][::-1])
                    else:
                        poly3_l.extend(poly2_l[end[1]:start[1]+1])
                    poly3_l.append(poly3_l[0])
            return np.array(poly3_l)
        else:
            raise Exception("Polygons are the same - there is no need to merge them.")

这个函数用两个三角形测试了所有可能性,但我会为此做更多的测试。

它的样子:


如果您只想将多边形标记为一种形状,则只需使用列表:

poly3 = np.array(list(poly1) + list(poly2))

您的情况如何:

希望对你有帮助!

【讨论】:

以上是关于组合按特定顺序的相邻 3D 多边形的主要内容,如果未能解决你的问题,请参考以下文章

ZBrush常用3D术语

python如何实现计算多边形面积

多边形重心问题

79-多边形的面积-计算几何

在 Blender 中按颜色选择对象的多边形

绘制平面上的多边形