获取网格的边界边缘 - 按缠绕顺序

Posted

技术标签:

【中文标题】获取网格的边界边缘 - 按缠绕顺序【英文标题】:Get border edges of mesh - in winding order 【发布时间】:2012-12-16 00:33:48 【问题描述】:

我有一个三角网格。假设它看起来像一个凹凸不平的表面。我希望能够找到落在网格周围边界上的所有边缘。 (忘记内部顶点)

我知道我必须找到只连接到一个三角形的边,并将所有这些边收集在一起,这就是答案。但我想确保这些边的顶点围绕形状顺时针排列。

我想这样做是因为我想在网格外部获得一条多边形线。

我希望这足够清楚,可以理解。从某种意义上说,我正在尝试对网格进行“去三角化”。哈!如果有这样的术语。

【问题讨论】:

是的 - 但我需要按顺序对周边的点进行排序。另外我不认为凸包会这样做,因为它可能会错过周边上的点,但在凸包内部。 好吧,想象一下,如果我的表面在它的侧面有一个大凹痕,延伸到它的形状的内部,但它的脖子很细 - 。凸包会跟随那个凹痕吗?还是会跳过它,因为它只考虑形状的最大范围?没有图片很难解释。 【参考方案1】:

边界边仅由网格中的单个三角形引用,因此要找到它们,您需要扫描网格中的所有三角形并使用单个引用计数获取边。您可以通过使用哈希表有效地做到这一点(O(N))。

要将边集转换为有序多边形循环,您可以使用遍历方法:

    选择任何未访问的边段[v_start,v_next] 并将这些顶点添加到多边形循环中。 找到具有v_i = v_nextv_j = v_next 的未访问边段[v_i,v_j],并将另一个顶点( 等于v_next)添加到多边形循环。将v_next重置为这个新添加的顶点,将边标记为已访问并从2继续。 当我们回到v_start时,遍历完成。

遍历将给出一个多边形循环,该循环可以具有顺时针或逆时针顺序。通过考虑signed area of the polygon,可以建立一致的排序。如果遍历导致方向错误,您只需反转多边形循环顶点的顺序即可。

【讨论】:

嗨,Darren,我遵循了您上面建议的算法。它第一次奏效!谢谢你。它的工作原理是完全合乎逻辑的,所以感谢你给我的准确描述。我已经找到了仅存在于网格上的一个三角形中的边缘。所以我只需要遍历算法来正确排序顶点。【参考方案2】:

俗话说 - 让它工作 - 然后让它工作得更好。我注意到在我上面的例子中,它假设边数组中的所有边实际上都链接在一个漂亮的边框中。在现实世界中可能并非如此(正如我从我正在使用的输入文件中发现的那样!)事实上,我的一些输入文件实际上有很多多边形并且都需要检测到边界。我还想确保缠绕顺序正确。所以我也解决了这个问题。见下文。 (感觉我终于取得了进展!)

    private static List<int> OrganizeEdges(List<int> edges, List<Point> positions)
    
        var visited = new Dictionary<int, bool>();
        var edgeList = new List<int>();
        var resultList = new List<int>();
        var nextIndex = -1;
        while (resultList.Count < edges.Count)
        
            if (nextIndex < 0)
            
                for (int i = 0; i < edges.Count; i += 2)
                
                    if (!visited.ContainsKey(i))
                    
                        nextIndex = edges[i];
                        break;
                    
                
            

            for (int i = 0; i < edges.Count; i += 2)
            
                if (visited.ContainsKey(i))
                    continue;

                int j = i + 1;
                int k = -1;
                if (edges[i] == nextIndex)
                    k = j;
                else if (edges[j] == nextIndex)
                    k = i;

                if (k >= 0)
                
                    var edge = edges[k];
                    visited[i] = true;
                    edgeList.Add(nextIndex);
                    edgeList.Add(edge);
                    nextIndex = edge;
                    i = 0;
                
            

            // calculate winding order - then add to final result.
            var borderPoints = new List<Point>();
            edgeList.ForEach(ei => borderPoints.Add(positions[ei]));
            var winding = CalculateWindingOrder(borderPoints);
            if (winding > 0)
                edgeList.Reverse();

            resultList.AddRange(edgeList);
            edgeList = new List<int>();
            nextIndex = -1;
        

        return resultList;
    




    /// <summary>
    /// returns 1 for CW, -1 for CCW, 0 for unknown.
    /// </summary>
    public static int CalculateWindingOrder(IList<Point> points)
    
        // the sign of the 'area' of the polygon is all we are interested in.
        var area = CalculateSignedArea(points);
        if (area < 0.0)
            return 1;
        else if (area > 0.0)
            return - 1;        
        return 0; // error condition - not even verts to calculate, non-simple poly, etc.
    

    public static double CalculateSignedArea(IList<Point> points)
    
        double area = 0.0;
        for (int i = 0; i < points.Count; i++)
        
            int j = (i + 1) % points.Count;
            area += points[i].X * points[j].Y;
            area -= points[i].Y * points[j].X;
        
        area /= 2.0f;

        return area;
    

【讨论】:

【参考方案3】:

遍历代码(效率不高 - 需要整理,会在某个时候达到) 请注意:我将链中的每个段存储为 2 索引 - 而不是建议的 1达伦。这纯粹是为了我自己的实现/渲染需求。

        // okay now lets sort the segments so that they make a chain.

        var sorted = new List<int>();
        var visited = new Dictionary<int, bool>();

        var startIndex = edges[0];
        var nextIndex = edges[1];

        sorted.Add(startIndex);
        sorted.Add(nextIndex);

        visited[0] = true;
        visited[1] = true;

        while (nextIndex != startIndex)
        
            for (int i = 0; i < edges.Count - 1; i += 2)
            
                var j = i + 1;
                if (visited.ContainsKey(i) || visited.ContainsKey(j))
                    continue;

                var iIndex = edges[i];
                var jIndex = edges[j];

                if (iIndex == nextIndex)
                
                    sorted.Add(nextIndex);
                    sorted.Add(jIndex);
                    nextIndex = jIndex;
                    visited[j] = true;
                    break;
                
                else if (jIndex == nextIndex)
                
                    sorted.Add(nextIndex);
                    sorted.Add(iIndex);
                    nextIndex = iIndex;
                    visited[i] = true;
                    break;
                
            
        

        return sorted;

【讨论】:

以上是关于获取网格的边界边缘 - 按缠绕顺序的主要内容,如果未能解决你的问题,请参考以下文章

WPF 网格 — 按特定顺序调整列的大小

顶点的缠绕顺序

使用矩阵反转顶点缠绕顺序

使用topomerge合并topojson会打乱缠绕顺序

DX11 中的顶点缠绕顺序

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