Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added GDX port of Slick2D's AStarPathFinder #575

Closed
wants to merge 5 commits into from

Conversation

hneuer
Copy link
Member

@hneuer hneuer commented Sep 12, 2013

This is a GDX port of Slick2D's AStarPathFinder which I am currently using. It uses GDX container classes and works without the need of additional memory allocations for a path search.

It may be of use to others too so I moved it into a gdx package and created this PR.

@badlogic
Copy link
Member

Soo, i've been pondering if i want to include this in core. Unless @NathanSweet objects, i'd like to hold out. What's really nice is that this doesn't allocate anything. What's less nice is that it's bound to tilemaps. I have an A* implementation laying around somewhere that's more general, that is, it works on arbitrary graphs (a tilemap is just a specialized graph really). My implementation doesn't use a binary heap though. I'll see if i can dig it up, maybe we can merge the two things. I'll send it via e-mail.

@NathanSweet
Copy link
Member

A generic implementation would be nice. I'd also like to see it done in a small amount of code and optimized. FWIW, here is some code from an old project of mine:
http://pastebin.com/zjgVCzcJ
It was for hex maps, but isn't much different for regular grids. It is pretty efficient. It uses arrays instead of maps. The checkedID is a clever way of not having to clear the "checked" array. It uses a binary heap, but an older version of the one in libgdx.

@hneuer
Copy link
Member Author

hneuer commented Sep 14, 2013

The current implementation can be changed to work with an arbitrary graph instead of a tiled map. Just the places where x/y are used need to be replaced with graph-navigation through the nodes.

The implementation provides the mean of a Context which is passed around while searching. This makes it possible to find different paths for different cases (e.g. flying units). The downside of this is that we have to program against interfaces which is maybe not what @NathanSweet had in mind as optimized code.

I did a short brainstorming and came up with what needed to be changed imho (see list below). If this is something you guys want in libgdx I can follow up on this. Maybe it is not required in libgdx at all as there seem to be some libs around, but so far I could not find one with has no memory allocation, is gwt ready and doesn't use Maps.

If it is not required you can close this PR as I am happy with the implementation in my current project as it is.

  • TileBasedMap becomes NavGraph:
    public interface NavGraph {
        public boolean blocked (PathFindingContext context, NavNode targetNode);
        public float getCost (PathFindingContext context, NavNode targetNode);
    }
  • Node becomes abstract NavNode containing the date necessary for pathfinding. Subclasses need to take care of relevant user data (e.g. a NavNodeTiled containing int x,y or a NaveNode3D containing 3D polygons). Neighbors need to be filled in some sort of NavGraphBuilder.
    public abstract class NavNode extends BinaryHeap.Node {
        /** The parent of this node, how we reached it in the search */
        NavNode parent;
        int checkedID;
        float heuristic;
        int depth;
        float cost;
        Array<NavNode> neighbors;
    }
  • PathFindingContext becomes NavContext:
    public interface NavContext {
        public Object getMover ();
        public NavNode getSourceNode ();
        public float getSearchDistance ();
    }
  • PathFinder changes:
    public interface PathFinder {
        public boolean findPath (Object mover, NavNode startNode, NavNode targetNode, NavPath out);
    }
  • NavPath becomes an interface:
    public interface NavPath {
        public void fill (NavNode targetNode);
    }

@dsaltares
Copy link
Member

Hi guys, pretty interesting stuff.

I think this would be a good addition to libgdx, if not in the core, as an extension. The approach hneuer just proposed seems good to me, pretty generic and it'd work with 2D as well as 3D nav graphs.

May I make a suggestion? It'd be super cool if the system supported deferred path requests. You could give the pathfinder a time budget per frame and we could time slice the whole process so as to cope with many requests at the same time.

I guess we might not want to do that in the basic PathFinder, but why not have a derived class featuring that? I'm happy to do it myself.

@jrenner
Copy link
Member

jrenner commented Sep 14, 2013

As long as we are making requests, I'd like to see it extensible so
implementing something like jump point search on top of it wouldn't be
Impossible

@NathanSweet
Copy link
Member

Using interfaces doesn't stop the internals from being efficient. :) No GC, no maps, binary heap, checkedID, etc.

It would be nice to support n-directions (eg hex grids). I haven't looked at the proposal close enough to see if that would work.

FWIW I've found it useful to find all possible paths within a given distance/cost. Imagine a board game and you want to show possible moves.

@hneuer
Copy link
Member Author

hneuer commented Sep 14, 2013

Second iteration.

AStarPathFinder now works on arbitrary graphs, details about the path itself need to be implemented as needed in subclasses. What is required is a NavNode implementation (they are building the graph), a NavPath implementation (this is holding the result of the search) and heuristics. For a tile based map (integer x/y) I added the implementations (package com.badlogic.gdx.utils.pathfinding.tiled).

Algorithm specific data is attached to a NavNode (it is no longer part of a NavNode) by the field algoData, so different implementations of the PathFinder interface can use their own data (jump point search could be implemented this way in addition).

In the PathFinderTest class there can be found a simple graph implementation for a map holding its tiles bocling information as a boolean array and setting up a graph with up to 4 neighbours for each tile.

I think I have respected all your suggestions except two:

  • deferred requests: As far as I understood the requirement it should be doable with a subclass and some refactoring of the base class (adding exit/entry points?).
  • finding all possible paths: is AStar the correct algorithm for this? Wouldnt a simple Breadth-first search do the trick?

Comments are welcome!

@NathanSweet
Copy link
Member

All possible paths would be a way of (ab)using the astar to do a BFS, since the same pathing costs need to be done.

@jrenner
Copy link
Member

jrenner commented Sep 15, 2013

I think Dijkstra / bfs would be better, since when you are getting all paths you do not need heuristics, right? Should be faster than doing all paths plus heuristics

@NathanSweet
Copy link
Member

That is true. Mostly I want a BFS for free. :) I'm not actually using this in a game right now, so it's not that important.

Quick glance at the latest update, looks good. Merge has my vote if @badlogic likes.

@cortobass
Copy link
Contributor

I'm not sure that you can provide a generic AND efficient algorithm for Android. I tried almost all A* algo found on internet and many of them were too slow on an ordinary phone for a dozen of units on a non-tiled map, so I had to create my own implementation (I increased the speed by 100% but it is not as generic as you expect). I think it's better to create different algorithms, each one with a different goal.

@hneuer
Copy link
Member Author

hneuer commented Sep 22, 2013

@cortobass, I am not sure what you mean. Do you see any specific problems?

@cortobass
Copy link
Contributor

@hneuer It's not a comment about your code (I don't have enough time to try it, but at first glance it looks fine); I just warn that is difficult (impossible?) to implement an algorithm for Android that is both generic and quick enough for a real use. Any algorithm will have to be carefully tested under this OS because I had a lot of surprises while working on my project. Maybe, instead of trying to create a function that tries to satisfy every need, it is better to create different functions with different algorithms to cover most needs with the best performance. For a map without movement costs (or just two or three different costs), JPS is an excellent algorithm (probably the best). With movement costs, A* is very good. If you don't need heuristics at all (???), there are probably faster choices.

@hneuer
Copy link
Member Author

hneuer commented Sep 22, 2013

The PathFinder from this PR is only an interface (as everything else too) and the AStar algo is just a single implementation provided, so other implementations can be used without a problem if one just programs against the interface.

@gjroelofs
Copy link
Member

I would like to argue two points:

  • If this is included, then it should be headed under a different package, maybe gdx-algos or something the like. As I have a lot to contribute in terms of other search techniques and general A.I. related code. (Pathing, State-space search, planner algorithms)
  • Any non-problem specific implementation of an algorithm will only be used for prototyping, or early/mid development. The key issue is deciding on what algorithm to use for what problem case, and as such should be the focus of the suite.

I personally only start deviating from my pre-written core techniques when all else fails; and then all bets are off. (bitfields, static misusage, etc)
But up till that point, I rely on selecting the correct technique for the job; or delaying search (spread over multiple frames etc)

Also if speed is of the essence, the argument could be made that we would need to implement native code.

So I'd like to suggest selecting a datastructure (or abstraction thereof / set of) to be used for pathfinding, along with a common query interface and then implementing a suite of core search techniques on top of this.

This also allows for higher-level optimization:

  • In-Search; e.g. Pruning, correct underlying datastructures, or delayed execution (stick to a frame budget)
  • Post-Search; e.g. re-using already found paths.

@cortobass
If you have multiple units on a level; do not use A* (or variations thereof, JFS, etc) where the focus is on finding a single path as quickly as possible. If the goal is the same (e.g.; a Tower Defense, in which units need to converge on a single goal), use floodfill to generate a shortest path from each location to the goal. Otherwise, consider using Floyd–Warshall.

@cortobass
Copy link
Contributor

@methius I completely agree with you.
I tried to find some valuable info on the Floodfill algorithm but what I found so far doesn't fulfill my requirements. So i'm going to stay with my custom implementation of A*.

@hneuer
Copy link
Member Author

hneuer commented Sep 30, 2013

@methius having a GDX extension with common data structures and algorithms on top of that sounds like a great idea. After all I did this A* port because GDX was missing it and I did not have something pre-written as I am currently in my first game project :-)

As it is my first game I am afraid I can't bring in too much knowledge about such an extension and what is required to make it generally useful.

@hneuer hneuer closed this Oct 7, 2013
@NathanSweet
Copy link
Member

@badlogic did we want to merge this after it was revised?

@methius seems to be wanting for a more complex API? Without a PR for that, maybe this PR is just fine. Performance is likely fine for an out of the box solution, native code is not the answer for everything. Also it increases maintenance as it doesn't run on GWT.

@hneuer
Copy link
Member Author

hneuer commented Oct 7, 2013

Should I reopen it?

@NathanSweet
Copy link
Member

You could reopen it until @badlogic comments. From his first post it seemed like he might merge it if it were more general, which I believe you have made it.

@NathanSweet
Copy link
Member

FWIW, here is an executable example of my simple but optimized a* stuff:
https://gist.github.com/NathanSweet/7587981

@hneuer
Copy link
Member Author

hneuer commented Nov 22, 2013

OK, this PR is now open for discussion again. :-)

@hneuer hneuer reopened this Nov 22, 2013
@slebed
Copy link

slebed commented Dec 15, 2013

Hi, I just found this PR and I'm really interested in knowing if deferred pathfinding is still in the works. Also, is there any way to add Pooling so that multiple units can search the graph without running out of memory.

For my own game I've implemented a time slice proceedure to spread the pathfinding over several frames. So far I've been able to get 500+ units walking randomly around a 64x64 tiled map at 60fps on my Nexus 7. Unfortunately I screwed something up and now I'm getting out of memory errors.

I learning a lot from the code here. I'm hoping to improve my code with the addition of a binary heap, pooling, and a better way to handle deferred pathfinding.

@NathanSweet
Copy link
Member

For the desktop green threads would be great for deferred pathing:
http://www.java-gaming.org/topics/java-continuations-and-greenthreads/28337/view.html

@badlogic
Copy link
Member

I'll add a new extension for algos like this. We can then merge those things into that extensions.

manuel-freire referenced this pull request in manuel-freire/ead Apr 24, 2014
* PathFinder is the main entry point
* unit tests available in PathFinderTest
* graphical test (PathTester)
* graphical shape editing (ShapeEditor2)
@davebaol
Copy link
Member

I like the idea of creating a new extension intended to provide the most common techniques of the artificial intelligence literature, stuff like automata, behavior trees, steering behaviors, path finding, fuzzy logic, goal-driven behaviors, and so on.
We might call it AIDA (Artificial Intelligence Data structures and Algorithms) or something like that.
I'd be glad to contribute of course.

@davebaol
Copy link
Member

Ok I've implemented generic state machine and messaging.
Steering behaviors for 2D, 2.5D and 3D are a work in progress.
Should I do a pool request? Otherwise I can create a new repo on my github.

@davebaol
Copy link
Member

davebaol commented Jun 9, 2014

@hneuer
Please can you make a new PR with this code moved to the new extension gdx-ai?

@davebaol
Copy link
Member

@hneuer
Also, you're not in the CONTRIBUTORS list, so I think you still have to sign the CLA to allow us to use and distribute your code.

@badlogic
Copy link
Member

@hneuer has been with us since forever :) I should update the CONTRIBUTORS list some day.

@hneuer
Copy link
Member Author

hneuer commented Jun 15, 2014

@davebaol sure, I will pack up a new PR for this.

@davebaol
Copy link
Member

thank :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants