private static InternalNode CreateNode(RTree <Edge> wallsTree, Point2D point) { var closestObstacle = wallsTree.GetKNearest(1, point, (edge, p) => Math.Sqrt(DistanceSquared(p, edge.Start, edge.End))).First().Data; return(new InternalNode { Point = point, Neighbors = new List <InternalNode>(), MinDistanceToObstacleSquared = DistanceSquared(point, closestObstacle.Start, closestObstacle.End) }); }
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)); }
private static IReadOnlyList <TerrainNode> MapToTerrainNodes(IEnumerable <InternalNode> nodes, RTree <Edge> wallsTree) { var mappedNodes = new Dictionary <InternalNode, TerrainNode>(); var terrainNodes = new List <TerrainNode>(); foreach (var node in nodes) { if (mappedNodes.TryGetValue(node, out var terrainNode)) { terrainNodes.Add(terrainNode); continue; } terrainNodes.Add(MapToTerrainNode(node, mappedNodes, wallsTree)); } return(terrainNodes); }
private static TerrainNode MapToTerrainNode(InternalNode internalNode, IDictionary <InternalNode, TerrainNode> mappedNodes, RTree <Edge> wallsTree) { if (mappedNodes.TryGetValue(internalNode, out var terrainNode)) { return(terrainNode); } var neighbors = new List <TerrainNode>(); terrainNode = new TerrainNode(internalNode.Point, internalNode.Type == InternalNode.NodeType.Choke, neighbors); if (internalNode.Type == InternalNode.NodeType.Choke) { var chokeEdge = wallsTree.GetKNearest(1, internalNode.Point, (edge, p) => Math.Sqrt(DistanceSquared(p, edge.Start, edge.End))).First().Data; terrainNode.ChokeRadius = (int)(Math.Sqrt(DistanceSquared(internalNode.Point, chokeEdge.Start, chokeEdge.End))); } mappedNodes[internalNode] = terrainNode; neighbors.AddRange(internalNode.Neighbors.Select(x => MapToTerrainNode(x, mappedNodes, wallsTree))); return(terrainNode); }