private bool FlipIfNeeded(Tris trisA, Tris trisB, TrisVertex newPoint, out Tris rt) { var indexB = trisB.IndexOfPoint(newPoint); var indexBNext = indexB < 2 ? indexB + 1 : 0; var indexBPrev = indexB > 0 ? indexB - 1 : 2; var indexA = trisA.IndexOfThirdPoint(trisB[indexBNext], trisB[indexBPrev]); var downPoint = trisA[indexA]; var indexANext = indexA < 2 ? indexA + 1 : 0; var indexAPrev = indexA > 0 ? indexA - 1 : 2; var needAnotherCut = trisA.IsPointBrokeDelaunay(newPoint); if (!needAnotherCut) { needAnotherCut = trisB.IsPointBrokeDelaunay(downPoint); } if (needAnotherCut) { // запоминаем треугольники, "ниже" var trA = downPoint.FindAnotherTrisWith(trisA, trisA[indexANext], downPoint); var trB = downPoint.FindAnotherTrisWith(trisA, trisA[indexAPrev], downPoint); // рубим по другому образуемый этими треугольниками четырехугольник. trisA[indexAPrev].Trises.Remove(trisA); trisB[indexBPrev].Trises.Remove(trisB); trisA.Points[indexAPrev] = newPoint; trisB.Points[indexBPrev] = downPoint; newPoint.Trises.Add(trisA); downPoint.Trises.Add(trisB); // вызываем рекурсивно для внутренних rt = trisA; Tris tmp; if (trA != null) { if (FlipIfNeeded(trA, trisA, newPoint, out tmp)) { rt = tmp; } } if (trB != null) { FlipIfNeeded(trB, trisB, newPoint, out tmp); } return(true); } rt = null; return(false); }
public int IndexOfThirdPoint(TrisVertex a, TrisVertex b) { if (Points[0] != a && Points[0] != b) { return(0); } if (Points[1] != a && Points[1] != b) { return(1); } return(2); }
/// <summary> /// Находим позиции и квадрат радиуса описаной окружности, через 3 точки /// </summary> public bool IsPointBrokeDelaunay(TrisVertex p) { // Используем относительные координаты до точки 'a' double xba = Points[1].X - Points[0].X; double yba = Points[1].Y - Points[0].Y; double xca = Points[2].X - Points[0].X; double yca = Points[2].Y - Points[0].Y; // Квадраты растояний граней принадлежащих 'a' var baLength = xba * xba + yba * yba; var caLength = xca * xca + yca * yca; // Считаем деноминатор формулы. var d = xba * yca - yba * xca; if (d == 0) { return(false); } var denominator = 0.5 / d; // Рассчитываем смещение (от 'pa') центра описанной окружности var xC = (yca * baLength - yba * caLength) * denominator; var yC = (xba * caLength - xca * baLength) * denominator; var radius2 = xC * xC + yC * yC; if (radius2 > 1e10 * baLength || radius2 > 1e10 * caLength) { return(false); } var px = Points[0].X + xC - p.X; var py = Points[0].Y + yC - p.Y; var pointRadius2 = px * px + py * py; return(pointRadius2 < radius2); }
public Tris(TrisVertex a, TrisVertex b, TrisVertex c) { Points[0] = a; Points[1] = b; Points[2] = c; }
public int IndexOfPoint(TrisVertex a) { return(Points[0] == a ? 0 : Points[1] == a ? 1 : 2); }
public bool HasEdge(TrisVertex a, TrisVertex b) { return((a == Points[0] || a == Points[1] || a == Points[2]) && (b == Points[0] || b == Points[1] || b == Points[2])); }
public override Graph GenerateGraph(int vertexCount, int edgePercent, int size = 500) { var graph = new Graph(); var random = new Random(); graph.Vertices = Enumerable.Range(0, vertexCount) .Select(i => new Vertex(i, new Point(random.NextDouble() * size, random.NextDouble() * size))) .ToDictionary(_ => _.Id, _ => _); if (graph.Vertices.Count < 2) { return(graph); } if (graph.Vertices.Count == 2) { graph.AddEdge(graph.Vertices[0], graph.Vertices[1]); return(graph); } // соединяем точки гранями, строим триангуляцию Делоне // чтобы ускорить процесс, найдем самую левую вверхнюю точку, отсортируем все точки по расстоянию от нее // и соеденим с ближайшей к ней точке, оброзовав первую грань графа. var first = graph.Vertices[0]; foreach (var v in graph.Vertices.Values) { if (v.X < first.X || v.X == first.X && v.Y < first.Y) { first = v; } } var range = new double[graph.Vertices.Count]; for (var i = 0; i < graph.Vertices.Count; i++) { var x = graph.Vertices[i].X - first.X; var y = graph.Vertices[i].Y - first.Y; range[i] = x * x + y * y; } var sortedVertices = graph.Vertices.Values.ToArray(); Array.Sort(range, sortedVertices); var vertex = new TrisVertex[sortedVertices.Length]; for (var i = 0; i < sortedVertices.Length; i++) { vertex[i] = new TrisVertex(sortedVertices[i]); } // создаем первую грань var tris = new Tris(vertex[0], vertex[1], vertex[2]); tris.MakeClockwise(); var resultTriangles = new List <Tris>(sortedVertices.Length * 2); tris[0].Trises.Add(tris); tris[1].Trises.Add(tris); tris[2].Trises.Add(tris); resultTriangles.Add(tris); var hull = new CircularList <TrisEdge>(); hull.AddItem(new TrisEdge(tris, tris[0], tris[1])); hull.AddItem(new TrisEdge(tris, tris[1], tris[2])); hull.AddItem(new TrisEdge(tris, tris[2], tris[0])); var seekStart = hull.Last; for (var i = 3; i < vertex.Length; i++) { var nextPoint = vertex[i]; CircularItem <TrisEdge> visiblePoint = null; if (seekStart.Value.EdgeVisibleFrom(nextPoint.Position)) { visiblePoint = seekStart; } else { visiblePoint = hull.FindNext(seekStart, t => t.Value.EdgeVisibleFrom(nextPoint.Position)); } if (visiblePoint == null) { continue; } var notVisibleLeft = hull.FindPrevious(visiblePoint, t => !t.Value.EdgeVisibleFrom(nextPoint.Position)); var notVisibleRight = hull.FindNext(visiblePoint, t => !t.Value.EdgeVisibleFrom(nextPoint.Position)); if (notVisibleLeft == null || notVisibleRight == null) { continue; } var visibleLeft = notVisibleLeft.Next; var edge = visibleLeft.Value; var hullItem = visibleLeft; Tris firstEdgeTris = null; Tris lastEdgeTris = null; while (hullItem != notVisibleRight) { var nextTris = new Tris(edge.A, nextPoint, edge.B); nextTris[0].Trises.Add(nextTris); nextTris[1].Trises.Add(nextTris); nextTris[2].Trises.Add(nextTris); resultTriangles.Add(nextTris); // разворачиваем треугольники, чтобы удовлетворяли критерию Делоне if (FlipIfNeeded(edge.tris, nextTris, nextPoint, out var returnLinkTris)) { if (firstEdgeTris == null) { firstEdgeTris = returnLinkTris; } } if (firstEdgeTris == null) { firstEdgeTris = nextTris; } lastEdgeTris = nextTris; hullItem = hullItem.Next; edge = hullItem.Value; } // закрываем дырку hull.LinkTwoItem(notVisibleLeft, notVisibleRight); seekStart = hull.AddItemAfter(notVisibleLeft); seekStart.Value = new TrisEdge(firstEdgeTris, notVisibleLeft.Value.B, nextPoint); seekStart = hull.AddItemAfter(seekStart); seekStart.Value = new TrisEdge(lastEdgeTris, nextPoint, notVisibleRight.Value.A); } // формируем связи на основе триангуляции graph.Edges = new List <Edge>(resultTriangles.Count * 2); foreach (var tr in resultTriangles) { graph.AddEdge(tr[0].Original, tr[1].Original); graph.AddEdge(tr[1].Original, tr[2].Original); graph.AddEdge(tr[2].Original, tr[0].Original); } // пытаемся оставить только нужное кол-во связей, при этом проверяя что все узлы соеденены var needEdges = (int)(graph.Edges.Count / 100f * edgePercent); var rnd = new Random(); var testEdges = new List <Edge>(graph.Edges); while (testEdges.Count > 0 && needEdges < graph.Edges.Count) { var removeIndex = rnd.Next(testEdges.Count); var testEdge = testEdges[removeIndex]; if (graph.HasWayBetweenWithoutEdge(testEdge.A, testEdge.B)) { graph.RemoveEdge(testEdge.A, testEdge.B); } testEdges.RemoveAt(removeIndex); } return(graph); }