/// <summary> /// Добавить точку полилинии, которая попала в этот треугольник /// </summary> /// <param name="pt"></param> public void AddPolylinePoint(PolylinePt pt) { SortedSet <PolylinePt> polylinePts = null; pts.TryGetValue(pt.Node, out polylinePts); if (polylinePts == null) { polylinePts = new SortedSet <PolylinePt>(); pts.Add(pt.Node, polylinePts); } polylinePts.Add(pt); }
/// <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); } } } //~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~ }
/// <summary> /// Определить участки полилиний, попавшие в треугольник /// </summary> public void ResolvePolylineParts() { foreach (KeyValuePair <PolylineNesting.Node, SortedSet <PolylinePt> > kvp in pts) { //TEST #region MyRegion //using (Transaction tr = PolylineNesting.db.TransactionManager.StartTransaction()) //using (Polyline pline = new Polyline()) //{ // PolylineNesting.ms = tr.GetObject(PolylineNesting.ms.Id, OpenMode.ForWrite) as BlockTableRecord; // Point3d vert13d = TinSurfaceTriangle.Vertex1.Location; // Point3d vert23d = TinSurfaceTriangle.Vertex2.Location; // Point3d vert33d = TinSurfaceTriangle.Vertex3.Location; // pline.Color = Color.FromColorIndex(ColorMethod.ByAci, 5); // pline.AddVertexAt(0, new Point2d(vert13d.X, vert13d.Y), 0, 0, 0); // pline.AddVertexAt(1, new Point2d(vert23d.X, vert23d.Y), 0, 0, 0); // pline.AddVertexAt(2, new Point2d(vert33d.X, vert33d.Y), 0, 0, 0); // pline.Closed = true; // PolylineNesting.ms.AppendEntity(pline); // tr.AddNewlyCreatedDBObject(pline, true); // tr.Commit(); // pline.Draw(); // PolylineNesting.ed.Regen(); // PolylineNesting.ed.UpdateScreen(); //} #endregion //TEST //Участок полилинии состоит из точек, параметры которых не перескакивают через целые значения //При этом если участок содержит параметр 0, //то необходимо состыковать его с замыкающими точками (1 или более) //Замыкающие точки - участок без перехода через целые значения с наибольшими параметрами //После стыковки участка с параметром 0, удалить все участки, содержащие менее 2 точек //Участок полилинии должен начинаться и заканчиваться точкой лежащей на ребре или совпавшей с вершиной, //кроме тех случаев, когда вся полилиния находится внутри одного треугольника //Не нужны участки, у которых все сегменты лежат на ребрах треугольника PolylineNesting.Node node = kvp.Key; PolylinePt ptWithStartParam = null; SortedSet <PolylinePt> polylinePts = kvp.Value; //Последовательности без перехода через целые значения List <LinkedList <PolylinePt> > sequences = new List <LinkedList <PolylinePt> >(); double?prevParamFloor = null; foreach (PolylinePt pt in polylinePts) { double parameter = pt.Parameter; if (parameter == node.Polyline.StartParam) //стартовый параметр - всегда 0? { ptWithStartParam = pt; } if (prevParamFloor != null && sequences.Count > 0 && parameter - prevParamFloor <= 1) { //Продолжить последовательность sequences.Last().AddLast(pt); } else { //Начать новую последовательность LinkedList <PolylinePt> seq = new LinkedList <PolylinePt>(); seq.AddLast(pt); sequences.Add(seq); } prevParamFloor = Math.Floor(parameter); } //Если обнаружен стартовый параметр, то объединить первую и последнюю последовательности if (ptWithStartParam != null && sequences.Count > 1) { LinkedList <PolylinePt> seq1 = sequences.First(); LinkedList <PolylinePt> seq2 = sequences.Last(); if ( //seq1.First().TinSurfaceEdge==null seq2.Last().Parameter >= node.Polyline.EndParam - 1//Нужно проверить параметр последней точки последей последовательности (что он находится в пределах 1.00 от конечного параметра полилинии) ) { for (LinkedListNode <PolylinePt> lln = seq2.Last; lln != null; lln = lln.Previous) { seq1.AddFirst(lln.Value); } sequences.RemoveAt(sequences.Count - 1); } } //Проверить все участки полилинии и удалить неправильные //Tolerance tolerance = Tolerance.Global; Point2d vert1 = new Point2d(tinSurfaceTriangle.Vertex1.Location.X, tinSurfaceTriangle.Vertex1.Location.Y); Point2d vert2 = new Point2d(tinSurfaceTriangle.Vertex2.Location.X, tinSurfaceTriangle.Vertex2.Location.Y); Point2d vert3 = new Point2d(tinSurfaceTriangle.Vertex3.Location.X, tinSurfaceTriangle.Vertex3.Location.Y); Vector2d edge1Vector = vert2 - vert1; Vector2d edge2Vector = vert3 - vert2; Vector2d edge3Vector = vert1 - vert3; sequences.RemoveAll(seq => { bool removeThis = seq.Count < 2;//Количество точек не менее двух if (!removeThis) { //Не может быть с одной стороны ребро или вершина а с другой нет bool firstPtOnEdge = seq.First().TinSurfaceEdge != null || seq.First().TinSurfaceVertex != null; bool lastPtOnEdge = seq.Last().TinSurfaceEdge != null || seq.Last().TinSurfaceVertex != null; removeThis = firstPtOnEdge != lastPtOnEdge;//Несоответствие привязки участка к ребрам if (!removeThis) { removeThis = true; //Проверить все ребра на совпадение с ребрами треугольника for (LinkedListNode <PolylinePt> lln = seq.First; lln.Next != null; lln = lln.Next) { Point2d segPt1 = lln.Value.Point2D; Point2d segPt2 = lln.Next.Value.Point2D; if (!(Utils.LinesAreOverlapping(segPt1, segPt2, vert1, vert2) || Utils.LinesAreOverlapping(segPt1, segPt2, vert2, vert3) || Utils.LinesAreOverlapping(segPt1, segPt2, vert3, vert1))) { //Если сегмент не накладывается ни на одно ребро треугольника, то закончить проверку removeThis = false; break; } } } } return(removeThis); }); foreach (LinkedList <PolylinePt> seq in sequences) { PolylineParts.Add(new PolylinePart(seq)); } } }