Jumper - ( An Extremely Fast) 2D Pathfinder For grid-based games

Jumper is insane! Very helpful. Thanks for sharing your work :slight_smile: [import]uid: 105707 topic_id: 30317 reply_id: 126696[/import]

Hi all, fresh update.

Since the start, I had some odds with non-diagonal moves. Fact is, the original algorithm was designed to prefer diagonal search instead of horizontal/vertical search.
So I basically hijacked it, to be able to deal with diagonals and orthogonal moves. It worked, but I wasnā€™t that much satisfied with the results. Fact is, they were too much ā€œzigzagsā€ at each step when a path with no diagonal moves was requested.
I took a fresh look at that recently, and I made some changesā€¦
And the results were ashtonishing:

Until 1.5.0:


Now (1.5.1)


Thereā€™s still some things I could come back on, such as find a way to reduce the expanded nodes when diagonal moves are forbidden. Anyway, thatā€™s way better than before.

Github: Jumper

Hope you like it! [import]uid: 142361 topic_id: 30317 reply_id: 126926[/import]

Hi all, fresh update.

Since the start, I had some odds with non-diagonal moves. Fact is, the original algorithm was designed to prefer diagonal search instead of horizontal/vertical search.
So I basically hijacked it, to be able to deal with diagonals and orthogonal moves. It worked, but I wasnā€™t that much satisfied with the results. Fact is, they were too much ā€œzigzagsā€ at each step when a path with no diagonal moves was requested.
I took a fresh look at that recently, and I made some changesā€¦
And the results were ashtonishing:

Until 1.5.0:


Now (1.5.1)


Thereā€™s still some things I could come back on, such as find a way to reduce the expanded nodes when diagonal moves are forbidden. Anyway, thatā€™s way better than before.

Github: Jumper

Hope you like it! [import]uid: 142361 topic_id: 30317 reply_id: 126926[/import]

Hi everyone.

First of all, Jumper was updated to 1.5.1.2, bringing typos fixes and other tiny bugfixes.

Appart from that, Iā€™ve been working on a seperated version to support tiles that can be crossed on specific directions, like one-ways tiles (doors, ladders, etc)ā€¦
Just want to give heads up about the current progresses on this.
Considering these conditions:

  • Tiles can either be fully walkable/fully unwalkable.
  • Tiles can be partially walkable, meaning that they can be crossed in specific directions.
  • The overall should remain simple to use

At this point, I needed a way to represent all tiles and their ā€œpassabilityā€ (i.e how each node can be traversed). Thanks to some help with some the geniuses at bit.numerlua module, from which I ripped some functions i needed (bit.band, bit.bor).

I made lots of changes internally. Well the algorithm remains the same (A* + Jump Point Search), but on the top of that, some rules to discard expanding the search process on nodes that cannot be crossed in the actual heading direction of search.

The results are clearly awesome. See the relevant screenshots below.

On this first series of screenshots, you can see the pathfinder in action, with diagonal moves allowed. Note that on the second screenshot, one tile was found passable following the north-south direction, then the pathfinder chooses it.

Here is the same situation, with only straight moves allowed.

Side note, I havenā€™t released this modified version yet, as iā€™m actually trying to design a simple public interface that will let the user alter easily tiles passability rules, without having to mess with bitwise opsā€¦ Once I get this done, iā€™ll be pushing it on a separated branch on the Github repository.

Thanks reading. [import]uid: 142361 topic_id: 30317 reply_id: 127615[/import]

Hi everyone.

First of all, Jumper was updated to 1.5.1.2, bringing typos fixes and other tiny bugfixes.

Appart from that, Iā€™ve been working on a seperated version to support tiles that can be crossed on specific directions, like one-ways tiles (doors, ladders, etc)ā€¦
Just want to give heads up about the current progresses on this.
Considering these conditions:

  • Tiles can either be fully walkable/fully unwalkable.
  • Tiles can be partially walkable, meaning that they can be crossed in specific directions.
  • The overall should remain simple to use

At this point, I needed a way to represent all tiles and their ā€œpassabilityā€ (i.e how each node can be traversed). Thanks to some help with some the geniuses at bit.numerlua module, from which I ripped some functions i needed (bit.band, bit.bor).

I made lots of changes internally. Well the algorithm remains the same (A* + Jump Point Search), but on the top of that, some rules to discard expanding the search process on nodes that cannot be crossed in the actual heading direction of search.

The results are clearly awesome. See the relevant screenshots below.

On this first series of screenshots, you can see the pathfinder in action, with diagonal moves allowed. Note that on the second screenshot, one tile was found passable following the north-south direction, then the pathfinder chooses it.

Here is the same situation, with only straight moves allowed.

Side note, I havenā€™t released this modified version yet, as iā€™m actually trying to design a simple public interface that will let the user alter easily tiles passability rules, without having to mess with bitwise opsā€¦ Once I get this done, iā€™ll be pushing it on a separated branch on the Github repository.

Thanks reading. [import]uid: 142361 topic_id: 30317 reply_id: 127615[/import]

Hi folks,

Little update. Indeed, that was a bugfix.
The problem occuring was a sort of ā€œtunnelingā€ issue, so that when the goal node was neighbouring the start node,
the pathfinder would always return the straight line, even if this would have implied going through walls, as shown in the following picture:

With the latest bugfix, I had to add an extra function that acts as a validator routine between the online jump node search process and the regular a-star evaluation. When a jump point is found, and happened to be the goal node, this routine evaluate if the pathfinder can actually enter the goal node heading straight, through this function. If not, it will choose for another possible way. All this extra-process is called internally only for diagonal mode, and on the first jump point expanded. As a result, you might no longer encounter such an issue:

Feel free to try it (Github repo).
Note that i have only updated the default branch, not the experimental version (as this might require a bit more thinking).

Thanks reading. [import]uid: 142361 topic_id: 30317 reply_id: 128703[/import]

Hi folks,

Little update. Indeed, that was a bugfix.
The problem occuring was a sort of ā€œtunnelingā€ issue, so that when the goal node was neighbouring the start node,
the pathfinder would always return the straight line, even if this would have implied going through walls, as shown in the following picture:

With the latest bugfix, I had to add an extra function that acts as a validator routine between the online jump node search process and the regular a-star evaluation. When a jump point is found, and happened to be the goal node, this routine evaluate if the pathfinder can actually enter the goal node heading straight, through this function. If not, it will choose for another possible way. All this extra-process is called internally only for diagonal mode, and on the first jump point expanded. As a result, you might no longer encounter such an issue:

Feel free to try it (Github repo).
Note that i have only updated the default branch, not the experimental version (as this might require a bit more thinking).

Thanks reading. [import]uid: 142361 topic_id: 30317 reply_id: 128703[/import]

Hi all,

Some bugs which was causing the pathfinder to fail between successive paths call were recently fixed. Pick up the latest version on Github (now 1.5.2.2).

Appart from that, Iā€™ve been working on a benchmark program for Jumper, with maps taken from the 2012 Grid-Based Path Planning Competition (GPPC).
The whole program can be found here : Jumper-Benchmark.
[import]uid: 142361 topic_id: 30317 reply_id: 129720[/import]

Hi all,

Some bugs which was causing the pathfinder to fail between successive paths call were recently fixed. Pick up the latest version on Github (now 1.5.2.2).

Appart from that, Iā€™ve been working on a benchmark program for Jumper, with maps taken from the 2012 Grid-Based Path Planning Competition (GPPC).
The whole program can be found here : Jumper-Benchmark.
[import]uid: 142361 topic_id: 30317 reply_id: 129720[/import]

Hi all,

Jumper reaches v1.6.0, with some new tuning options.

Now, when initializing Jumper, passing it a 2D map (2-dimensional array), Jumper keeps track of the map and perform node passability checks according to this map values. So that you can easily update your map cells (lock/unlock cells) changing directly the map values) and Jumper will perform accordingly.

[code]
local map = {
{0,0,0},
{0,0,0},
{0,0,0},
}

local Jumper = require ā€˜Jumper.initā€™
local walkable = 0
local pather = Jumper(map,walkable)
ā€“ etc etc
map[2][1] = 1 ā€“ Cell[1,2] becomes unpassable[/code]

Second, I have managed to add specialized grids, and a tuning parameter, called grid processing. Therefore, you can either choose to init Jumper in pre-processing mode (by default) or post-processing mode.

In pre-processing mode (which is the default mode), Jumper caches all map cells in an internal grid and create some internal data needed for pathfinding operations. This will faster a little further all pathfinding requests, but will have a drawback in terms of memory consumed. As an example, a 500 x 650 sized map will consume around 55 Mb of memory right after initializing Jumper, in pre-preprocesed mode.

You can optionally choose to post-process the grid, setting the relevant argument to true when initializing Jumper.

local Jumper = require 'Jumper.init' local walkable = 0 local allowDiagonal = false local heuristicName = 'MANHATTAN' local autoFill = false local postProcess = true local pather = Jumper(map,walkable,allowDiagonal,heuristicName,autoFill,postProcess)

In this case, the internal grid will consume 0 kB (no memory) at initialization. But later on, this is likely to grow, as Jumper will create and keep caching new nodes and relevant data on demand. This might be a better approach if you are working with huge maps and running out of memory resources. But it also has a little inconvenience : pathfinding requests will take a bit longer being anwsered (about 10-30 extra-milliseconds on huge maps).

Extra - informations, documentation can be found at Github.
Any feedback would be appreciated.
Thanks for your interest in this.

[import]uid: 142361 topic_id: 30317 reply_id: 129882[/import]

Hi all,

Jumper reaches v1.6.0, with some new tuning options.

Now, when initializing Jumper, passing it a 2D map (2-dimensional array), Jumper keeps track of the map and perform node passability checks according to this map values. So that you can easily update your map cells (lock/unlock cells) changing directly the map values) and Jumper will perform accordingly.

[code]
local map = {
{0,0,0},
{0,0,0},
{0,0,0},
}

local Jumper = require ā€˜Jumper.initā€™
local walkable = 0
local pather = Jumper(map,walkable)
ā€“ etc etc
map[2][1] = 1 ā€“ Cell[1,2] becomes unpassable[/code]

Second, I have managed to add specialized grids, and a tuning parameter, called grid processing. Therefore, you can either choose to init Jumper in pre-processing mode (by default) or post-processing mode.

In pre-processing mode (which is the default mode), Jumper caches all map cells in an internal grid and create some internal data needed for pathfinding operations. This will faster a little further all pathfinding requests, but will have a drawback in terms of memory consumed. As an example, a 500 x 650 sized map will consume around 55 Mb of memory right after initializing Jumper, in pre-preprocesed mode.

You can optionally choose to post-process the grid, setting the relevant argument to true when initializing Jumper.

local Jumper = require 'Jumper.init' local walkable = 0 local allowDiagonal = false local heuristicName = 'MANHATTAN' local autoFill = false local postProcess = true local pather = Jumper(map,walkable,allowDiagonal,heuristicName,autoFill,postProcess)

In this case, the internal grid will consume 0 kB (no memory) at initialization. But later on, this is likely to grow, as Jumper will create and keep caching new nodes and relevant data on demand. This might be a better approach if you are working with huge maps and running out of memory resources. But it also has a little inconvenience : pathfinding requests will take a bit longer being anwsered (about 10-30 extra-milliseconds on huge maps).

Extra - informations, documentation can be found at Github.
Any feedback would be appreciated.
Thanks for your interest in this.

[import]uid: 142361 topic_id: 30317 reply_id: 129882[/import]

This is really amazing, I have been following this for a few weeks now hoping someone would make a more in-depth tutorial for this because Iā€™m still a beginner and have tried several times to get it to work but donā€™t understand it very well. How do you specify the size of each box, how do you even see the map, I canā€™t even get it to work with your example =(

Anyway love you for doing something like this, itā€™s the only diagonal pathfinding module I could find. Hope someone releases a tutorial in the near future! [import]uid: 194360 topic_id: 30317 reply_id: 130505[/import]

This is really amazing, I have been following this for a few weeks now hoping someone would make a more in-depth tutorial for this because Iā€™m still a beginner and have tried several times to get it to work but donā€™t understand it very well. How do you specify the size of each box, how do you even see the map, I canā€™t even get it to work with your example =(

Anyway love you for doing something like this, itā€™s the only diagonal pathfinding module I could find. Hope someone releases a tutorial in the near future! [import]uid: 194360 topic_id: 30317 reply_id: 130505[/import]

Hi Evonalien,

Would be please be a little bit more explicit on the problem ?
Is it all about using Jumper (setting the collision map, make pathfinding requests and so on) ?
You can take the time to review this thread,there might be some useful hints for you.
You can also open a ticket on the issue tracker, and submit the exact problem, on Github.

If you are looking for a full example with Corona, you can always look at this demo made by Mario Roberti. Find the source code here.

Iā€™ll be working on some examples for Corona by myself, when I have the time, hopefully. [import]uid: 142361 topic_id: 30317 reply_id: 130533[/import]

Hi Evonalien,

Would be please be a little bit more explicit on the problem ?
Is it all about using Jumper (setting the collision map, make pathfinding requests and so on) ?
You can take the time to review this thread,there might be some useful hints for you.
You can also open a ticket on the issue tracker, and submit the exact problem, on Github.

If you are looking for a full example with Corona, you can always look at this demo made by Mario Roberti. Find the source code here.

Iā€™ll be working on some examples for Corona by myself, when I have the time, hopefully. [import]uid: 142361 topic_id: 30317 reply_id: 130533[/import]

Very cool, thanks for sharing! [import]uid: 1560 topic_id: 30317 reply_id: 132109[/import]

Very cool, thanks for sharing! [import]uid: 1560 topic_id: 30317 reply_id: 132109[/import]

Hi community,

Jumper reached v1.6.2. See the detailed changelog.

First of all, Iā€™ve removed all links to third-party libs previously included. I chose to rewrite a lighter version of binary-heaps, featuring only the operations I needed (push, pop, heapify). Jumper no longer uses any extra dependancy, which makes less files to cope with.
I also made a full code review, bringing some slight internal changes.

I have also included a new type of distance heuristic, which is cardinal/intercadinal distance. I also included support for custom distance heuristics.

The initialization pattern have also been changed, to provide a way to init the pathfinder with a limited number of args. So, from now on, Jumper receives only three args upon initialization. See here for more details.

Last point, setDiagonalMoves and getDiagonalMoves methods were removed, as I didnā€™t find them explicit enough. Instead, you can now use setMode and getMode. setMode requires a string argument stating how the search should be processed: in diagonal mode (8-directions) or straight-mode only (4-directions).

Docs & Readme have been updated with the latest changes.
The benchmark program have also been updated to the latest version of Jumper.

Thanks reading. [import]uid: 142361 topic_id: 30317 reply_id: 133247[/import]

Hi community,

Jumper reached v1.6.2. See the detailed changelog.

First of all, Iā€™ve removed all links to third-party libs previously included. I chose to rewrite a lighter version of binary-heaps, featuring only the operations I needed (push, pop, heapify). Jumper no longer uses any extra dependancy, which makes less files to cope with.
I also made a full code review, bringing some slight internal changes.

I have also included a new type of distance heuristic, which is cardinal/intercadinal distance. I also included support for custom distance heuristics.

The initialization pattern have also been changed, to provide a way to init the pathfinder with a limited number of args. So, from now on, Jumper receives only three args upon initialization. See here for more details.

Last point, setDiagonalMoves and getDiagonalMoves methods were removed, as I didnā€™t find them explicit enough. Instead, you can now use setMode and getMode. setMode requires a string argument stating how the search should be processed: in diagonal mode (8-directions) or straight-mode only (4-directions).

Docs & Readme have been updated with the latest changes.
The benchmark program have also been updated to the latest version of Jumper.

Thanks reading. [import]uid: 142361 topic_id: 30317 reply_id: 133247[/import]

Hi all,

Jumper reaches version 1.6.3.
Basically, they were not much changes, but just some new features added, as some people have requested.
First, Jumper now supports string maps. The collision map passed upon initialization of the library no longer have to be a 2D table. Instead, you can pass a string, that Jumper will parse in rows (using line-break chars - ā€˜\nā€™ and ā€˜\rā€™ as delimiters).

Nodes walkability rules were also enhanced, for convenience. You might want to have multiples values for walkable tiles on a collision map, for instance. As of v1.6.3, you can pass a function that Jumper will call to evaluate whether or not a tile is walkable.

[code]
local stringMap = [[
xxxxx#xxxxx
xxxx#*#xxxx
xxx# . #xxx

. .

. . .

#*. .$. .*#

. . .

. .

xxx# . #xxx
xxxx#*#xxxx
xxxxx#xxxxx
]]

ā€“ We want chars ā€˜#ā€™, ā€˜.ā€™ and ā€˜*ā€™ to be walkable tiles.
local function isWalkable(value)
return value:match(ā€™[#.*]ā€™)
end
local Jumper = require (ā€œJumperā€)
local pathfinder = Jumper(stringMap, isWalkable)[/code]

A path iterator function have been added, too:

local path, len = pathfinder:getPath(startx, starty, goalx, goaly) if path then for x,y in path:iter() do print('Cell: '..x..' - '..y) end end

The Grid object API, adding some functions, mostly iterators too:

Grid:iter() Grid:each(f,...) Grid:eachRange(lx,ly,ex,ey,f,...) Grid:imap(f,...) Grid:imapRange(lx,ly,ex,ey,f,...)

Some little code improvements were made, too.
There also a complete HTML documentation, available on the git repository. It fully describes the API, and gives a brief description of the purpose of each of Jumperā€™s modules. This documentation was generated using the excellent LDoc.

See the changelog for more details. The benchmark for Jumper was also updated with the latest version of the library, feel free to try! [import]uid: 142361 topic_id: 30317 reply_id: 139587[/import]