Move the Vertex Pathfinder to its own helper class
The vertex pathfinder was implemented directly in CCmpPathfinder, instead of being a separate helper like the hierarchical pathfinder or the long pathfinder. This moves it to its own helper VertexPathfinder, which gets us ready for D14 and pathfinder threading. Some struct definitions need to be moved around. Differential Revision: https://code.wildfiregames.com/D1855 This was SVN commit r22253.
This commit is contained in:
parent
6970a168d9
commit
809f297707
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2018 Wildfire Games.
|
||||
/* Copyright (C) 2019 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -35,6 +35,7 @@
|
||||
#include "simulation2/components/ICmpTerrain.h"
|
||||
#include "simulation2/components/ICmpWaterManager.h"
|
||||
#include "simulation2/helpers/Rasterize.h"
|
||||
#include "simulation2/helpers/VertexPathfinder.h"
|
||||
#include "simulation2/serialization/SerializeTemplates.h"
|
||||
|
||||
REGISTER_COMPONENT_TYPE(Pathfinder)
|
||||
@ -49,11 +50,12 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode))
|
||||
|
||||
m_NextAsyncTicket = 1;
|
||||
|
||||
m_DebugOverlay = false;
|
||||
m_AtlasOverlay = NULL;
|
||||
|
||||
m_SameTurnMovesCount = 0;
|
||||
|
||||
m_VertexPathfinder = std::unique_ptr<VertexPathfinder>(new VertexPathfinder(m_MapSize, m_TerrainOnlyGrid));
|
||||
|
||||
// Register Relax NG validator
|
||||
CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
|
||||
|
||||
@ -93,6 +95,8 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode))
|
||||
}
|
||||
}
|
||||
|
||||
CCmpPathfinder::~CCmpPathfinder() {};
|
||||
|
||||
void CCmpPathfinder::Deinit()
|
||||
{
|
||||
SetDebugOverlay(false); // cleans up memory
|
||||
@ -183,12 +187,31 @@ void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global))
|
||||
|
||||
void CCmpPathfinder::RenderSubmit(SceneCollector& collector)
|
||||
{
|
||||
for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i)
|
||||
collector.Submit(&m_DebugOverlayShortPathLines[i]);
|
||||
|
||||
m_VertexPathfinder->RenderSubmit(collector);
|
||||
m_LongPathfinder.HierarchicalRenderSubmit(collector);
|
||||
}
|
||||
|
||||
void CCmpPathfinder::SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass)
|
||||
{
|
||||
m_LongPathfinder.SetDebugPath(x0, z0, goal, passClass);
|
||||
}
|
||||
|
||||
void CCmpPathfinder::SetDebugOverlay(bool enabled)
|
||||
{
|
||||
m_VertexPathfinder->SetDebugOverlay(enabled);
|
||||
m_LongPathfinder.SetDebugOverlay(enabled);
|
||||
}
|
||||
|
||||
void CCmpPathfinder::SetHierDebugOverlay(bool enabled)
|
||||
{
|
||||
m_LongPathfinder.SetHierDebugOverlay(enabled, &GetSimContext());
|
||||
}
|
||||
|
||||
void CCmpPathfinder::GetDebugData(u32& steps, double& time, Grid<u8>& grid) const
|
||||
{
|
||||
m_LongPathfinder.GetDebugData(steps, time, grid);
|
||||
}
|
||||
|
||||
void CCmpPathfinder::SetAtlasOverlay(bool enable, pass_class_t passClass)
|
||||
{
|
||||
if (enable)
|
||||
@ -672,8 +695,6 @@ void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability/* = true */)
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
// Async path requests:
|
||||
|
||||
u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify)
|
||||
{
|
||||
AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, notify };
|
||||
@ -688,6 +709,13 @@ u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, enti
|
||||
return req.ticket;
|
||||
}
|
||||
|
||||
WaypointPath CCmpPathfinder::ComputeShortPath(const AsyncShortPathRequest& request) const
|
||||
{
|
||||
return m_VertexPathfinder->ComputeShortPath(request, CmpPtr<ICmpObstructionManager>(GetSystemEntity()));
|
||||
}
|
||||
|
||||
// Async processing:
|
||||
|
||||
void CCmpPathfinder::FinishAsyncRequests()
|
||||
{
|
||||
PROFILE2("Finish Async Requests");
|
||||
@ -726,9 +754,7 @@ void CCmpPathfinder::ProcessShortRequests(const std::vector<AsyncShortPathReques
|
||||
for (size_t i = 0; i < shortRequests.size(); ++i)
|
||||
{
|
||||
const AsyncShortPathRequest& req = shortRequests[i];
|
||||
WaypointPath path;
|
||||
ControlGroupMovementObstructionFilter filter(req.avoidMovingUnits, req.group);
|
||||
ComputeShortPath(filter, req.x0, req.z0, req.clearance, req.range, req.goal, req.passClass, path);
|
||||
WaypointPath path = m_VertexPathfinder->ComputeShortPath(req, CmpPtr<ICmpObstructionManager>(GetSystemEntity()));
|
||||
CMessagePathResult msg(req.ticket, path);
|
||||
GetSimContext().GetComponentManager().PostMessage(req.notify, msg);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Wildfire Games.
|
||||
/* Copyright (C) 2019 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -35,9 +35,12 @@
|
||||
#include "graphics/Terrain.h"
|
||||
#include "maths/MathUtil.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "renderer/TerrainOverlay.h"
|
||||
#include "simulation2/components/ICmpObstructionManager.h"
|
||||
#include "simulation2/helpers/LongPathfinder.h"
|
||||
|
||||
class VertexPathfinder;
|
||||
|
||||
class SceneCollector;
|
||||
class AtlasOverlay;
|
||||
|
||||
@ -47,78 +50,10 @@ class AtlasOverlay;
|
||||
#define PATHFIND_DEBUG 1
|
||||
#endif
|
||||
|
||||
|
||||
struct AsyncLongPathRequest
|
||||
{
|
||||
u32 ticket;
|
||||
entity_pos_t x0;
|
||||
entity_pos_t z0;
|
||||
PathGoal goal;
|
||||
pass_class_t passClass;
|
||||
entity_id_t notify;
|
||||
};
|
||||
|
||||
struct AsyncShortPathRequest
|
||||
{
|
||||
u32 ticket;
|
||||
entity_pos_t x0;
|
||||
entity_pos_t z0;
|
||||
entity_pos_t clearance;
|
||||
entity_pos_t range;
|
||||
PathGoal goal;
|
||||
pass_class_t passClass;
|
||||
bool avoidMovingUnits;
|
||||
entity_id_t group;
|
||||
entity_id_t notify;
|
||||
};
|
||||
|
||||
// A vertex around the corners of an obstruction
|
||||
// (paths will be sequences of these vertexes)
|
||||
struct Vertex
|
||||
{
|
||||
enum
|
||||
{
|
||||
UNEXPLORED,
|
||||
OPEN,
|
||||
CLOSED,
|
||||
};
|
||||
|
||||
CFixedVector2D p;
|
||||
fixed g, h;
|
||||
u16 pred;
|
||||
u8 status;
|
||||
u8 quadInward : 4; // the quadrant which is inside the shape (or NONE)
|
||||
u8 quadOutward : 4; // the quadrants of the next point on the path which this vertex must be in, given 'pred'
|
||||
};
|
||||
|
||||
// Obstruction edges (paths will not cross any of these).
|
||||
// Defines the two points of the edge.
|
||||
struct Edge
|
||||
{
|
||||
CFixedVector2D p0, p1;
|
||||
};
|
||||
|
||||
// Axis-aligned obstruction squares (paths will not cross any of these).
|
||||
// Defines the opposing corners of an axis-aligned square
|
||||
// (from which four individual edges can be trivially computed), requiring p0 <= p1
|
||||
struct Square
|
||||
{
|
||||
CFixedVector2D p0, p1;
|
||||
};
|
||||
|
||||
// Axis-aligned obstruction edges.
|
||||
// p0 defines one end; c1 is either the X or Y coordinate of the other end,
|
||||
// depending on the context in which this is used.
|
||||
struct EdgeAA
|
||||
{
|
||||
CFixedVector2D p0;
|
||||
fixed c1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of ICmpPathfinder
|
||||
*/
|
||||
class CCmpPathfinder : public ICmpPathfinder
|
||||
class CCmpPathfinder final : public ICmpPathfinder
|
||||
{
|
||||
public:
|
||||
static void ClassInit(CComponentManager& componentManager)
|
||||
@ -131,6 +66,8 @@ public:
|
||||
componentManager.SubscribeToMessageType(MT_TurnStart);
|
||||
}
|
||||
|
||||
~CCmpPathfinder();
|
||||
|
||||
DEFAULT_COMPONENT_ALLOCATOR(Pathfinder)
|
||||
|
||||
// Template state:
|
||||
@ -158,6 +95,7 @@ public:
|
||||
GridUpdateInformation m_AIPathfinderDirtinessInformation;
|
||||
bool m_TerrainDirty;
|
||||
|
||||
std::unique_ptr<VertexPathfinder> m_VertexPathfinder;
|
||||
// Interface to the long-range pathfinder.
|
||||
LongPathfinder m_LongPathfinder;
|
||||
|
||||
@ -165,23 +103,6 @@ public:
|
||||
|
||||
u16 m_MaxSameTurnMoves; // max number of moves that can be created and processed in the same turn
|
||||
|
||||
// memory optimizations: those vectors are created once, reused for all calculations;
|
||||
std::vector<Edge> edgesUnaligned;
|
||||
std::vector<EdgeAA> edgesLeft;
|
||||
std::vector<EdgeAA> edgesRight;
|
||||
std::vector<EdgeAA> edgesBottom;
|
||||
std::vector<EdgeAA> edgesTop;
|
||||
|
||||
// List of obstruction vertexes (plus start/end points); we'll try to find paths through
|
||||
// the graph defined by these vertexes
|
||||
std::vector<Vertex> vertexes;
|
||||
// List of collision edges - paths must never cross these.
|
||||
// (Edges are one-sided so intersections are fine in one direction, but not the other direction.)
|
||||
std::vector<Edge> edges;
|
||||
std::vector<Square> edgeSquares; // axis-aligned squares; equivalent to 4 edges
|
||||
|
||||
bool m_DebugOverlay;
|
||||
std::vector<SOverlayLine> m_DebugOverlayShortPathLines;
|
||||
AtlasOverlay* m_AtlasOverlay;
|
||||
|
||||
static std::string GetSchema()
|
||||
@ -252,30 +173,17 @@ public:
|
||||
|
||||
virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify);
|
||||
|
||||
virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t clearance, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, WaypointPath& ret);
|
||||
virtual WaypointPath ComputeShortPath(const AsyncShortPathRequest& request) const;
|
||||
|
||||
virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t clearance, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify);
|
||||
|
||||
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass)
|
||||
{
|
||||
m_LongPathfinder.SetDebugPath(x0, z0, goal, passClass);
|
||||
}
|
||||
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass);
|
||||
|
||||
virtual void SetDebugOverlay(bool enabled)
|
||||
{
|
||||
m_DebugOverlay = enabled;
|
||||
m_LongPathfinder.SetDebugOverlay(enabled);
|
||||
}
|
||||
virtual void SetDebugOverlay(bool enabled);
|
||||
|
||||
virtual void SetHierDebugOverlay(bool enabled)
|
||||
{
|
||||
m_LongPathfinder.SetHierDebugOverlay(enabled, &GetSimContext());
|
||||
}
|
||||
virtual void SetHierDebugOverlay(bool enabled);
|
||||
|
||||
virtual void GetDebugData(u32& steps, double& time, Grid<u8>& grid) const
|
||||
{
|
||||
m_LongPathfinder.GetDebugData(steps, time, grid);
|
||||
}
|
||||
virtual void GetDebugData(u32& steps, double& time, Grid<u8>& grid) const;
|
||||
|
||||
virtual void SetAtlasOverlay(bool enable, pass_class_t passClass = 0);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Wildfire Games.
|
||||
/* Copyright (C) 2019 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -114,7 +114,7 @@ public:
|
||||
* a unit of clearance 'clearance' will be able to follow the path with no collisions.
|
||||
* The path is restricted to a box of radius 'range' from the starting point.
|
||||
*/
|
||||
virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t clearance, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, WaypointPath& ret) = 0;
|
||||
virtual WaypointPath ComputeShortPath(const AsyncShortPathRequest& request) const = 0;
|
||||
|
||||
/**
|
||||
* Asynchronous version of ComputeShortPath (using ControlGroupObstructionFilter).
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
#include "simulation2/components/ICmpObstructionManager.h"
|
||||
#include "simulation2/components/ICmpPathfinder.h"
|
||||
#include "simulation2/components/CCmpPathfinder_Common.h"
|
||||
|
||||
#include "graphics/MapReader.h"
|
||||
#include "graphics/Terrain.h"
|
||||
@ -214,10 +213,8 @@ public:
|
||||
cmpObstructionMan->AddUnitShape(INVALID_ENTITY, x, z, fixed::FromInt(2), 0, INVALID_ENTITY);
|
||||
}
|
||||
|
||||
NullObstructionFilter filter;
|
||||
PathGoal goal = { PathGoal::POINT, range, range };
|
||||
WaypointPath path;
|
||||
cmpPathfinder->ComputeShortPath(filter, range/3, range/3, fixed::FromInt(2), range, goal, 0, path);
|
||||
WaypointPath path = cmpPathfinder->ComputeShortPath(AsyncShortPathRequest{ 0, range/3, range/3, fixed::FromInt(2), range, goal, 0, false, 0, 0 });
|
||||
for (size_t i = 0; i < path.m_Waypoints.size(); ++i)
|
||||
printf("# %d: %f %f\n", (int)i, path.m_Waypoints[i].x.ToFloat(), path.m_Waypoints[i].z.ToFloat());
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
/* Copyright (C) 2019 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -21,6 +21,7 @@
|
||||
#include "maths/MathUtil.h"
|
||||
#include "ps/CLogger.h"
|
||||
|
||||
#include "simulation2/system/Entity.h"
|
||||
#include "simulation2/system/ParamNode.h"
|
||||
#include "graphics/Terrain.h"
|
||||
#include "Grid.h"
|
||||
@ -28,6 +29,30 @@
|
||||
|
||||
typedef u16 pass_class_t;
|
||||
|
||||
struct AsyncLongPathRequest
|
||||
{
|
||||
u32 ticket;
|
||||
entity_pos_t x0;
|
||||
entity_pos_t z0;
|
||||
PathGoal goal;
|
||||
pass_class_t passClass;
|
||||
entity_id_t notify;
|
||||
};
|
||||
|
||||
struct AsyncShortPathRequest
|
||||
{
|
||||
u32 ticket;
|
||||
entity_pos_t x0;
|
||||
entity_pos_t z0;
|
||||
entity_pos_t clearance;
|
||||
entity_pos_t range;
|
||||
PathGoal goal;
|
||||
pass_class_t passClass;
|
||||
bool avoidMovingUnits;
|
||||
entity_id_t group;
|
||||
entity_id_t notify;
|
||||
};
|
||||
|
||||
struct Waypoint
|
||||
{
|
||||
entity_pos_t x, z;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Wildfire Games.
|
||||
/* Copyright (C) 2019 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -32,13 +32,15 @@
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "CCmpPathfinder_Common.h"
|
||||
#include "VertexPathfinder.h"
|
||||
|
||||
#include "lib/timer.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "renderer/Scene.h"
|
||||
#include "simulation2/components/ICmpObstructionManager.h"
|
||||
#include "simulation2/helpers/PriorityQueue.h"
|
||||
#include "simulation2/helpers/Render.h"
|
||||
#include "simulation2/system/SimContext.h"
|
||||
|
||||
/* Quadrant optimisation:
|
||||
* (loosely based on GPG2 "Optimizing Points-of-Visibility Pathfinding")
|
||||
@ -257,7 +259,6 @@ static void AddTerrainEdges(std::vector<Edge>& edges, std::vector<Vertex>& verte
|
||||
int i0, int j0, int i1, int j1,
|
||||
pass_class_t passClass, const Grid<NavcellData>& grid)
|
||||
{
|
||||
PROFILE("AddTerrainEdges");
|
||||
|
||||
// Clamp the coordinates so we won't attempt to sample outside of the grid.
|
||||
// (This assumes the outermost ring of navcells (which are always impassable)
|
||||
@ -509,87 +510,50 @@ struct SquareSort
|
||||
}
|
||||
};
|
||||
|
||||
void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
entity_pos_t x0, entity_pos_t z0, entity_pos_t clearance,
|
||||
entity_pos_t range, const PathGoal& goal, pass_class_t passClass, WaypointPath& path)
|
||||
WaypointPath VertexPathfinder::ComputeShortPath(const AsyncShortPathRequest& request, CmpPtr<ICmpObstructionManager> cmpObstructionManager) const
|
||||
{
|
||||
PROFILE("ComputeShortPath");
|
||||
m_DebugOverlayShortPathLines.clear();
|
||||
PROFILE2("ComputeShortPath");
|
||||
|
||||
if (m_DebugOverlay)
|
||||
{
|
||||
// Render the goal shape
|
||||
m_DebugOverlayShortPathLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 0, 0, 1);
|
||||
switch (goal.type)
|
||||
{
|
||||
case PathGoal::POINT:
|
||||
{
|
||||
SimRender::ConstructCircleOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), 0.2f, m_DebugOverlayShortPathLines.back(), true);
|
||||
break;
|
||||
}
|
||||
case PathGoal::CIRCLE:
|
||||
case PathGoal::INVERTED_CIRCLE:
|
||||
{
|
||||
SimRender::ConstructCircleOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat(), m_DebugOverlayShortPathLines.back(), true);
|
||||
break;
|
||||
}
|
||||
case PathGoal::SQUARE:
|
||||
case PathGoal::INVERTED_SQUARE:
|
||||
{
|
||||
float a = atan2f(goal.v.X.ToFloat(), goal.v.Y.ToFloat());
|
||||
SimRender::ConstructSquareOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat()*2, goal.hh.ToFloat()*2, a, m_DebugOverlayShortPathLines.back(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List of collision edges - paths must never cross these.
|
||||
// (Edges are one-sided so intersections are fine in one direction, but not the other direction.)
|
||||
edges.clear();
|
||||
edgeSquares.clear(); // axis-aligned squares; equivalent to 4 edges
|
||||
DebugRenderGoal(cmpObstructionManager->GetSimContext(), request.goal);
|
||||
|
||||
// Create impassable edges at the max-range boundary, so we can't escape the region
|
||||
// where we're meant to be searching
|
||||
fixed rangeXMin = x0 - range;
|
||||
fixed rangeXMax = x0 + range;
|
||||
fixed rangeZMin = z0 - range;
|
||||
fixed rangeZMax = z0 + range;
|
||||
fixed rangeXMin = request.x0 - request.range;
|
||||
fixed rangeXMax = request.x0 + request.range;
|
||||
fixed rangeZMin = request.z0 - request.range;
|
||||
fixed rangeZMax = request.z0 + request.range;
|
||||
|
||||
// we don't actually add the "search space" edges as edges, since we may want to cross them
|
||||
// in some cases (such as if we need to go around an obstruction that's partly out of the search range)
|
||||
|
||||
// List of obstruction vertexes (plus start/end points); we'll try to find paths through
|
||||
// the graph defined by these vertexes
|
||||
vertexes.clear();
|
||||
|
||||
// Add the start point to the graph
|
||||
CFixedVector2D posStart(x0, z0);
|
||||
fixed hStart = (posStart - goal.NearestPointOnGoal(posStart)).Length();
|
||||
CFixedVector2D posStart(request.x0, request.z0);
|
||||
fixed hStart = (posStart - request.goal.NearestPointOnGoal(posStart)).Length();
|
||||
Vertex start = { posStart, fixed::Zero(), hStart, 0, Vertex::OPEN, QUADRANT_NONE, QUADRANT_ALL };
|
||||
vertexes.push_back(start);
|
||||
m_Vertexes.push_back(start);
|
||||
const size_t START_VERTEX_ID = 0;
|
||||
|
||||
// Add the goal vertex to the graph.
|
||||
// Since the goal isn't always a point, this a special magic virtual vertex which moves around - whenever
|
||||
// we look at it from another vertex, it is moved to be the closest point on the goal shape to that vertex.
|
||||
Vertex end = { CFixedVector2D(goal.x, goal.z), fixed::Zero(), fixed::Zero(), 0, Vertex::UNEXPLORED, QUADRANT_NONE, QUADRANT_ALL };
|
||||
vertexes.push_back(end);
|
||||
Vertex end = { CFixedVector2D(request.goal.x, request.goal.z), fixed::Zero(), fixed::Zero(), 0, Vertex::UNEXPLORED, QUADRANT_NONE, QUADRANT_ALL };
|
||||
m_Vertexes.push_back(end);
|
||||
const size_t GOAL_VERTEX_ID = 1;
|
||||
|
||||
// Find all the obstruction squares that might affect us
|
||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
|
||||
std::vector<ICmpObstructionManager::ObstructionSquare> squares;
|
||||
size_t staticShapesNb = 0;
|
||||
cmpObstructionManager->GetStaticObstructionsInRange(filter, rangeXMin - clearance, rangeZMin - clearance, rangeXMax + clearance, rangeZMax + clearance, squares);
|
||||
ControlGroupMovementObstructionFilter filter(request.avoidMovingUnits, request.group);
|
||||
cmpObstructionManager->GetStaticObstructionsInRange(filter, rangeXMin - request.clearance, rangeZMin - request.clearance, rangeXMax + request.clearance, rangeZMax + request.clearance, squares);
|
||||
staticShapesNb = squares.size();
|
||||
cmpObstructionManager->GetUnitObstructionsInRange(filter, rangeXMin - clearance, rangeZMin - clearance, rangeXMax + clearance, rangeZMax + clearance, squares);
|
||||
cmpObstructionManager->GetUnitObstructionsInRange(filter, rangeXMin - request.clearance, rangeZMin - request.clearance, rangeXMax + request.clearance, rangeZMax + request.clearance, squares);
|
||||
|
||||
// Change array capacities to reduce reallocations
|
||||
vertexes.reserve(vertexes.size() + squares.size()*4);
|
||||
edgeSquares.reserve(edgeSquares.size() + squares.size()); // (assume most squares are AA)
|
||||
m_Vertexes.reserve(m_Vertexes.size() + squares.size()*4);
|
||||
m_EdgeSquares.reserve(m_EdgeSquares.size() + squares.size()); // (assume most squares are AA)
|
||||
|
||||
entity_pos_t pathfindClearance = clearance;
|
||||
entity_pos_t pathfindClearance = request.clearance;
|
||||
|
||||
// Convert each obstruction square into collision edges and search graph vertexes
|
||||
for (size_t i = 0; i < squares.size(); ++i)
|
||||
@ -599,7 +563,7 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
CFixedVector2D v = squares[i].v;
|
||||
|
||||
if (i >= staticShapesNb)
|
||||
pathfindClearance = clearance - entity_pos_t::FromInt(1)/2;
|
||||
pathfindClearance = request.clearance - entity_pos_t::FromInt(1)/2;
|
||||
|
||||
// Expand the vertexes by the moving unit's collision radius, to find the
|
||||
// closest we can get to it
|
||||
@ -614,22 +578,22 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
vert.status = Vertex::UNEXPLORED;
|
||||
vert.quadInward = QUADRANT_NONE;
|
||||
vert.quadOutward = QUADRANT_ALL;
|
||||
vert.p.X = center.X - hd0.Dot(u); vert.p.Y = center.Y + hd0.Dot(v); if (aa) vert.quadInward = QUADRANT_BR; vertexes.push_back(vert);
|
||||
vert.p.X = center.X - hd0.Dot(u); vert.p.Y = center.Y + hd0.Dot(v); if (aa) vert.quadInward = QUADRANT_BR; m_Vertexes.push_back(vert);
|
||||
if (vert.p.X < rangeXMin) rangeXMin = vert.p.X;
|
||||
if (vert.p.Y < rangeZMin) rangeZMin = vert.p.Y;
|
||||
if (vert.p.X > rangeXMax) rangeXMax = vert.p.X;
|
||||
if (vert.p.Y > rangeZMax) rangeZMax = vert.p.Y;
|
||||
vert.p.X = center.X - hd1.Dot(u); vert.p.Y = center.Y + hd1.Dot(v); if (aa) vert.quadInward = QUADRANT_TR; vertexes.push_back(vert);
|
||||
vert.p.X = center.X - hd1.Dot(u); vert.p.Y = center.Y + hd1.Dot(v); if (aa) vert.quadInward = QUADRANT_TR; m_Vertexes.push_back(vert);
|
||||
if (vert.p.X < rangeXMin) rangeXMin = vert.p.X;
|
||||
if (vert.p.Y < rangeZMin) rangeZMin = vert.p.Y;
|
||||
if (vert.p.X > rangeXMax) rangeXMax = vert.p.X;
|
||||
if (vert.p.Y > rangeZMax) rangeZMax = vert.p.Y;
|
||||
vert.p.X = center.X + hd0.Dot(u); vert.p.Y = center.Y - hd0.Dot(v); if (aa) vert.quadInward = QUADRANT_TL; vertexes.push_back(vert);
|
||||
vert.p.X = center.X + hd0.Dot(u); vert.p.Y = center.Y - hd0.Dot(v); if (aa) vert.quadInward = QUADRANT_TL; m_Vertexes.push_back(vert);
|
||||
if (vert.p.X < rangeXMin) rangeXMin = vert.p.X;
|
||||
if (vert.p.Y < rangeZMin) rangeZMin = vert.p.Y;
|
||||
if (vert.p.X > rangeXMax) rangeXMax = vert.p.X;
|
||||
if (vert.p.Y > rangeZMax) rangeZMax = vert.p.Y;
|
||||
vert.p.X = center.X + hd1.Dot(u); vert.p.Y = center.Y - hd1.Dot(v); if (aa) vert.quadInward = QUADRANT_BL; vertexes.push_back(vert);
|
||||
vert.p.X = center.X + hd1.Dot(u); vert.p.Y = center.Y - hd1.Dot(v); if (aa) vert.quadInward = QUADRANT_BL; m_Vertexes.push_back(vert);
|
||||
if (vert.p.X < rangeXMin) rangeXMin = vert.p.X;
|
||||
if (vert.p.Y < rangeZMin) rangeZMin = vert.p.Y;
|
||||
if (vert.p.X > rangeXMax) rangeXMax = vert.p.X;
|
||||
@ -645,13 +609,13 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
CFixedVector2D ev2(center.X + h0.Dot(u), center.Y - h0.Dot(v));
|
||||
CFixedVector2D ev3(center.X + h1.Dot(u), center.Y - h1.Dot(v));
|
||||
if (aa)
|
||||
edgeSquares.emplace_back(Square{ ev1, ev3 });
|
||||
m_EdgeSquares.emplace_back(Square{ ev1, ev3 });
|
||||
else
|
||||
{
|
||||
edges.emplace_back(Edge{ ev0, ev1 });
|
||||
edges.emplace_back(Edge{ ev1, ev2 });
|
||||
edges.emplace_back(Edge{ ev2, ev3 });
|
||||
edges.emplace_back(Edge{ ev3, ev0 });
|
||||
m_Edges.emplace_back(Edge{ ev0, ev1 });
|
||||
m_Edges.emplace_back(Edge{ ev1, ev2 });
|
||||
m_Edges.emplace_back(Edge{ ev2, ev3 });
|
||||
m_Edges.emplace_back(Edge{ ev3, ev0 });
|
||||
}
|
||||
|
||||
// TODO: should clip out vertexes and edges that are outside the range,
|
||||
@ -663,103 +627,32 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
u16 i0, j0, i1, j1;
|
||||
Pathfinding::NearestNavcell(rangeXMin, rangeZMin, i0, j0, m_MapSize*Pathfinding::NAVCELLS_PER_TILE, m_MapSize*Pathfinding::NAVCELLS_PER_TILE);
|
||||
Pathfinding::NearestNavcell(rangeXMax, rangeZMax, i1, j1, m_MapSize*Pathfinding::NAVCELLS_PER_TILE, m_MapSize*Pathfinding::NAVCELLS_PER_TILE);
|
||||
AddTerrainEdges(edges, vertexes, i0, j0, i1, j1, passClass, *m_TerrainOnlyGrid);
|
||||
AddTerrainEdges(m_Edges, m_Vertexes, i0, j0, i1, j1, request.passClass, *m_TerrainOnlyGrid);
|
||||
}
|
||||
|
||||
// Clip out vertices that are inside an edgeSquare (i.e. trivially unreachable)
|
||||
for (size_t i = 0; i < edgeSquares.size(); ++i)
|
||||
for (size_t i = 0; i < m_EdgeSquares.size(); ++i)
|
||||
{
|
||||
// If the start point is inside the square, ignore it
|
||||
if (start.p.X >= edgeSquares[i].p0.X &&
|
||||
start.p.Y >= edgeSquares[i].p0.Y &&
|
||||
start.p.X <= edgeSquares[i].p1.X &&
|
||||
start.p.Y <= edgeSquares[i].p1.Y)
|
||||
if (start.p.X >= m_EdgeSquares[i].p0.X &&
|
||||
start.p.Y >= m_EdgeSquares[i].p0.Y &&
|
||||
start.p.X <= m_EdgeSquares[i].p1.X &&
|
||||
start.p.Y <= m_EdgeSquares[i].p1.Y)
|
||||
continue;
|
||||
|
||||
// Remove every non-start/goal vertex that is inside an edgeSquare;
|
||||
// since remove() would be inefficient, just mark it as closed instead.
|
||||
for (size_t j = 2; j < vertexes.size(); ++j)
|
||||
if (vertexes[j].p.X >= edgeSquares[i].p0.X &&
|
||||
vertexes[j].p.Y >= edgeSquares[i].p0.Y &&
|
||||
vertexes[j].p.X <= edgeSquares[i].p1.X &&
|
||||
vertexes[j].p.Y <= edgeSquares[i].p1.Y)
|
||||
vertexes[j].status = Vertex::CLOSED;
|
||||
for (size_t j = 2; j < m_Vertexes.size(); ++j)
|
||||
if (m_Vertexes[j].p.X >= m_EdgeSquares[i].p0.X &&
|
||||
m_Vertexes[j].p.Y >= m_EdgeSquares[i].p0.Y &&
|
||||
m_Vertexes[j].p.X <= m_EdgeSquares[i].p1.X &&
|
||||
m_Vertexes[j].p.Y <= m_EdgeSquares[i].p1.Y)
|
||||
m_Vertexes[j].status = Vertex::CLOSED;
|
||||
}
|
||||
|
||||
ENSURE(vertexes.size() < 65536); // we store array indexes as u16
|
||||
ENSURE(m_Vertexes.size() < 65536); // We store array indexes as u16.
|
||||
|
||||
// Render the debug overlay
|
||||
if (m_DebugOverlay)
|
||||
{
|
||||
#define PUSH_POINT(p) STMT(xz.push_back(p.X.ToFloat()); xz.push_back(p.Y.ToFloat()))
|
||||
// Render the vertexes as little Pac-Man shapes to indicate quadrant direction
|
||||
for (size_t i = 0; i < vertexes.size(); ++i)
|
||||
{
|
||||
m_DebugOverlayShortPathLines.emplace_back();
|
||||
m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 1, 0, 1);
|
||||
|
||||
float x = vertexes[i].p.X.ToFloat();
|
||||
float z = vertexes[i].p.Y.ToFloat();
|
||||
|
||||
float a0 = 0, a1 = 0;
|
||||
// Get arc start/end angles depending on quadrant (if any)
|
||||
if (vertexes[i].quadInward == QUADRANT_BL) { a0 = -0.25f; a1 = 0.50f; }
|
||||
else if (vertexes[i].quadInward == QUADRANT_TR) { a0 = 0.25f; a1 = 1.00f; }
|
||||
else if (vertexes[i].quadInward == QUADRANT_TL) { a0 = -0.50f; a1 = 0.25f; }
|
||||
else if (vertexes[i].quadInward == QUADRANT_BR) { a0 = 0.00f; a1 = 0.75f; }
|
||||
|
||||
if (a0 == a1)
|
||||
SimRender::ConstructCircleOnGround(GetSimContext(), x, z, 0.5f,
|
||||
m_DebugOverlayShortPathLines.back(), true);
|
||||
else
|
||||
SimRender::ConstructClosedArcOnGround(GetSimContext(), x, z, 0.5f,
|
||||
a0 * ((float)M_PI*2.0f), a1 * ((float)M_PI*2.0f),
|
||||
m_DebugOverlayShortPathLines.back(), true);
|
||||
}
|
||||
|
||||
// Render the edges
|
||||
for (size_t i = 0; i < edges.size(); ++i)
|
||||
{
|
||||
m_DebugOverlayShortPathLines.emplace_back();
|
||||
m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1);
|
||||
std::vector<float> xz;
|
||||
PUSH_POINT(edges[i].p0);
|
||||
PUSH_POINT(edges[i].p1);
|
||||
|
||||
// Add an arrowhead to indicate the direction
|
||||
CFixedVector2D d = edges[i].p1 - edges[i].p0;
|
||||
d.Normalize(fixed::FromInt(1)/8);
|
||||
CFixedVector2D p2 = edges[i].p1 - d*2;
|
||||
CFixedVector2D p3 = p2 + d.Perpendicular();
|
||||
CFixedVector2D p4 = p2 - d.Perpendicular();
|
||||
PUSH_POINT(p3);
|
||||
PUSH_POINT(p4);
|
||||
PUSH_POINT(edges[i].p1);
|
||||
|
||||
SimRender::ConstructLineOnGround(GetSimContext(), xz, m_DebugOverlayShortPathLines.back(), true);
|
||||
}
|
||||
#undef PUSH_POINT
|
||||
|
||||
// Render the axis-aligned squares
|
||||
for (size_t i = 0; i < edgeSquares.size(); ++i)
|
||||
{
|
||||
m_DebugOverlayShortPathLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1);
|
||||
std::vector<float> xz;
|
||||
Square s = edgeSquares[i];
|
||||
xz.push_back(s.p0.X.ToFloat());
|
||||
xz.push_back(s.p0.Y.ToFloat());
|
||||
xz.push_back(s.p0.X.ToFloat());
|
||||
xz.push_back(s.p1.Y.ToFloat());
|
||||
xz.push_back(s.p1.X.ToFloat());
|
||||
xz.push_back(s.p1.Y.ToFloat());
|
||||
xz.push_back(s.p1.X.ToFloat());
|
||||
xz.push_back(s.p0.Y.ToFloat());
|
||||
xz.push_back(s.p0.X.ToFloat());
|
||||
xz.push_back(s.p0.Y.ToFloat());
|
||||
SimRender::ConstructLineOnGround(GetSimContext(), xz, m_DebugOverlayShortPathLines.back(), true);
|
||||
}
|
||||
}
|
||||
DebugRenderGraph(cmpObstructionManager->GetSimContext(), m_Vertexes, m_Edges, m_EdgeSquares);
|
||||
|
||||
// Do an A* search over the vertex/visibility graph:
|
||||
|
||||
@ -772,8 +665,6 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
// we can't reach. Since the algorithm can only reach a vertex once (and then it'll be marked
|
||||
// as closed), we won't be doing any redundant visibility computations.
|
||||
|
||||
PROFILE_START("Short pathfinding - A*");
|
||||
|
||||
VertexPriorityQueue open;
|
||||
VertexPriorityQueue::Item qiStart = { START_VERTEX_ID, start.h, start.h };
|
||||
open.push(qiStart);
|
||||
@ -785,7 +676,7 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
{
|
||||
// Move best tile from open to closed
|
||||
VertexPriorityQueue::Item curr = open.pop();
|
||||
vertexes[curr.id].status = Vertex::CLOSED;
|
||||
m_Vertexes[curr.id].status = Vertex::CLOSED;
|
||||
|
||||
// If we've reached the destination, stop
|
||||
if (curr.id == GOAL_VERTEX_ID)
|
||||
@ -798,27 +689,27 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
// The heuristic based on distance is very rough, especially for squares that are further away;
|
||||
// we're also only really interested in the closest squares since they are the only ones that block a lot of rays.
|
||||
// Thus we only do a partial sort; the threshold is just a somewhat reasonable value.
|
||||
if (edgeSquares.size() > 8)
|
||||
std::partial_sort(edgeSquares.begin(), edgeSquares.begin() + 8, edgeSquares.end(), SquareSort(vertexes[curr.id].p));
|
||||
if (m_EdgeSquares.size() > 8)
|
||||
std::partial_sort(m_EdgeSquares.begin(), m_EdgeSquares.begin() + 8, m_EdgeSquares.end(), SquareSort(m_Vertexes[curr.id].p));
|
||||
|
||||
edgesUnaligned.clear();
|
||||
edgesLeft.clear();
|
||||
edgesRight.clear();
|
||||
edgesBottom.clear();
|
||||
edgesTop.clear();
|
||||
SplitAAEdges(vertexes[curr.id].p, edges, edgeSquares, edgesUnaligned, edgesLeft, edgesRight, edgesBottom, edgesTop);
|
||||
m_EdgesUnaligned.clear();
|
||||
m_EdgesLeft.clear();
|
||||
m_EdgesRight.clear();
|
||||
m_EdgesBottom.clear();
|
||||
m_EdgesTop.clear();
|
||||
SplitAAEdges(m_Vertexes[curr.id].p, m_Edges, m_EdgeSquares, m_EdgesUnaligned, m_EdgesLeft, m_EdgesRight, m_EdgesBottom, m_EdgesTop);
|
||||
|
||||
// Check the lines to every other vertex
|
||||
for (size_t n = 0; n < vertexes.size(); ++n)
|
||||
for (size_t n = 0; n < m_Vertexes.size(); ++n)
|
||||
{
|
||||
if (vertexes[n].status == Vertex::CLOSED)
|
||||
if (m_Vertexes[n].status == Vertex::CLOSED)
|
||||
continue;
|
||||
|
||||
// If this is the magical goal vertex, move it to near the current vertex
|
||||
CFixedVector2D npos;
|
||||
if (n == GOAL_VERTEX_ID)
|
||||
{
|
||||
npos = goal.NearestPointOnGoal(vertexes[curr.id].p);
|
||||
npos = request.goal.NearestPointOnGoal(m_Vertexes[curr.id].p);
|
||||
|
||||
// To prevent integer overflows later on, we need to ensure all vertexes are
|
||||
// 'close' to the source. The goal might be far away (not a good idea but
|
||||
@ -827,17 +718,17 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
npos.Y = clamp(npos.Y, rangeZMin, rangeZMax);
|
||||
}
|
||||
else
|
||||
npos = vertexes[n].p;
|
||||
npos = m_Vertexes[n].p;
|
||||
|
||||
// Work out which quadrant(s) we're approaching the new vertex from
|
||||
u8 quad = 0;
|
||||
if (vertexes[curr.id].p.X <= npos.X && vertexes[curr.id].p.Y <= npos.Y) quad |= QUADRANT_BL;
|
||||
if (vertexes[curr.id].p.X >= npos.X && vertexes[curr.id].p.Y >= npos.Y) quad |= QUADRANT_TR;
|
||||
if (vertexes[curr.id].p.X <= npos.X && vertexes[curr.id].p.Y >= npos.Y) quad |= QUADRANT_TL;
|
||||
if (vertexes[curr.id].p.X >= npos.X && vertexes[curr.id].p.Y <= npos.Y) quad |= QUADRANT_BR;
|
||||
if (m_Vertexes[curr.id].p.X <= npos.X && m_Vertexes[curr.id].p.Y <= npos.Y) quad |= QUADRANT_BL;
|
||||
if (m_Vertexes[curr.id].p.X >= npos.X && m_Vertexes[curr.id].p.Y >= npos.Y) quad |= QUADRANT_TR;
|
||||
if (m_Vertexes[curr.id].p.X <= npos.X && m_Vertexes[curr.id].p.Y >= npos.Y) quad |= QUADRANT_TL;
|
||||
if (m_Vertexes[curr.id].p.X >= npos.X && m_Vertexes[curr.id].p.Y <= npos.Y) quad |= QUADRANT_BR;
|
||||
|
||||
// Check that the new vertex is in the right quadrant for the old vertex
|
||||
if (!(vertexes[curr.id].quadOutward & quad))
|
||||
if (!(m_Vertexes[curr.id].quadOutward & quad))
|
||||
{
|
||||
// Hack: Always head towards the goal if possible, to avoid missing it if it's
|
||||
// inside another unit
|
||||
@ -846,86 +737,227 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
|
||||
}
|
||||
|
||||
bool visible =
|
||||
CheckVisibilityLeft(vertexes[curr.id].p, npos, edgesLeft) &&
|
||||
CheckVisibilityRight(vertexes[curr.id].p, npos, edgesRight) &&
|
||||
CheckVisibilityBottom(vertexes[curr.id].p, npos, edgesBottom) &&
|
||||
CheckVisibilityTop(vertexes[curr.id].p, npos, edgesTop) &&
|
||||
CheckVisibility(vertexes[curr.id].p, npos, edgesUnaligned);
|
||||
CheckVisibilityLeft(m_Vertexes[curr.id].p, npos, m_EdgesLeft) &&
|
||||
CheckVisibilityRight(m_Vertexes[curr.id].p, npos, m_EdgesRight) &&
|
||||
CheckVisibilityBottom(m_Vertexes[curr.id].p, npos, m_EdgesBottom) &&
|
||||
CheckVisibilityTop(m_Vertexes[curr.id].p, npos, m_EdgesTop) &&
|
||||
CheckVisibility(m_Vertexes[curr.id].p, npos, m_EdgesUnaligned);
|
||||
|
||||
/*
|
||||
// Render the edges that we examine
|
||||
m_DebugOverlayShortPathLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayShortPathLines.back().m_Color = visible ? CColor(0, 1, 0, 0.5) : CColor(1, 0, 0, 0.5);
|
||||
std::vector<float> xz;
|
||||
xz.push_back(vertexes[curr.id].p.X.ToFloat());
|
||||
xz.push_back(vertexes[curr.id].p.Y.ToFloat());
|
||||
xz.push_back(npos.X.ToFloat());
|
||||
xz.push_back(npos.Y.ToFloat());
|
||||
SimRender::ConstructLineOnGround(GetSimContext(), xz, m_DebugOverlayShortPathLines.back(), false);
|
||||
*/
|
||||
// Render the edges that we examine.
|
||||
DebugRenderEdges(cmpObstructionManager->GetSimContext(), visible, m_Vertexes[curr.id].p, npos);
|
||||
|
||||
if (visible)
|
||||
{
|
||||
fixed g = vertexes[curr.id].g + (vertexes[curr.id].p - npos).Length();
|
||||
fixed g = m_Vertexes[curr.id].g + (m_Vertexes[curr.id].p - npos).Length();
|
||||
|
||||
// If this is a new tile, compute the heuristic distance
|
||||
if (vertexes[n].status == Vertex::UNEXPLORED)
|
||||
if (m_Vertexes[n].status == Vertex::UNEXPLORED)
|
||||
{
|
||||
// Add it to the open list:
|
||||
vertexes[n].status = Vertex::OPEN;
|
||||
vertexes[n].g = g;
|
||||
vertexes[n].h = goal.DistanceToPoint(npos);
|
||||
vertexes[n].pred = curr.id;
|
||||
m_Vertexes[n].status = Vertex::OPEN;
|
||||
m_Vertexes[n].g = g;
|
||||
m_Vertexes[n].h = request.goal.DistanceToPoint(npos);
|
||||
m_Vertexes[n].pred = curr.id;
|
||||
|
||||
// If this is an axis-aligned shape, the path must continue in the same quadrant
|
||||
// direction (but not go into the inside of the shape).
|
||||
// Hack: If we started *inside* a shape then perhaps headed to its corner (e.g. the unit
|
||||
// was very near another unit), don't restrict further pathing.
|
||||
if (vertexes[n].quadInward && !(curr.id == START_VERTEX_ID && g < fixed::FromInt(8)))
|
||||
vertexes[n].quadOutward = ((~vertexes[n].quadInward) & quad) & 0xF;
|
||||
if (m_Vertexes[n].quadInward && !(curr.id == START_VERTEX_ID && g < fixed::FromInt(8)))
|
||||
m_Vertexes[n].quadOutward = ((m_Vertexes[n].quadInward) & quad) & 0xF;
|
||||
|
||||
if (n == GOAL_VERTEX_ID)
|
||||
vertexes[n].p = npos; // remember the new best goal position
|
||||
m_Vertexes[n].p = npos; // remember the new best goal position
|
||||
|
||||
VertexPriorityQueue::Item t = { (u16)n, g + vertexes[n].h, vertexes[n].h };
|
||||
VertexPriorityQueue::Item t = { (u16)n, g + m_Vertexes[n].h, m_Vertexes[n].h };
|
||||
open.push(t);
|
||||
|
||||
// Remember the heuristically best vertex we've seen so far, in case we never actually reach the target
|
||||
if (vertexes[n].h < hBest)
|
||||
if (m_Vertexes[n].h < hBest)
|
||||
{
|
||||
idBest = (u16)n;
|
||||
hBest = vertexes[n].h;
|
||||
hBest = m_Vertexes[n].h;
|
||||
}
|
||||
}
|
||||
else // must be OPEN
|
||||
{
|
||||
// If we've already seen this tile, and the new path to this tile does not have a
|
||||
// better cost, then stop now
|
||||
if (g >= vertexes[n].g)
|
||||
if (g >= m_Vertexes[n].g)
|
||||
continue;
|
||||
|
||||
// Otherwise, we have a better path, so replace the old one with the new cost/parent
|
||||
fixed gprev = vertexes[n].g;
|
||||
vertexes[n].g = g;
|
||||
vertexes[n].pred = curr.id;
|
||||
fixed gprev = m_Vertexes[n].g;
|
||||
m_Vertexes[n].g = g;
|
||||
m_Vertexes[n].pred = curr.id;
|
||||
|
||||
// If this is an axis-aligned shape, the path must continue in the same quadrant
|
||||
// direction (but not go into the inside of the shape).
|
||||
if (vertexes[n].quadInward)
|
||||
vertexes[n].quadOutward = ((~vertexes[n].quadInward) & quad) & 0xF;
|
||||
if (m_Vertexes[n].quadInward)
|
||||
m_Vertexes[n].quadOutward = ((m_Vertexes[n].quadInward) & quad) & 0xF;
|
||||
|
||||
if (n == GOAL_VERTEX_ID)
|
||||
vertexes[n].p = npos; // remember the new best goal position
|
||||
m_Vertexes[n].p = npos; // remember the new best goal position
|
||||
|
||||
open.promote((u16)n, gprev + vertexes[n].h, g + vertexes[n].h, vertexes[n].h);
|
||||
open.promote((u16)n, gprev + m_Vertexes[n].h, g + m_Vertexes[n].h, m_Vertexes[n].h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reconstruct the path (in reverse)
|
||||
for (u16 id = idBest; id != START_VERTEX_ID; id = vertexes[id].pred)
|
||||
path.m_Waypoints.emplace_back(Waypoint{ vertexes[id].p.X, vertexes[id].p.Y });
|
||||
WaypointPath path;
|
||||
for (u16 id = idBest; id != START_VERTEX_ID; id = m_Vertexes[id].pred)
|
||||
path.m_Waypoints.emplace_back(Waypoint{ m_Vertexes[id].p.X, m_Vertexes[id].p.Y });
|
||||
|
||||
PROFILE_END("Short pathfinding - A*");
|
||||
|
||||
m_Edges.clear();
|
||||
m_EdgeSquares.clear();
|
||||
m_Vertexes.clear();
|
||||
|
||||
m_EdgesUnaligned.clear();
|
||||
m_EdgesLeft.clear();
|
||||
m_EdgesRight.clear();
|
||||
m_EdgesBottom.clear();
|
||||
m_EdgesTop.clear();
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
void VertexPathfinder::DebugRenderGoal(const CSimContext& simContext, const PathGoal& goal) const
|
||||
{
|
||||
if (!m_DebugOverlay)
|
||||
return;
|
||||
|
||||
m_DebugOverlayShortPathLines.clear();
|
||||
|
||||
// Render the goal shape
|
||||
m_DebugOverlayShortPathLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 0, 0, 1);
|
||||
switch (goal.type)
|
||||
{
|
||||
case PathGoal::POINT:
|
||||
{
|
||||
SimRender::ConstructCircleOnGround(simContext, goal.x.ToFloat(), goal.z.ToFloat(), 0.2f, m_DebugOverlayShortPathLines.back(), true);
|
||||
break;
|
||||
}
|
||||
case PathGoal::CIRCLE:
|
||||
case PathGoal::INVERTED_CIRCLE:
|
||||
{
|
||||
SimRender::ConstructCircleOnGround(simContext, goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat(), m_DebugOverlayShortPathLines.back(), true);
|
||||
break;
|
||||
}
|
||||
case PathGoal::SQUARE:
|
||||
case PathGoal::INVERTED_SQUARE:
|
||||
{
|
||||
float a = atan2f(goal.v.X.ToFloat(), goal.v.Y.ToFloat());
|
||||
SimRender::ConstructSquareOnGround(simContext, goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat()*2, goal.hh.ToFloat()*2, a, m_DebugOverlayShortPathLines.back(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VertexPathfinder::DebugRenderGraph(const CSimContext& simContext, const std::vector<Vertex>& vertexes, const std::vector<Edge>& edges, const std::vector<Square>& edgeSquares) const
|
||||
{
|
||||
if (!m_DebugOverlay)
|
||||
return;
|
||||
|
||||
#define PUSH_POINT(p) STMT(xz.push_back(p.X.ToFloat()); xz.push_back(p.Y.ToFloat()))
|
||||
// Render the vertexes as little Pac-Man shapes to indicate quadrant direction
|
||||
for (size_t i = 0; i < vertexes.size(); ++i)
|
||||
{
|
||||
m_DebugOverlayShortPathLines.emplace_back();
|
||||
m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 1, 0, 1);
|
||||
|
||||
float x = vertexes[i].p.X.ToFloat();
|
||||
float z = vertexes[i].p.Y.ToFloat();
|
||||
|
||||
float a0 = 0, a1 = 0;
|
||||
// Get arc start/end angles depending on quadrant (if any)
|
||||
if (vertexes[i].quadInward == QUADRANT_BL) { a0 = -0.25f; a1 = 0.50f; }
|
||||
else if (vertexes[i].quadInward == QUADRANT_TR) { a0 = 0.25f; a1 = 1.00f; }
|
||||
else if (vertexes[i].quadInward == QUADRANT_TL) { a0 = -0.50f; a1 = 0.25f; }
|
||||
else if (vertexes[i].quadInward == QUADRANT_BR) { a0 = 0.00f; a1 = 0.75f; }
|
||||
|
||||
if (a0 == a1)
|
||||
SimRender::ConstructCircleOnGround(simContext, x, z, 0.5f,
|
||||
m_DebugOverlayShortPathLines.back(), true);
|
||||
else
|
||||
SimRender::ConstructClosedArcOnGround(simContext, x, z, 0.5f,
|
||||
a0 * ((float)M_PI*2.0f), a1 * ((float)M_PI*2.0f),
|
||||
m_DebugOverlayShortPathLines.back(), true);
|
||||
}
|
||||
|
||||
// Render the edges
|
||||
for (size_t i = 0; i < edges.size(); ++i)
|
||||
{
|
||||
m_DebugOverlayShortPathLines.emplace_back();
|
||||
m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1);
|
||||
std::vector<float> xz;
|
||||
PUSH_POINT(edges[i].p0);
|
||||
PUSH_POINT(edges[i].p1);
|
||||
|
||||
// Add an arrowhead to indicate the direction
|
||||
CFixedVector2D d = edges[i].p1 - edges[i].p0;
|
||||
d.Normalize(fixed::FromInt(1)/8);
|
||||
CFixedVector2D p2 = edges[i].p1 - d*2;
|
||||
CFixedVector2D p3 = p2 + d.Perpendicular();
|
||||
CFixedVector2D p4 = p2 - d.Perpendicular();
|
||||
PUSH_POINT(p3);
|
||||
PUSH_POINT(p4);
|
||||
PUSH_POINT(edges[i].p1);
|
||||
|
||||
SimRender::ConstructLineOnGround(simContext, xz, m_DebugOverlayShortPathLines.back(), true);
|
||||
}
|
||||
#undef PUSH_POINT
|
||||
|
||||
// Render the axis-aligned squares
|
||||
for (size_t i = 0; i < edgeSquares.size(); ++i)
|
||||
{
|
||||
m_DebugOverlayShortPathLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1);
|
||||
std::vector<float> xz;
|
||||
Square s = edgeSquares[i];
|
||||
xz.push_back(s.p0.X.ToFloat());
|
||||
xz.push_back(s.p0.Y.ToFloat());
|
||||
xz.push_back(s.p0.X.ToFloat());
|
||||
xz.push_back(s.p1.Y.ToFloat());
|
||||
xz.push_back(s.p1.X.ToFloat());
|
||||
xz.push_back(s.p1.Y.ToFloat());
|
||||
xz.push_back(s.p1.X.ToFloat());
|
||||
xz.push_back(s.p0.Y.ToFloat());
|
||||
xz.push_back(s.p0.X.ToFloat());
|
||||
xz.push_back(s.p0.Y.ToFloat());
|
||||
SimRender::ConstructLineOnGround(simContext, xz, m_DebugOverlayShortPathLines.back(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void VertexPathfinder::DebugRenderEdges(const CSimContext& UNUSED(simContext), bool UNUSED(visible), CFixedVector2D UNUSED(curr), CFixedVector2D UNUSED(npos)) const
|
||||
{
|
||||
if (!m_DebugOverlay)
|
||||
return;
|
||||
|
||||
// Disabled by default.
|
||||
/*
|
||||
m_DebugOverlayShortPathLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayShortPathLines.back().m_Color = visible ? CColor(0, 1, 0, 0.5) : CColor(1, 0, 0, 0.5);
|
||||
m_DebugOverlayShortPathLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayShortPathLines.back().m_Color = visible ? CColor(0, 1, 0, 0.5) : CColor(1, 0, 0, 0.5);
|
||||
std::vector<float> xz;
|
||||
xz.push_back(curr.X.ToFloat());
|
||||
xz.push_back(curr.Y.ToFloat());
|
||||
xz.push_back(npos.X.ToFloat());
|
||||
xz.push_back(npos.Y.ToFloat());
|
||||
SimRender::ConstructLineOnGround(simContext, xz, m_DebugOverlayShortPathLines.back(), false);
|
||||
SimRender::ConstructLineOnGround(simContext, xz, m_DebugOverlayShortPathLines.back(), false);
|
||||
*/
|
||||
}
|
||||
|
||||
void VertexPathfinder::RenderSubmit(SceneCollector& collector)
|
||||
{
|
||||
if (!m_DebugOverlay)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i)
|
||||
collector.Submit(&m_DebugOverlayShortPathLines[i]);
|
||||
}
|
120
source/simulation2/helpers/VertexPathfinder.h
Executable file
120
source/simulation2/helpers/VertexPathfinder.h
Executable file
@ -0,0 +1,120 @@
|
||||
/* Copyright (C) 2019 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_VERTEXPATHFINDER
|
||||
#define INCLUDED_VERTEXPATHFINDER
|
||||
|
||||
#include "graphics/Overlay.h"
|
||||
#include "simulation2/helpers/Pathfinding.h"
|
||||
#include "simulation2/system/CmpPtr.h"
|
||||
|
||||
// A vertex around the corners of an obstruction
|
||||
// (paths will be sequences of these vertexes)
|
||||
struct Vertex
|
||||
{
|
||||
enum
|
||||
{
|
||||
UNEXPLORED,
|
||||
OPEN,
|
||||
CLOSED,
|
||||
};
|
||||
|
||||
CFixedVector2D p;
|
||||
fixed g, h;
|
||||
u16 pred;
|
||||
u8 status;
|
||||
u8 quadInward : 4; // the quadrant which is inside the shape (or NONE)
|
||||
u8 quadOutward : 4; // the quadrants of the next point on the path which this vertex must be in, given 'pred'
|
||||
};
|
||||
|
||||
// Obstruction edges (paths will not cross any of these).
|
||||
// Defines the two points of the edge.
|
||||
struct Edge
|
||||
{
|
||||
CFixedVector2D p0, p1;
|
||||
};
|
||||
|
||||
// Axis-aligned obstruction squares (paths will not cross any of these).
|
||||
// Defines the opposing corners of an axis-aligned square
|
||||
// (from which four individual edges can be trivially computed), requiring p0 <= p1
|
||||
struct Square
|
||||
{
|
||||
CFixedVector2D p0, p1;
|
||||
};
|
||||
|
||||
// Axis-aligned obstruction edges.
|
||||
// p0 defines one end; c1 is either the X or Y coordinate of the other end,
|
||||
// depending on the context in which this is used.
|
||||
struct EdgeAA
|
||||
{
|
||||
CFixedVector2D p0;
|
||||
fixed c1;
|
||||
};
|
||||
|
||||
class ICmpObstructionManager;
|
||||
class CSimContext;
|
||||
class SceneCollector;
|
||||
|
||||
class VertexPathfinder
|
||||
{
|
||||
public:
|
||||
VertexPathfinder(const u16& mapSize, Grid<NavcellData>* const & terrainOnlyGrid) : m_MapSize(mapSize), m_TerrainOnlyGrid(terrainOnlyGrid), m_DebugOverlay(false) {};
|
||||
|
||||
/**
|
||||
* Compute a precise path from the given point to the goal, and return the set of waypoints.
|
||||
* The path is based on the full set of obstructions that pass the filter, such that
|
||||
* a unit of clearance 'clearance' will be able to follow the path with no collisions.
|
||||
* The path is restricted to a box of radius 'range' from the starting point.
|
||||
* Defined in CCmpPathfinder_Vertex.cpp
|
||||
*/
|
||||
WaypointPath ComputeShortPath(const AsyncShortPathRequest& request, CmpPtr<ICmpObstructionManager> cmpObstructionManager) const;
|
||||
|
||||
void SetDebugOverlay(bool enabled) { m_DebugOverlay = enabled; }
|
||||
void RenderSubmit(SceneCollector& collector);
|
||||
|
||||
private:
|
||||
|
||||
void DebugRenderGoal(const CSimContext& simContext, const PathGoal& goal) const;
|
||||
void DebugRenderGraph(const CSimContext& simContext, const std::vector<Vertex>& vertexes, const std::vector<Edge>& edges, const std::vector<Square>& edgeSquares) const;
|
||||
void DebugRenderEdges(const CSimContext& simContext, bool visible, CFixedVector2D curr, CFixedVector2D npos) const;
|
||||
|
||||
// References to the Pathfinder for convenience.
|
||||
const u16& m_MapSize;
|
||||
Grid<NavcellData>* const & m_TerrainOnlyGrid;
|
||||
|
||||
std::atomic<bool> m_DebugOverlay;
|
||||
mutable std::vector<SOverlayLine> m_DebugOverlayShortPathLines;
|
||||
|
||||
// These vectors are expensive to recreate on every call, so we cache them here.
|
||||
// They are made mutable to allow using them in the otherwise const ComputeShortPath.
|
||||
|
||||
mutable std::vector<Edge> m_EdgesUnaligned;
|
||||
mutable std::vector<EdgeAA> m_EdgesLeft;
|
||||
mutable std::vector<EdgeAA> m_EdgesRight;
|
||||
mutable std::vector<EdgeAA> m_EdgesBottom;
|
||||
mutable std::vector<EdgeAA> m_EdgesTop;
|
||||
|
||||
// List of obstruction vertexes (plus start/end points); we'll try to find paths through
|
||||
// the graph defined by these vertexes.
|
||||
mutable std::vector<Vertex> m_Vertexes;
|
||||
// List of collision edges - paths must never cross these.
|
||||
// (Edges are one-sided so intersections are fine in one direction, but not the other direction.)
|
||||
mutable std::vector<Edge> m_Edges;
|
||||
mutable std::vector<Square> m_EdgeSquares; // Axis-aligned squares; equivalent to 4 edges.
|
||||
};
|
||||
|
||||
#endif // INCLUDED_VERTEXPATHFINDER
|
Loading…
Reference in New Issue
Block a user