Получить граничные края сетки-в порядке намотки
У меня есть триангулированная сетка. Предположим, что это выглядит как неровная поверхность. Я хочу иметь возможность найти все ребра, которые попадают на окружающую границу сетки. (забудьте о внутренних вершинах)
Я знаю, что мне нужно найти ребра, которые связаны только с одним треугольником, и собрать все это вместе, и это ответ. Но я хочу быть уверен, что вершины этих ребер упорядочены по часовой стрелке вокруг фигуры.Я хочу сделать это, потому что я хотел бы получить полигональную линию вокруг внешней стороны сетки.
Я надеюсь, что это достаточно ясно, чтобы понять. В некотором смысле я пытаюсь "де-триангулировать" сетку. ха! если есть такой термин.
3 ответа:
На граничные ребра ссылается только один треугольник в сетке, поэтому, чтобы найти их, вам нужно просканировать все треугольники в сетке и взять ребра с одним отсчетом ссылок. Вы можете сделать это эффективно (в
O(N)
), используя хэш-таблицу.Для преобразования набора ребер в упорядоченный полигональный цикл можно использовать метод обхода:
- выберите любой непросмотренный реберный сегмент
[v_start,v_next]
и добавьте эти вершины в полигональный цикл.- найти не посещаемый край отрезок
[v_i,v_j]
, имеющий либоv_i = v_next
, либоv_j = v_next
и добавляющий другую вершину (ту, которая не равнаv_next
) к контуру полигона. Сбросьтеv_next
как эту вновь добавленную вершину, отметьте ребро как посещенное и продолжите с 2.- обход делается, когда мы возвращаемся к
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;