* @brief Cost-based path search (Dijkstra / A*) for pathfinding through boxes. * * This function expands the search using a priority queue ordered by accumulated * path cost. It builds a "flow field" where each box's exitBox points toward the * target, preferring lower-cost paths. When A* is selected, a heuristic is added * to guide the search toward the creature's source box. * * ALGORITHM
| 1889 | * @return true if more boxes remain to expand, false if search exhausted. |
| 1890 | */ |
| 1891 | bool SearchLOT_DijkstraAStar(LOTInfo* LOT, int depth, PathfindingMode mode) |
| 1892 | { |
| 1893 | PathQueue queue = {}; |
| 1894 | auto& zone = g_Level.Zones[(int)LOT->Zone][(int)FlipStatus]; |
| 1895 | |
| 1896 | // Determine whether A* heuristic should be applied. |
| 1897 | bool useHeuristic = (mode == PathfindingMode::AStar && LOT->SourceBox != NO_VALUE); |
| 1898 | |
| 1899 | // A* heuristic target (creature's source box center). |
| 1900 | auto sourceCenter = useHeuristic ? GetBoxCenter(LOT->SourceBox) : Vector3::Zero; |
| 1901 | |
| 1902 | // Move legacy Head/Tail expansion list into priority queue. |
| 1903 | int currentBox = LOT->Head; |
| 1904 | while (currentBox != NO_VALUE) |
| 1905 | { |
| 1906 | auto* node = &LOT->Node[currentBox]; |
| 1907 | |
| 1908 | int next = node->nextExpansion; |
| 1909 | node->nextExpansion = NO_VALUE; |
| 1910 | |
| 1911 | float pathCost = node->cost; |
| 1912 | float heuristicCost = useHeuristic ? Vector3::Distance(GetBoxCenter(currentBox), sourceCenter) : 0.0f; |
| 1913 | |
| 1914 | queue.push({ pathCost + heuristicCost, pathCost, currentBox }); |
| 1915 | |
| 1916 | currentBox = next; |
| 1917 | } |
| 1918 | |
| 1919 | // Reset legacy queue; it will be rebuilt from remaining PQ entries. |
| 1920 | LOT->Head = NO_VALUE; |
| 1921 | LOT->Tail = NO_VALUE; |
| 1922 | |
| 1923 | int expansions = 0; |
| 1924 | |
| 1925 | // Main Dijkstra / A* expansion loop. |
| 1926 | while (expansions < depth && !queue.empty()) |
| 1927 | { |
| 1928 | auto [currentEstimatedCost, currentPathCost, headBox] = queue.top(); |
| 1929 | queue.pop(); |
| 1930 | |
| 1931 | auto* box = &g_Level.PathfindingBoxes[headBox]; |
| 1932 | auto* node = &LOT->Node[headBox]; |
| 1933 | |
| 1934 | // Skip stale queue entries superseded by a cheaper path. |
| 1935 | if (currentPathCost > node->cost) |
| 1936 | continue; |
| 1937 | |
| 1938 | expansions++; |
| 1939 | |
| 1940 | int index = box->overlapIndex; |
| 1941 | int searchZone = zone[headBox]; |
| 1942 | |
| 1943 | auto currentCenter = GetBoxCenter(headBox); |
| 1944 | bool done = false; |
| 1945 | |
| 1946 | // Iterate through all overlapping neighbor boxes. |
| 1947 | if (index >= 0) |
| 1948 | { |
no test coverage detected