如何从边列表构造一个面列表,具有一致的顶点排序?

时间:2013-12-21 19:52:54

标签: python geometry graph-theory

我有一些看起来像这样的数据:

vertex_numbers = [1, 2, 3, 4, 5, 6]

# all order here is unimportant - this could be a set of frozensets and it would
# not affect my desired output. However, that would be horribly verbose!
edges = [
    (1, 2),
    (1, 3),
    (1, 4),
    (1, 5),

    (2, 3),
    (3, 4),
    (4, 5),
    (5, 2),

    (2, 6),
    (3, 6),
    (4, 6),
    (5, 6)
]

上面的例子描述了一个八面体 - 对顶点1到6进行编号,其中1和6彼此相对,每个条目描述每条边的末端的顶点数。

根据这些数据,我想制作一份面孔清单。面部保证为三角形。这是上面输入的一个这样的面部列表,由手工确定:

faces = [
    (1, 2, 3),
    (1, 3, 4),
    (1, 4, 5),
    (1, 5, 2),
    (2, 5, 6),
    (3, 2, 6),
    (4, 3, 6),
    (5, 4, 6)
]

从图形上看,这可以表示如下:

graph

对于任何脸部,按照卷曲箭头的方向,您可以读取上面的顶点数字。这不适用于外表1, 3, 4,但你可以通过在球体表面上绘图来解决这个问题


我可以接近这个:

edge_lookup = defaultdict(set)
for a, b in edges:
    edge_lookup[a] |= {b}
    edge_lookup[b] |= {a}

faces = set()
for a in vertex_numbers:
    for b in edge_lookup[a]:
        for c in edge_lookup[a]:
            if b in edge_lookup[c]:
                faces.add(frozenset([a, b, c]))

faces = map(tuple, faces)

给予(从输出中重新排序以便于与原始比较):

[
    (1, 2, 3),  # ok
    (1, 3, 4),  # ok
    (1, 4, 5),  # ok
    (1, 2, 5),  # cyclically incorrect!
    (2, 5, 6),  # ok
    (2, 3, 6),  # cyclically incorrect!
    (3, 4, 6),  # cyclically incorrect!
    (4, 5, 6),  # cyclically incorrect!
}

然而,这对于两个的原因是不利的:

  1. 至少是O(N³)

    在这种特殊情况下,这不是问题,因为N = 10242,它在不到5秒的时间内完成

  2. 它不会确定面部排序

    我在那里使用frozenset,这本身就是无序的。我需要生成与我的示例输出具有相同循环次序的面。

    生成的面部序列用于使用OpenGL渲染单侧曲面。因此,所有面顶点都处于相同的旋转顺序是至关重要的(无论最终是顺时针还是逆时针是顶点本身的属性 - 我关心的是每个面都是相同的)

  3. 假设形成三角形的所有边都必须是面

    正如@Bartosz在评论中指出的那样,情况并非如此 - 采取任何两个三角形网格,并将它们加在一个面上,你有一些不再是面孔的东西。


  4. 我应该使用什么方法来构建具有正确旋转顺序的面列表?

3 个答案:

答案 0 :(得分:4)

我可以给你一个第二部分的线索;一旦你有了面孔,有一种简单的方法可以让它循环正确。

首先选择一个面(a,b,c)是正确的,然后没有其他面可以按顺序包含(a,b),(b,c)或(c,a)。换句话说,找到包含顶点a,b的面,然后使其成为(b,a,x),依此类推。

如果你没有得到我的意思 - 使用以下事实:每个边(x,y)由两个面包含,如果它们是循环正确的,其中一个面具有(x,y) ),另一个为(y,x)。

可能的实施: 首先创建一个图形,其中面是顶点,边是指两个面在原始问题中共享边。然后使用DFS或BFS。

答案 1 :(得分:1)

鉴于巴托斯的信息,这就是我想出的。

class vertex(object):
    def __init__(self, ID):
        self.ID = ID
        self.connected = set()

    def connect(self, cVertex):
        self.connected.add(cVertex.ID)

vertex_list = [vertex(ID) for ID in range(1,6+1)]
face_list = set()
edge_list = set()
edges.sort(key=lambda tup: tup[0] + tup[1]/10.0)
for (a,b) in edges:
    vertex_list[a-1].connect(vertex_list[b-1])
    vertex_list[b-1].connect(vertex_list[a-1])
    common = vertex_list[a-1].connected & vertex_list[b-1].connected
    if (common):
        for x in common:
            if not set([(x, a),(a, b),(b, x)]) & edge_list:
                face_list.add((x, a, b))
                edge_list.update([(x, a),(a, b),(b, x)])

            elif not set([(a, x),(x, b),(b, a)]) & edge_list:
                face_list.add((a, x, b))
                edge_list.update([(a, x),(x, b),(b, a)])

for face in face_list:
    print face

答案 2 :(得分:1)

this answer

的实施
from collections import defaultdict, deque
import itertools

def facetize(edges):
    """turn a set of edges into a set of consistently numbered faces"""

    # build lookups for vertices
    adjacent_vertices = defaultdict(set)
    for a, b in edges:
        adjacent_vertices[a] |= {b}
        adjacent_vertices[b] |= {a}

    orderless_faces = set()
    adjacent_faces = defaultdict(set)

    for a, b in edges:
        # create faces initially with increasing vertex numbers
        f1, f2 = (
            tuple(sorted([a, b, c]))
            for c in adjacent_vertices[a] & adjacent_vertices[b]
        )

        orderless_faces |= {f1, f2}
        adjacent_faces[f1] |= {f2}
        adjacent_faces[f2] |= {f1}


    def conflict(f1, f2):
        """returns true if the order of two faces conflict with one another"""
        return any(
            e1 == e2
            for e1, e2 in itertools.product(
                (f1[0:2], f1[1:3], f1[2:3] + f1[0:1]),
                (f2[0:2], f2[1:3], f2[2:3] + f2[0:1])
            )
        )

    # state for BFS
    processed = set()
    to_visit = deque()

    # result of BFS
    needs_flip = {}

    # define the first face as requiring no flip
    first = next(orderless_faces)
    needs_flip[first] = False
    to_visit.append(first)

    while to_visit:
        face = to_visit.popleft()
        for next_face in adjacent_faces[face]:
            if next_face not in processed:
                processed.add(next_face)
                to_visit.append(next_face)
                if conflict(next_face, face):
                    needs_flip[next_face] = not needs_flip[face]
                else:
                    needs_flip[next_face] = needs_flip[face]


    return [f[::-1] if needs_flip[f] else f for f in orderless_faces]