Skip to content

Commit

Permalink
AStar
Browse files Browse the repository at this point in the history
  • Loading branch information
RicardoLuis0 authored and madame-rachelle committed Mar 5, 2024
1 parent 3348822 commit ad52e2c
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 59 deletions.
3 changes: 3 additions & 0 deletions src/common/objects/dobjgc.h
Expand Up @@ -215,6 +215,9 @@ class TObjPtr
mutable DObject *o;
};
public:
TObjPtr() = default;

TObjPtr(T t) : pp(t) {}

constexpr TObjPtr<T>& operator=(T q) noexcept
{
Expand Down
171 changes: 120 additions & 51 deletions src/g_level.cpp
Expand Up @@ -2473,100 +2473,169 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, GetEpisodeName)
//----------------------------------------------------------------------------

// Code by RicardoLuis0
TArray<AActor*> * GetNeighbors(AActor * self)
static TArray<TObjPtr<AActor*>>& GetPathNodeNeighbors(AActor * self)
{
PClass * cls = PClass::FindClass("PathNode");
if(!cls->IsAncestorOf(self->GetClass()))
static PClass * nodeCls = PClass::FindClass(NAME_PathNode);

#ifndef NDEBUG
if(!nodeCls->IsAncestorOf(self->GetClass()))
{
ThrowAbortException(X_BAD_SELF, "Invalid class passed to GetNeighbors (must be PathNodeInfo)");
ThrowAbortException(X_BAD_SELF, "Invalid class passed to GetNeighbors (must be PathNode)");
}
#endif

PField *var = dyn_cast<PField>(cls->FindSymbol("neighbors", true));
static PField *var = dyn_cast<PField>(nodeCls->FindSymbol("neighbors", true));

assert(var);
assert(var->Type->isDynArray());
assert(static_cast<PDynArray*>(var->Type)->ElementType == cls);
assert(static_cast<PDynArray*>(var->Type)->ElementType == nodeCls->VMType);

return reinterpret_cast<TArray<AActor*>*>(reinterpret_cast<uintptr_t>(self) + var->Offset);
return *reinterpret_cast<TArray<TObjPtr<AActor*>>*>(reinterpret_cast<uintptr_t>(self) + var->Offset);
}

int AS_BinarySearch(TMap<AActor*, double>* fScore, bool exact = false)
static void ReconstructPath(TMap<AActor*, AActor*> &cameFrom, AActor* current, TArray<TObjPtr<AActor*>> &path)
{
return 0;
path.Clear();
path.Push(current);
AActor ** tmp = cameFrom.CheckKey(current);

if(tmp) do
{
path.Insert(0, *tmp);
}
while(tmp = cameFrom.CheckKey(*tmp));
}

void AS_ReconstructPath(TMap<AActor*, AActor*>* cameFrom, AActor* chaser)
static AActor* FindClosestNode(AActor* from, double maxSearch)
{
static PClass * nodeCls = PClass::FindClass(NAME_PathNode);

FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);
FMultiBlockThingsIterator it(check, from->Level, from->Pos().X, from->Pos().Y, from->Pos().Z - ((from->Height + maxSearch) / 2), from->Height + maxSearch, from->radius + maxSearch, false, from->Sector);
FMultiBlockThingsIterator::CheckResult res;

AActor * closest = nullptr;
double closestDist = DBL_MAX;

while(it.Next(&res))
{
if(nodeCls->IsAncestorOf(res.thing->GetClass()))
{
double dst = res.thing->Distance3D(from);
if(dst < closestDist && P_CheckSight(res.thing, from))
{
closestDist = dst;
closest = res.thing;
}
}
}

return closest;
}

bool FLevelLocals::AStar(AActor* chaser, AActor* target, AActor* startnode, AActor* goalnode)
template<typename K, typename V>
static V GetOr(TMap<K, V> map, const K &key, V alt)
{
if (!chaser || !target || PathNodes.Size() < 1)
return false;
V *k = map.CheckKey(key);
return k ? *k : alt;
}

static bool FindPathAStar(AActor* start, AActor* goal, TArray<TObjPtr<AActor*>> &path)
{

TArray<AActor*> openSet;
TMap<AActor*, AActor*> cameFrom;
TMap<AActor*, double> gScore;
TMap<AActor*, double> fScore;

// If supplying nodes, skip the search.
const bool getstart = (!startnode || !startnode->IsKindOf(NAME_PathNode));
const bool getgoal = (!goalnode || !goalnode->IsKindOf(NAME_PathNode));
openSet.Push(start);
gScore.Insert(start, 0);
fScore.Insert(start, start->Distance3D(goal));

if (getstart || getgoal)
auto lt_fScore = [&fScore](AActor* lhs, AActor* rhs)
{
double dist[2];
dist[0] = dist[1] = 100000000.0;
return GetOr(fScore, lhs, DBL_MAX) < GetOr(fScore, rhs, DBL_MAX);
};

while(openSet.Size() > 0)
{
AActor * current = openSet[0];
openSet.Delete(0);
if(current == goal)
{
ReconstructPath(cameFrom, current, path);
return true;
}

double current_gScore = GetOr(gScore, current, DBL_MAX);

for (int i = 0; i < PathNodes.Size(); i++)
for(AActor * neighbor : GetPathNodeNeighbors(current))
{
AActor *node = PathNodes[i];
if (!node) continue;

double dis;
if (getstart)
{
dis = node->Distance2DSquared(chaser);
if (dis < dist[0] && P_CheckSight(node, chaser)) // TO DO: Make 3D aware, so 3D floors can work better.
{
startnode = node;
dist[0] = dis;
}
}
double tentative_gScore = current_gScore + current->Distance3D(neighbor);

double neighbor_gScore = GetOr(gScore, neighbor, DBL_MAX);

dis = node->Distance2DSquared(target);
if (dis < dist[1] && P_CheckSight(node, target))
if(tentative_gScore < neighbor_gScore)
{
goalnode = node;
dist[1] = dis;
openSet.SortedDelete(neighbor, lt_fScore);
cameFrom.Insert(neighbor, current);
gScore.Insert(neighbor, tentative_gScore);
fScore.Insert(neighbor, tentative_gScore + neighbor->Distance3D(goal));
openSet.SortedInsert(neighbor, lt_fScore);
}
}
}
return false;
}

// Incomplete graph.
if (!startnode || !goalnode)
return false;
bool FLevelLocals::FindPath(AActor* start, AActor* goal, AActor* startNode, AActor* goalNode, double maxSearch)
{
static PClass * nodeCls = PClass::FindClass(NAME_PathNode);

if (startnode == goalnode)
if (!start || !goal)
{
return false;
}

assert(startNode == nullptr || nodeCls->IsAncestorOf(startNode->GetClass()));
assert(goalNode == nullptr || nodeCls->IsAncestorOf(goalNode->GetClass()));

if(startNode == nullptr) startNode = FindClosestNode(start, maxSearch);
if(goalNode == nullptr) goalNode = FindClosestNode(goal, maxSearch);

// Incomplete graph.
if (!startNode || !goalNode)
{
return false;
}

if (startNode == goalNode)
{
start->ClearPath();
start->Path.Push(MakeObjPtr<AActor*>(startNode));
return true;
}

if(FindPathAStar(startNode, goalNode, start->Path))
{
if (start->goal && nodeCls->IsAncestorOf(start->goal->GetClass()))
{
chaser->ClearPath();
chaser->Path.Push(MakeObjPtr<AActor*>(startnode));
return true;
start->goal = nullptr;
}
return true;
}

// Begin A* here.
TArray<AActor*> openSet;
TMap<AActor*, AActor*> cameFrom;
TMap<AActor*, double> gScore;
TMap<AActor*, double> fScore;

return false;
}

DEFINE_ACTION_FUNCTION(FLevelLocals, AStar)
DEFINE_ACTION_FUNCTION(FLevelLocals, FindPath)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
PARAM_OBJECT(chaser, AActor);
PARAM_OBJECT(target, AActor);
PARAM_OBJECT(startnode, AActor);
PARAM_OBJECT(goalnode, AActor);
return self->AStar(chaser, target, startnode, goalnode);
PARAM_FLOAT(maxSearch);
return self->FindPath(chaser, target, startnode, goalnode, maxSearch);
}
3 changes: 1 addition & 2 deletions src/g_levellocals.h
Expand Up @@ -444,9 +444,8 @@ struct FLevelLocals

void SetMusic();

bool AStar(AActor *chaser, AActor *target, AActor *startnode = nullptr, AActor *goalnode = nullptr);
bool FindPath(AActor *chaser, AActor *target, AActor *startnode = nullptr, AActor *goalnode = nullptr, double maxSearch = 256.0);

TArray<AActor *> PathNodes;
TArray<vertex_t> vertexes;
TArray<sector_t> sectors;
TArray<extsector_t> extsectors; // container for non-trivial sector information. sector_t must be trivially copyable for *_fakeflat to work as intended.
Expand Down
2 changes: 0 additions & 2 deletions src/p_setup.cpp
Expand Up @@ -611,8 +611,6 @@ void P_SetupLevel(FLevelLocals *Level, int position, bool newGame)
while ((ac = it.Next()))
{
ac->SetDynamicLights();
if (ac->IsKindOf(NAME_PathNode))
Level->PathNodes.Push(ac);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/playsim/p_enemy.cpp
Expand Up @@ -2532,7 +2532,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi
}
if (!actor->goal)
{
if (actor->Path.Size() < 1 && actor->Level->AStar(actor, actor->target))
if (actor->Path.Size() < 1 && actor->Level->FindPath(actor, actor->target))
actor->goal = actor->Path[0];

}
Expand Down
2 changes: 1 addition & 1 deletion wadsrc/static/zscript/actors/actor.zs
Expand Up @@ -261,7 +261,7 @@ class Actor : Thinker native
private native int InventoryID; // internal counter.
native uint freezetics;
native Vector2 AutomapOffsets;
native Array<Actor> Path; // Cannot be cast to PathNode, unfortunately.
native Array<PathNode> Path;

meta String Obituary; // Player was killed by this actor
meta String HitObituary; // Player was killed by this actor in melee
Expand Down
2 changes: 1 addition & 1 deletion wadsrc/static/zscript/actors/shared/sharedmisc.zs
Expand Up @@ -255,7 +255,7 @@ class PathNode : Actor
// For non-connected paths. Stamina will be used to set this. Necessary for tele/portals.
private int group;

Array<Actor> neighbors;
Array<PathNode> neighbors;

Default
{
Expand Down
2 changes: 1 addition & 1 deletion wadsrc/static/zscript/doombase.zs
Expand Up @@ -554,7 +554,7 @@ struct LevelLocals native
native void SpawnParticle(FSpawnParticleParams p);
native VisualThinker SpawnVisualThinker(Class<VisualThinker> type);

native bool AStar(Actor chaser, Actor target, Actor startnode = null, Actor goalnode = null);
native bool FindPath(Actor chaser, Actor target, Actor startnode = null, Actor goalnode = null, double maxSearch = 256.0);
}

// a few values of this need to be readable by the play code.
Expand Down

0 comments on commit ad52e2c

Please sign in to comment.