Exemple #1
0
        internal static Path FindPath(NodeHandle startNode, NodeHandle targetNode, ConcurrentSet <NodeHandle> closedSet, uint maxMs, CancellationToken cancelToken, uint clearance, Action <NodeHandle> progress, bool debug = false)
        {
            var s = new Stopwatch();

            findPathTimer.Start();
            try {
                Vector3 targetNodePos = Position(targetNode);

                if (startNode == 0)
                {
                    return(Fail($"[{targetNode}] FindPath failed: startNode is zero."));
                }
                if (targetNode == 0)
                {
                    return(Fail($"[{targetNode}] FindPath failed: targetNode is zero."));
                }

                closedSetTimer.Start();
                closedSet.Remove(startNode);
                if (closedSet.Contains(targetNode))
                {
                    return(Fail($"[{targetNode}] FindPath failed: targetNode is blocked"));
                }
                closedSetTimer.Stop();

                // TODO: it would be best if we could combine fScore and openSet
                // fScore should be a heap that re-heaps when a value updates
                // isOpen(node) becomes fScore.Contains(node)
                // var fScore = new Dictionary<NodeHandle, float>();
                fScoreTimer.Start();
                var fScore = new Heap <NodeHandle>();
                fScore.Add(startNode, Estimate(startNode, targetNode));
                fScoreTimer.Stop();

                var cameFrom = new Dictionary <NodeHandle, NodeHandle>();

                var gScore = new Dictionary <NodeHandle, float>();
                gScore.TryAdd(startNode, 0);
                float GScore(NodeHandle n) => gScore.ContainsKey(n) ? gScore[n] : float.MaxValue;

                s.Start();
                fScoreTimer.Start();
                while (fScore.TryPop(out NodeHandle best))
                {
                    fScoreTimer.Stop();
                    if (cancelToken.IsCancellationRequested)
                    {
                        return(Fail($"[{targetNode}] Cancelled."));
                    }
                    if (s.ElapsedMilliseconds > maxMs)
                    {
                        return(Fail($"[{targetNode}] Searching for too long, ({closedSet.Count} nodes in {s.ElapsedMilliseconds}ms."));
                    }

                    // close this node we are just about to visit
                    closedSetTimer.Start();
                    closedSet.Add(best);
                    closedSetTimer.Stop();

                    // update the progress callback
                    progress(best);

                    Vector3 curPos = Position(best);
                    float   dist   = (curPos - targetNodePos).LengthSquared();
                    // Log($"dist = {dist:F2}");
                    if (dist <= .5f)
                    {
                        var ret = new Path(UnrollPath(cameFrom, best, debug));
                        Log($"[{targetNode}] Found a path of {ret.Count()} steps ({closedSet.Count} searched in {s.ElapsedMilliseconds}ms)");
                        return(ret);
                    }

                    foreach (NodeHandle e in Edges(best))
                    {
                        closedSetTimer.Start();
                        bool closed = closedSet.Contains(e);
                        closedSetTimer.Stop();

                        if (!closed && Clearance(e) >= clearance)
                        {
                            gScoreTimer.Start();
                            Vector3 ePos           = Position(e);
                            float   scoreOfNewPath = GScore(best) + (curPos - ePos).Length();
                            float   scoreOfOldPath = GScore(e);
                            gScoreTimer.Stop();

                            if (scoreOfNewPath < scoreOfOldPath)
                            {
                                cameFrom[e] = best;
                                gScore[e]   = scoreOfNewPath;
                                fScoreTimer.Start();
                                fScore.AddOrUpdate(e,
                                                   gScore[e]                       // best path to e so far
                                                   + Estimate(ePos, targetNodePos) // plus standard A* estimate
                                                   + Abs(ePos.Z - curPos.Z)        // plus a penalty for going vertical
                                                   + ((15 - Clearance(e)) * .3f)   // plus a penalty for low clearance
                                                   );
                                fScoreTimer.Stop();
                            }
                        }
                    }
                    fScoreTimer.Start();
                }
                return(Fail($"[{targetNode}] Searched all reachable nodes ({closedSet.Count} nodes in {s.ElapsedMilliseconds}ms)."));
            } finally {
                findPathTimer.Stop();
                fScoreTimer.Stop();
            }
        }