Beispiel #1
0
        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)
            });
        }
Beispiel #2
0
        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));
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        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);
        }