1
0
forked from 0ad/0ad

Optimisations to vertex pathfinder - relax optimality, sort unaligned edges

Two changes to the vertex pathfinder that should generally improve
performance:
 - Sort unaligned edges by distance like we did aligned edges. This
isn't very scientific, but the idea is generally sound, and empirically
it seems to do OK.
 - Relax the optimality by using a weighted heuristic, with weight 4/3
or 5/3 depending on the number of obstacles around. In the worst cases,
A* will return paths that are this many times less optimal, but search
should be faster in general (and sometimes much faster).

Both of these optimisations might increase the constant-cost slightly,
but should decrease the worst-case runtimes.

Differential Revision: https://code.wildfiregames.com/D5034
This was SVN commit r27724.
This commit is contained in:
wraitii 2023-06-20 07:14:11 +00:00
parent c917c39a0d
commit 73ec56a44e

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -514,9 +514,20 @@ struct SquareSort
SquareSort(CFixedVector2D src) : src(src) { }
bool operator()(const Square& a, const Square& b) const
{
if ((a.p0 - src).CompareLength(b.p0 - src) < 0)
return true;
return false;
return (a.p0 - src).CompareLength(b.p0 - src) < 0;
}
};
/**
* Functor for sorting unaligned edges by approximate proximity to a fixed point.
*/
struct UnalignedEdgesSort
{
CFixedVector2D src;
UnalignedEdgesSort(CFixedVector2D src) : src(src) { }
bool operator()(const Edge& a, const Edge& b) const
{
return (a.p0 - src).CompareLength(b.p0 - src) < 0;
}
};
@ -710,6 +721,17 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request,
// Since we are just measuring Euclidean distance the heuristic is admissible,
// so we never have to re-examine a node once it's been moved to the closed set.
// Add a weight, as we vastly prefer speed to optimality.
// In general, if we're running the vertex pathfinder,
// it's that we're already in a suboptimal case on a meta level.
// The path found will be at most this many times worse than the optimal path, which is likely OK.
fixed heuristicWeight = fixed::FromInt(1);
// If we have a lot of edges to check, relax the constraint more.
if (m_Edges.size() > 100 || m_EdgeSquares.size() > 100)
heuristicWeight = heuristicWeight.MulDiv(fixed::FromInt(5), fixed::FromInt(3));
else
heuristicWeight = heuristicWeight.MulDiv(fixed::FromInt(4), fixed::FromInt(3));
// To save time in common cases, we don't precompute a graph of valid edges between vertexes;
// we do it lazily instead. When the search algorithm reaches a vertex, we examine every other
// vertex and see if we can reach it without hitting any collision edges, and ignore the ones
@ -750,6 +772,12 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request,
m_EdgesTop.clear();
SplitAAEdges(m_Vertexes[curr.id].p, m_Edges, m_EdgeSquares, m_EdgesUnaligned, m_EdgesLeft, m_EdgesRight, m_EdgesBottom, m_EdgesTop);
// Do the same partial sort for unaligned edges, which helps in e.g. forests.
// (Higher values because here we need ot account for all 4 edges,
// and checking for non-aligned visibility is very slow so it's likely worth it).
if (m_EdgesUnaligned.size() > 28)
std::partial_sort(m_EdgesUnaligned.begin(), m_EdgesUnaligned.begin() + 28, m_EdgesUnaligned.end(), UnalignedEdgesSort(m_Vertexes[curr.id].p));
// Check the lines to every other vertex
for (size_t n = 0; n < m_Vertexes.size(); ++n)
{
@ -807,7 +835,7 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request,
// Add it to the open list:
m_Vertexes[n].status = Vertex::OPEN;
m_Vertexes[n].g = g;
m_Vertexes[n].h = request.goal.DistanceToPoint(npos);
m_Vertexes[n].h = request.goal.DistanceToPoint(npos).Multiply(heuristicWeight);
m_Vertexes[n].pred = curr.id;
if (n == GOAL_VERTEX_ID)