You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Bow-type弓形非流形几何的三角网格高效检测算法咨询

弓形非流形示例

弓形非流形顶点检测算法说明

核心判定规则

弓形非流形顶点的核心判定依据是:该顶点邻接的所有三角面,通过「共享含该顶点的边」构成的连通分量数量 ≥ 2。
你最初的思路方向是正确的,但不能只判断顶点关联多个面就下结论——比如普通的流形内部顶点通常关联6-8个面,但所有面都通过边两两连通,连通分量数为1,属于合法结构。只有连通分量数超过1的顶点才是弓形非流形顶点,对应多个曲面仅在该点拼接的拓扑。

算法复杂度

该算法为线性时间复杂度O(V + F),是当前理论最优的检测方案:

  • 每个三角面只会被它的三个顶点各遍历一次,总遍历次数为3F
  • 每个顶点的连通分量计算仅在其关联面集合内进行,无额外冗余计算

伪代码

输入:三角网格的所有顶点集合,每个顶点已存储关联三角面集合adj_faces
输出:所有弓形非流形顶点列表

bowtie_nonmanifold_vertices = []
for each vertex v in vertices:
    if len(v.adj_faces) <= 1:
        continue  # 最多关联1个面,不可能是多曲面连接
    # 构建v的邻接面连通图:两个面连通当且仅当共享一条含v的边
    visited = 空集合
    component_count = 0
    for each face f in v.adj_faces:
        if f not in visited:
            component_count += 1
            # BFS遍历所有和f连通的面
            queue = [f]
            visited.add(f)
            while queue not empty:
                current_f = queue.pop()
                # 遍历current_f的相邻面,筛选同时关联v的面
                for neighbor_f in current_f.adjacent_triangles:
                    if neighbor_f in v.adj_faces and neighbor_f not in visited:
                        visited.add(neighbor_f)
                        queue.append(neighbor_f)
    if component_count >= 2:
        bowtie_nonmanifold_vertices.append( (v, component_count) )
return bowtie_nonmanifold_vertices

基于现有代码的Python实现

可以直接在你现有TriMesh类的基础上增加检测方法:

from collections import deque

class Triangle:
    def __init__(self, a, b, c, label):
        self.a = a
        self.b = b
        self.c = c
        self.label = label
        self.adjacent_triangles = []

    def __repr__(self):
        return f"{self.label}"


class Vertex:
    def __init__(self, x, y, label):
        self.x = x
        self.y = y
        self.label = label
        self.adjacent_triangles = set()

    def __repr__(self):
        return f"{self.label} - {self.adjacent_triangles}"


class Edge:
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.adjacent_triangles = []

    def __repr__(self):
        return f"{self.__dict__}"


class TriMesh:
    def __init__(self, vertices, triangles):
        edges = {}

        def process_edge(a, b, triangle):
            vertex_index_a = min(a, b)
            vertex_index_b = max(a, b)
            key = f"{vertex_index_a}_{vertex_index_b}"

            if key in edges:
                edge = edges[key]
            else:
                edge = Edge(vertices[vertex_index_a], vertices[vertex_index_b])
                edges[key] = edge

            edge.adjacent_triangles.append(triangle)

        for triangle in triangles:
            process_edge(triangle.a, triangle.b, triangle)
            process_edge(triangle.b, triangle.c, triangle)
            process_edge(triangle.c, triangle.a, triangle)

        for key in edges:
            edge = edges[key]
            f0 = edge.adjacent_triangles[0]
            edge.a.adjacent_triangles.add(f0)
            edge.b.adjacent_triangles.add(f0)

            if len(edge.adjacent_triangles) < 2:
                continue

            f1 = edge.adjacent_triangles[1]
            edge.a.adjacent_triangles.add(f1)
            edge.b.adjacent_triangles.add(f1)
            f0.adjacent_triangles.append(f1)
            f1.adjacent_triangles.append(f0)

        self.vertices = vertices
        self.triangles = triangles
        self.edges = edges
    
    # 新增弓形非流形检测方法
    def detect_bowtie_vertices(self):
        bowtie_vertices = []
        for v in self.vertices:
            adj_faces = v.adjacent_triangles
            if len(adj_faces) <= 1:
                continue
            visited = set()
            comp_cnt = 0
            for f in adj_faces:
                if f not in visited:
                    comp_cnt += 1
                    q = deque([f])
                    visited.add(f)
                    while q:
                        cur_f = q.popleft()
                        for nb_f in cur_f.adjacent_triangles:
                            if nb_f in adj_faces and nb_f not in visited:
                                visited.add(nb_f)
                                q.append(nb_f)
            if comp_cnt >= 2:
                bowtie_vertices.append((v.label, comp_cnt))
        return bowtie_vertices


if __name__ == "__main__":
    #     4---5   6   7
    #     |\t1|  /|\
    #     | \ | / | \
    #     |t0\|/t2|t3\
    #     0---1---2---3
    v = [
        Vertex(0, 0, "0"),
        Vertex(1, 0, "1"),
        Vertex(2, 0, "2"),
        Vertex(3, 0, "3"),
        Vertex(0, 1, "4"),
        Vertex(1, 1, "5"),
        Vertex(2, 1, "6"),
        Vertex(3, 1, "7"),
    ]
    t = [
        Triangle(0, 1, 4, "t0"),
        Triangle(1, 5, 4, "t1"),
        Triangle(1, 2, 6, "t2"),
        Triangle(2, 3, 6, "t3"),
    ]

    m = TriMesh(vertices=v, triangles=t)
    res = m.detect_bowtie_vertices()
    print("弓形非流形顶点(顶点标签,连接曲面数):", res)
    # 输出结果:弓形非流形顶点(顶点标签,连接曲面数): [('1', 2)]

结果验证

你给出的测试用例中顶点1的关联面是{t0,t1,t2},其中t0和t1共享边1-4连通,t2和前两个面没有共享含顶点1的边,所以连通分量数为2,符合预期的检测结果。

内容的提问来源于stack exchange,提问作者BPL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 12:36:08