我有一些看起来像这样的数据:
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)
]
从图形上看,这可以表示如下:
对于任何脸部,按照卷曲箭头的方向,您可以读取上面的顶点数字。这不适用于外表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!
}
然而,这对于两个的原因是不利的:
至少是O(N³)
在这种特殊情况下,这不是问题,因为N = 10242,它在不到5秒的时间内完成
它不会确定面部排序
我在那里使用frozenset
,这本身就是无序的。我需要生成与我的示例输出具有相同循环次序的面。
生成的面部序列用于使用OpenGL渲染单侧曲面。因此,所有面顶点都处于相同的旋转顺序是至关重要的(无论最终是顺时针还是逆时针是顶点本身的属性 - 我关心的是每个面都是相同的)
假设形成三角形的所有边都必须是面
正如@Bartosz在评论中指出的那样,情况并非如此 - 采取任何两个三角形网格,并将它们加在一个面上,你有一些不再是面孔的东西。
我应该使用什么方法来构建具有正确旋转顺序的面列表?
答案 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)
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]