private static void AddHorizontalBorder(BoostVoronoi voronoi, RTree <Edge> wallsTree, IReadOnlyList <int> border, int y, int maxX) { var index = 0; if (!border.Any()) { voronoi.AddSegment(0, y, maxX, y); wallsTree.Insert(new Edge { Start = new Point2D(0, y), End = new Point2D(maxX, y) }, new RTree <Edge> .Envelope(0, y, maxX, y)); return; } var firstX = border[index++]; if (firstX != 0) { var x2 = index >= border.Count ? maxX : border[index++]; voronoi.AddSegment(firstX, y, x2, y); wallsTree.Insert(new Edge { Start = new Point2D(firstX, y), End = new Point2D(x2, y) }, new RTree <Edge> .Envelope(firstX, y, x2, y)); } while (index < border.Count) { var x1 = border[index++]; var x2 = index >= border.Count ? maxX : border[index++]; voronoi.AddSegment(x1, y, x2, y); wallsTree.Insert(new Edge { Start = new Point2D(x1, y), End = new Point2D(x2, y) }, new RTree <Edge> .Envelope(x1, y, x2, y)); } }
private static void AddVerticalBorder(BoostVoronoi voronoi, RTree <Edge> wallsTree, IReadOnlyList <int> border, int x, int maxY) { var index = 0; if (!border.Any()) { voronoi.AddSegment(x, 0, x, maxY); wallsTree.Insert(new Edge { Start = new Point2D(x, 0), End = new Point2D(x, maxY) }, new RTree <Edge> .Envelope(x, 0, x, maxY)); return; } var firstY = border[index++]; if (firstY != 0) { var y2 = index >= border.Count ? maxY : border[index++]; voronoi.AddSegment(x, firstY, x, y2); wallsTree.Insert(new Edge { Start = new Point2D(x, firstY), End = new Point2D(x, y2) }, new RTree <Edge> .Envelope(x, firstY, x, y2)); } while (index < border.Count) { var y1 = border[index++]; var y2 = index >= border.Count ? maxY : border[index++]; voronoi.AddSegment(x, y1, x, y2); wallsTree.Insert(new Edge { Start = new Point2D(x, y1), End = new Point2D(x, y2) }, new RTree <Edge> .Envelope(x, y1, x, y2)); } }
public static IReadOnlyList <TerrainNode> Analyze <TNode>(Map2D <TNode> map, Func <TNode, bool> isBlocking, int pruneDistanceToObstacleSquared, int maxDistanceToMergeSquared) { var tracingResult = ComponentLabeling.Trace(map, node => isBlocking(node) ? 1 : 0); var contours = new List <List <Point2D> >(); foreach (var blob in tracingResult.Blobs) { contours.AddRange( new[] { blob.ExternalContour }.Concat(blob.InternalContours).Select( points => DouglasPeucker.Simplify(points.ToList(), 2 * 2, DistanceSquared))); } var edges = new List <Edge>(); var leftBorder = new List <int>(); var rightBorder = new List <int>(); var topBorder = new List <int>(); var bottomBorder = new List <int>(); var wallsTree = new RTree <Edge>(5, 9); using (var voronoi = new BoostVoronoi()) { foreach (var contour in contours) { var startPoint = contour.First(); foreach (var point in contour.Skip(1)) { voronoi.AddSegment((int)startPoint.X, (int)startPoint.Y, (int)point.X, (int)point.Y); wallsTree.Insert(new Edge { Start = startPoint, End = point }, new RTree <Edge> .Envelope(startPoint, point)); if ((int)startPoint.X == 0 && (int)point.X == 0) { leftBorder.Add((int)startPoint.Y); leftBorder.Add((int)point.Y); } if ((int)startPoint.X == map.Width - 1 && (int)point.X == map.Width - 1) { rightBorder.Add((int)startPoint.Y); rightBorder.Add((int)point.Y); } if ((int)startPoint.Y == 0 && (int)point.Y == 0) { topBorder.Add((int)startPoint.X); topBorder.Add((int)point.X); } if ((int)startPoint.Y == map.Height - 1 && (int)point.Y == map.Height - 1) { bottomBorder.Add((int)startPoint.X); bottomBorder.Add((int)point.X); } startPoint = point; } } AddVerticalBorder(voronoi, wallsTree, leftBorder.OrderBy(x => x).ToList(), 0, map.Height - 1); AddVerticalBorder(voronoi, wallsTree, rightBorder.OrderBy(x => x).ToList(), map.Width - 1, map.Height - 1); AddHorizontalBorder(voronoi, wallsTree, topBorder.OrderBy(x => x).ToList(), 0, map.Width - 1); AddHorizontalBorder(voronoi, wallsTree, bottomBorder.OrderBy(x => x).ToList(), map.Height - 1, map.Width - 1); var visitedTwins = new List <long>(); voronoi.Construct(); for (long i = 0; i < voronoi.CountEdges; i++) { var edge = voronoi.GetEdge(i); if (!edge.IsPrimary || !edge.IsFinite || visitedTwins.Contains(edge.Twin)) { continue; } visitedTwins.Add(edge.Twin); var start = voronoi.GetVertex(edge.Start); var end = voronoi.GetVertex(edge.End); if (double.IsNaN(start.X) || double.IsNaN(start.Y) || double.IsNaN(end.X) || double.IsNaN(end.Y)) { continue; } if (edges.Any( e => (int)e.Start.X == (int)start.X && (int)e.Start.Y == (int)start.Y && (int)e.End.X == (int)end.X && (int)e.End.Y == (int)end.Y || (int)e.Start.X == (int)end.X && (int)e.Start.Y == (int)end.Y && (int)e.End.X == (int)start.X && (int)e.End.Y == (int)start.Y)) { continue; } edges.Add(new Edge { Start = new Point2D((int)start.X, (int)start.Y), End = new Point2D((int)end.X, (int)end.Y) }); } } var walkableEdges = edges.Where(edge => IsOnNonBlocking(edge, map, isBlocking)).ToList(); var nodes = new List <InternalNode>(); foreach (var node in walkableEdges) { if (node.Start == node.End) { continue; } var startNode = nodes.Find(x => x.Point == node.Start); if (startNode == null) { startNode = CreateNode(wallsTree, node.Start); nodes.Add(startNode); } var endNode = nodes.Find(x => x.Point == node.End); if (endNode == null) { endNode = CreateNode(wallsTree, node.End); nodes.Add(endNode); } startNode.Neighbors.Add(endNode); endNode.Neighbors.Add(startNode); } PruneNodes(nodes, pruneDistanceToObstacleSquared); GetPointsOfInterest(nodes, pruneDistanceToObstacleSquared, maxDistanceToMergeSquared); var mergedNodes = MergeNodes(nodes, maxDistanceToMergeSquared); return(MapToTerrainNodes(mergedNodes, wallsTree)); }