Получить граничные края сетки-в порядке намотки


У меня есть триангулированная сетка. Предположим, что это выглядит как неровная поверхность. Я хочу иметь возможность найти все ребра, которые попадают на окружающую границу сетки. (забудьте о внутренних вершинах)

Я знаю, что мне нужно найти ребра, которые связаны только с одним треугольником, и собрать все это вместе, и это ответ. Но я хочу быть уверен, что вершины этих ребер упорядочены по часовой стрелке вокруг фигуры.

Я хочу сделать это, потому что я хотел бы получить полигональную линию вокруг внешней стороны сетки.

Я надеюсь, что это достаточно ясно, чтобы понять. В некотором смысле я пытаюсь "де-триангулировать" сетку. ха! если есть такой термин.

3 14

3 ответа:

На граничные ребра ссылается только один треугольник в сетке, поэтому, чтобы найти их, вам нужно просканировать все треугольники в сетке и взять ребра с одним отсчетом ссылок. Вы можете сделать это эффективно (в O(N)), используя хэш-таблицу.

Для преобразования набора ребер в упорядоченный полигональный цикл можно использовать метод обхода:

  1. выберите любой непросмотренный реберный сегмент [v_start,v_next] и добавьте эти вершины в полигональный цикл.
  2. найти не посещаемый край отрезок [v_i,v_j], имеющий либо v_i = v_next, либо v_j = v_next и добавляющий другую вершину (ту, которая не равна v_next) к контуру полигона. Сбросьте v_next как эту вновь добавленную вершину, отметьте ребро как посещенное и продолжите с 2.
  3. обход делается, когда мы возвращаемся к v_start.

Обход даст полигональный цикл, который может иметь как часовой, так и встречный порядок. Последовательное упорядочение может быть установлено путем рассмотрения знаковой областимногоугольника . Если обход приводит к неправильной ориентации, вам просто нужно изменить порядок вершин многоугольника.

Ну, как говорится-заставь его работать - тогда заставь его работать лучше. Я заметил, что в моем примере выше предполагается, что все ребра в массиве ребер действительно соединяются в красивую границу. Это может быть не так в реальном мире (как я обнаружил из своих входных файлов, которые я использую!) На самом деле некоторые из моих входных файлов на самом деле имеют много полигонов, и все они нуждаются в обнаружении границ. Я также хотел убедиться, что порядок намотки правильный. Так что я и это исправил. увидеть ниже. (Чувствую, что я делаю наконец-то вперед!)

    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;
    }

Код обхода (не эффективный - должен быть очищен, дойдет до этого в какой-то момент) обратите внимание: я храню каждый сегмент в цепочке как 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;