/// <summary> /// Расчитать полигоны для построения сети /// </summary> public void CalculatePoligons() { //Определить внутренние вершины в этом треугольнике foreach (LinkedListNode <GraphNode> lln in vertexNodes) { VertexGraphNode vgn = (VertexGraphNode)lln.Value; vgn.IsInnerVertex = polylineNesting.InnerVerts.Contains(verts[vgn.VertNum]); } List <List <Point3d> > holes = new List <List <Point3d> >(); //Добавление оставшихся узлов и ребер в граф foreach (PolylinePart pp in PolylineParts) { PolylinePt check = pp.PolylinePts.First(); if (check.TinSurfaceEdge != null || check.TinSurfaceVertex != null) //Эта полилиния пересекает треугольник? { PolylinePt[] nodePts = new PolylinePt[] //Точки присоединения полилинии к границам треугольника { pp.PolylinePts.First(), pp.PolylinePts.Last() }; for (short i = 0; i < 2; i++) { PolylinePt pt = nodePts[i]; GraphNode graphNode = null; if (pt.TinSurfaceVertex != null) { //Определить номер этой вершины в этом треугольнике и получить ссылку на узел графа short vertNum = GetVertNum(pt.TinSurfaceVertex); if (vertNum == -1) { throw new Exception(); } graphNode = vertexNodes[vertNum].Value; } else { //Определить номер этого ребра, добавить узел этого ребра short edgeNum = GetEdgeNum(pt.TinSurfaceEdge); if (edgeNum == -1) { throw new Exception(); } graphNode = new EdgeGraphNode(this, edgeNum, pt.Point2D); } //дополнить свойства graphNode указателями на участок полилинии graphNode.PolylinePart = pp; graphNode.PolylinePartConnectedByStart = i == 0; if (i == 0) { pp.StartNode = graphNode; } else { pp.EndNode = graphNode; } } //Соединения для созданных узлов pp.StartNode.ConnectedLinkedListNode = pp.EndNode.LinkedListNode; pp.EndNode.ConnectedLinkedListNode = pp.StartNode.LinkedListNode; } else { //Эта полилиния полностью находится внутри треугоьника //Если эта полилиния - внешняя граница, то добавить полигон по всем точкам полилинии if (pp.PolylineNestingNode.IsOuterBoundary) { List <Point3d> poligon = new List <Point3d>(); Polygons.Add(poligon); foreach (PolylinePt pt in pp.PolylinePts) { poligon.Add(new Point3d(pt.Point2D.X, pt.Point2D.Y, pt.Z)); } } else { //Если эта полилиния ограничивает островок, то отложить этот полигон для дальнейшей обработки List <Point3d> hole = new List <Point3d>(); holes.Add(hole); foreach (PolylinePt pt in pp.PolylinePts) { hole.Add(new Point3d(pt.Point2D.X, pt.Point2D.Y, pt.Z)); } } } } //Составление маршрутов обхода графа //Правила //- Начинать обход с любого еще не обойденнго узла, из которого исходит участок полилинии. Запомнить ссылку на PolylineNesting.Node //- Если из текущего узла исходит участок полилинии, который еще не обойден, то обойти его //- Из текущего узла не исходит такого участка полилинии (например, мы только что обошли участок полилинии и пришли в узел на ребре) => // - Есть 2 варианта куда идти дальше: либо вперед до следующего узла, либо назад // Проверяются оба варианта. При этом: // - Обходить до тех пор пока не будет дотигнут стартовый узел // - Нельзя заходить в те узлы которые уже посещены (только замыкание со стартовым узлом) // - Нельзя заходить в узлы вершин, которые не являются внутренними // Если в итоге получаются 2 варината обхода, взять точку изнутри каждого из обойденных полигонов и с помощью алгоритма WindingNumber проверить, // попадают ли они внутрь PolylineNesting.Node. Если PolylineNesting.Node.IsOuterBoundary = true, // то принять обход, точка которого попала внутрь PolylineNesting.Node, иначе принять обход, точка которого не попала внутрь PolylineNesting.Node //DisplayUtils.Polyline(vert2dLocs, true, 1, SurfaceMeshByBoundaryCommand.DB, null, SurfaceMeshByBoundaryCommand.ED); for (LinkedListNode <GraphNode> lln = graphNodes.First; lln != null; lln = lln.Next) { GraphNode startNode = lln.Value; if (!startNode.Visited && startNode.PolylinePart != null) { startNode.Visited = true; PolylinePart startPolyPart = startNode.PolylinePart; GraphNode nextNode = startNode.ConnectedLinkedListNode.Value; if (nextNode.Visited || startPolyPart.Visited) { //Такой ситуации не должно быть. Отметить проблемный треугольник DisplayUtils.Polyline(vert2dLocs, true, 1, SurfaceMeshByBoundaryCommand.DB); continue; } PolylineNesting.Node pNNode = startPolyPart.PolylineNestingNode; //Начать составление маршрутов 2 вариантов замкнутого пути. Начинается всегда с прохода по участку полилинии List <PathElement> path1 = new List <PathElement>() { startNode, startPolyPart, nextNode }; List <PathElement> path2 = new List <PathElement>() { startNode, startPolyPart, nextNode }; startPolyPart.Visited = true;//необходимо для правильной работы PathPreparing bool path1Prepared = PathPreparing(nextNode, startNode, true, path1); bool path2Prepared = PathPreparing(nextNode, startNode, false, path2); List <Point3d> poligon1 = null; List <Point3d> poligon2 = null; if (path1Prepared) { //Заполнить полигон 1 poligon1 = GetPoligonFromPath(path1); //DisplayUtils.Polyline(Utils.Poligon3DTo2D(poligon1), true, 1, SurfaceMeshByBoundaryCommand.DB); } if (path2Prepared) { //Заполнить полигон 2 poligon2 = GetPoligonFromPath(path2); //DisplayUtils.Polyline(Utils.Poligon3DTo2D(poligon2), true, 1, SurfaceMeshByBoundaryCommand.DB); } List <PathElement> actualPath = null; List <Point3d> actualPoligon = null; if (path1Prepared && path2Prepared)//Если составлено 2 возможных путя { //Для 1-го варианта обхода найти внутреннюю точку (не на границе) IList <Point2d> poligon1_2d = Utils.Poligon3DTo2D(poligon1); //IList<Point2d> poligon2_2d = Utils.Poligon3DTo2D(poligon2); Point2d?p1 = null; try { p1 = Utils.GetAnyPointInsidePoligon(poligon1_2d, Utils.DirectionIsClockwise(poligon1_2d)); } catch (Exception) { //Такой ситуации не должно быть. Отметить проблемный треугольник DisplayUtils.Polyline(vert2dLocs, true, 1, SurfaceMeshByBoundaryCommand.DB); continue; } //Point2d p2 = Utils.GetAnyPointInsidePoligon(poligon2_2d, Utils.DirectionIsClockwise(poligon2_2d)); if (p1 != null) { //Определить находится ли эта точка внутри полилинии bool p1InsidePolyline = Utils.PointIsInsidePolylineWindingNumber(p1.Value, pNNode.Point2DCollection); //Проверить, подходит ли 1-й вариант с учетом свойства IsOuterBoundary if ((pNNode.IsOuterBoundary && p1InsidePolyline) || (!pNNode.IsOuterBoundary && !p1InsidePolyline)) { //Первый вариант правильный actualPoligon = poligon1; actualPath = path1; } else { //Второй вариант правильный actualPoligon = poligon2; actualPath = path2; } } } else if (path1Prepared) { actualPoligon = poligon1; actualPath = path1; } else if (path2Prepared) { actualPoligon = poligon2; actualPath = path2; } if (actualPoligon != null) { //Для принятого пути обхода: // - Добавить полигон в набор // - Присвоить свойству Visited значение true Polygons.Add(actualPoligon); foreach (PathElement pe in actualPath) { pe.Visited = true; } } } } //~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~ //ОБРАБОТКА ОТВЕРСТИЙ ВНУТРИ ПОЛИГОНОВ if (holes.Count > 0) { //Каждое отверстие находится внутри одного из рассчитанных полигонов. Определить полигон, в который вложено отверстие //Эти полигоны будут дополнены участками, включающими в себя отверстия //в соответствии с https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf п 3 - 5 List <PolygonWithNested> poligonsWithHoles = PolygonWithHoles.ResolveHoles(Polygons, holes); foreach (PolygonWithNested p in poligonsWithHoles) { if (p.Polygon == null) { //Если отверстия есть в корневом узле, значит нужно рассматривать полигон равный текущему треугольнику с отверстиями p.Polygon = new List <Point3d>() { tinSurfaceTriangle.Vertex1.Location, tinSurfaceTriangle.Vertex2.Location, tinSurfaceTriangle.Vertex3.Location }; } else { //Если оказывается, что полигон включает в себя отверстие, то он должен быть удален из Polygons //Такие полигоны будут разбиваться на треугольники Polygons.Remove(p.Polygon); } p.MakeSimple(); //Polygons.Add(p.Polygon); //Нельзя просто добавить простой полигон к общему списку полигонов (SubDMesh создается неправильно). //Необходимо сделать триангуляцию //TEST #region Отрисовка простого полигона //using (Transaction tr // = SurfaceMeshByBoundaryCommand.DB.TransactionManager.StartTransaction()) //using (Polyline pline = new Polyline()) //{ // BlockTable bt = tr.GetObject(SurfaceMeshByBoundaryCommand.DB.BlockTableId, OpenMode.ForWrite) as BlockTable; // BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); // pline.Color = Color.FromColorIndex(ColorMethod.ByAci, 5); // foreach (Point3d pt in p.Polygon) // { // pline.AddVertexAt(0, new Point2d(pt.X, pt.Y), 0, 0, 0); // } // pline.Closed = true; // ms.AppendEntity(pline); // tr.AddNewlyCreatedDBObject(pline, true); // tr.Commit(); //} #endregion //TEST EarClippingTriangulator triangulator = new EarClippingTriangulator(p.Polygon); foreach (List <Point3d> tr in triangulator.Triangles) { Polygons.Add(tr); } } } //~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~ }