Simple Stupid Funnel Algorithm - Pathfinding
Simple Stupid Funnel Algorithm - Pathfinding
Digesting Duck
Blog about game AI and prototyping
Search
Simple Stupid Funnel Algorithm
Recast & Detour
Blog Archive
►
► 2012 (7)
►
► 2011 (30)
▼
▼ 2010 (70)
►
► December (3)
►
► November (4)
►
► October (4)
►
► September (2)
[EDIT] Fixed that example scenario below.
►
► August (18)
There's a nice master class over at AIGameDev.com about pathfinding: Hierarchical ►
► July (8)
Pathfinding Tips & Tricks with Alexander Kring. That slide about Left 4 Dead path
►
► June (2)
smoothing hit my pet peeve. It makes me sad that people still use "let's trace the
►
► May (4)
polygon edge midpoints" as the reference algorithm for path smoothing. Really sad.
►
► April (4)
Funnel algorithm is simple algorithm to find straight path along portals. Of the most ▼
▼ March (10)
detailed descriptions can be found in Efficient Triangulation-Based Pathfinding. My Favorite Protyping
Environment
Here's implementation of simple stupid funnel algorithm. Instead of using queue and
Detour Serialization API
all that fancy stuff, simple stupid funnel algorithm runs a loop and restarts the loop
Geometric vs. Sampling
from earlier location when new corner is added. This means that some portals are
calculated a bit more often than potentially necessary, but it makes the Custom hRVO
implementation a whole lot simpler. Local Navigation Grids
Simple Stupid Funnel Algorithm
Blind Sighted
Save Games and All That
Power of Less
Sketch for Hierarchical
Pathfinding Using Detour
►
► February (5)
►
► January (6)
►
► 2009 (56)
Interesting Blogs
Diary of a Graphics
Programmer
Coder Corner
Stumbling Toward
'Awesomeness'
Gaffer on Games
Atom
realtimecollisiondetection.net
- the blog
Game/AI
About Me
Mikko Mononen
Finland
View my complete profile
The above image shows six steps of the algorithm. Every time we process a new
portal edge (the dashed lines highlighted in yellow), we first:
Check if the left and right points are inside the current funnel (described
by the blue and red lines), if they are, we simple narrow the funnel (A-D).
If the new left endpoint is outside the funnel, the funnel is not update (E-
F)
If the new left end point is over right funnel edge (F), we add the right
funnel as a corner in the path and place the apex of the funnel at the right
funnel point location and restart the algorithm from there (G).
The same logic goes for left edge too. This is repeated until all the portal edges are
processed.
As seen in the above example, the algorithm recalculates some edge several times
because of the restart, but since the calculations are so simple, this simple stupid
trick makes the implementation much simpler and in practice is faster too.
return npts;
}
The array portals has all the portal segments of the path to simplify, first left edge
point and the right edge point. The first portal's left and right location are set to start
location, and the last portals left and right location is set to end location of the path.
Something like this:
// Start portal
vcpy(&portals[nportals*4+0], startPos);
vcpy(&portals[nportals*4+2], startPos);
nportals++;
// Portal between navmesh polygons
for (int i = 0; i < path->npolys-1; ++i)
{
getPortalPoints(mesh, path->poly[i], path->poly[i+1], &portals[nportals*4+0], &portals[nportals*4+2]);
nportals++;
}
// End portal
vcpy(&portals[nportals*4+0], endPos);
vcpy(&portals[nportals*4+2], endPos);
nportals++;
The cool thing is that this algorithm can be used with pretty much any navigation
format. Let it be a grid, quad-tree, connected squares, way-points or navigation
mesh. As long as you pass it a list of line segments that describe the portal from one
node to another.
This algorithm is also awesome when combined with steering. You can calculate the
desired movement velocity by calculating the next few turns and the steer towards
the next corner. It is so fast that you can calculate this every iteration just before you
use your favorite steering code.
So there you have it. The next person who goes public and suggest to use edge
midpoints and raycasting to find more straight path after A* will get his inbox flooded
with Cute Overload weekly selection to instigate his pet hate too.
68 comments:
Anonymous March 8, 2010 at 6:47 AM
I can think of one case where funneling does not quite work, which ultimately made
me write the very technique of raycasting midpoints for use within Recast/Detour on
my modified version, and it's a nice coincidence you're writing about this as I hit this
very problem late last week.
There can be a nasty horizon effect happening if the polygons found by A* do not
themselves form a straight/smooth path to the goal. This seems to get even worse
when you arbitrarily tesselate the navmesh to create areas (I wrote my own
implementation a few months ago not knowing about your solution, I have not
looked yet by how much they differ), where the polygons you select might have as
boundary some of these other extraneous polygons that are part of the straight path,
but have not been retained by A* because their midpoints were too far (most often
because of a big polygon). So you end up with a path that has these strange sharp
turns in the middle of a relatively open field. Raycasting has at least the advantage
of finding a straighter path regardless of the A* result, not quite perfect because
sometimes the next midpoint temporarily deviates behind an obstacle while the next
one is visible, but this approach still solved most of the problems I had before.
I did think of reusing the polygons found by raycasting to the funnel algorithm, but
another side effect of smoothing midpoints that I found interesting is that it often
makes the path prefer to stay a little further away from obstacles (provided the
triangles are not too large, but this case could be handled by sampling), rather than
tightly hugging them for a sharp turn, which looks a bit more natural and leave some
breathing room. Of course this is a problem possibly handled better in the path
follow logic, but given the time constraints, for now it works well enough. Maybe
however using the modified funnel algorithm from this paper could be interesting,
though the radius would not be a strict exclusion, more like a preferred stay-away-
from-obstacles distance, but I have not looked yet if this desired behavior is
compatible with this algorithm or your simplified implementation.
Of course I might have overlooked something, but this was the best solution I could
think of within a quite short time frame. I'm curious to see if you've encountered this
problem before and what are your thoughts about it.
Reply
That is not problem with funnel algorithm, but A*. Ideally you should not fix the path
smoothing (because it works), but actually add extra step after A* which tries to
adjust the list of path polygons so that it will create shorter path.
You should spend absolutely minimum amount of time trying to create smooth path,
because it will change when someone else moves in the game world.
Stuff like boundary hugging are better handled using steering algorithms or such,
you just get better results that way.
Just to reiterate:
1) A* transforms graph into a linear set of polygons which will leave you to target.
2) String pulling will tell you where to towards in order to move towards target
3) Locomotion/steering will be in charge of the final smooth path.
That way you will do as little work as necessary. Every smarter-than-potatoe string
pulling out there which tries to solve the whole path following dilemma at that stage,
just does too much work, and usually creates not very good results, which can
described as "more natural and human like" ;)
Reply
Great post! Could you talk a bit more about how you account for the agent radius
size? The way I handled this was to iterate over the final list of smoothed points, and
move them further away from the edges of the nav mesh. The question is, in what
direction do you move the points, to get them away from the edge of the nav mesh?
Here is a bit of psuedocode to describe the direction I use for the offset:
a = points[i+1] - points[i]
b = points[i+2] - points[i]
c = Normalize(a*05 + b*0.5)
if (ClockwiseTo(b,a))
{
offsetDir = Cross(c,up)
}
else
{
offsetDir = Cross(up,c)
}
points[i+1] += offsetDir*agentRadius
However, I can see some edge cases where this would not give the best position to
move toward. How did you handle this problem through steering?
Reply
This implementation assumes that the portals are already adjusted so that they
contain the agent radius. For example, a navmesh would be shrinked by agent
radius.
This assumption reduces the complexity of all the thing you need to do with
navmeshes or any navigation representation a huge amount.
The only corner case for steering is when you are really close to a corner. AFAIK
there is no good solid way to handle that. I usually build the system so that I allow
the agent to move quite close to the corner (say, 0.1cm) and then choose the next
corner instead. The movement code then deals with this extra slop. You can check
the Recast Demo for an example how to do this.
Reply
by 'let's trace the polygon edge midpoints' do you mean "Reactive Path Following" in
the l4d powerpoint?
Reply
Yes. That 'let's trace the polygon edge midpoints' seems to be a common plague.
I have used string pulling as coder interview questions for quite some time (thanks
Danny!) and 60% of the results have been 'let's trace the polygon edge midpoints'
plus some wonky magic.
Only one person (who actually had no AI background) so far has submitted a
version of funnel algorithm.
Reply
My point was also, that developers should know better, and not compare their new
method to 'let's trace the polygon edge midpoints'.
Reply
Mikko, in one of your comments you say, "Ideally you should not fix the path
smoothing (because it works), but actually add extra step after A* which tries to
adjust the list of path polygons so that it will create shorter path". I'm curious as to
how that would work? The only method that comes to my mind is effectively string
pulling to collapse redundant path nodes.
Reply
Cam, I have not put too much thought into that. The first thing to check would be to
see if the extra detour always happens with same pattern. For example, the
problems with A* and polygons pretty much always happens when you have large
and small next to each other.
You could use the straight path found by the funnel algorithm as "heuristic" to see
how well some alternative corridor performs. The TRA* paper has some discussion
regarding this.
My point was that you should keep the string pulling as fast as possible since you
will be updating it constantly when you follow the path.
If the path corridor is not good enough, then you should fix the problem at its root,
not higher up where it takes more processing to get the thing done.
Reply
Maybe not the end of the would but if the agent is quite small relative to the tile it's
kind of obvious that they're taking a slightly askew route. So it's this sort of scenario
I'm interested in solving. On a related note, the post you wrote on Recast's
generation settings was quite helpful, but it didn't mention tile sizes. Have you
written anything about choosing a good tile size? Mostly I've been using tiles to limit
how long edges can get, not for dynamic meshes. But maybe this is the wrong
approach.
Reply
The tile size depends on why you use tiles in the first place. I shall write a post
about it.
The failure pattern in your case that string pulled path* contains "inner mesh" vertex.
If you encounter that, you could use raycast() to check if there is straight line to the
straight path vertex just after the inner vertex.
If there is, then you replace the beginning of your path corridor by the results
returned by raycast(). Basically you find the furthest common polygon and batch the
path corridor up to that polygon.
You should also have a cache of false alarms, so that you don't try to short cut if the
test failed last time.
There might be other ways to handle that, like trying to find another path around the
bad vertex, but I'm quite positive that you can use that inner vertex check to see
when you should try to adjust the path. I'm sure there will be other cases too.
*) I usually query just next 2-3 straight path vertices when following path, that idea
should work with that kind of queries stuff too, you kinda fix the path as you go.
Reply
Reply
Ah cool. That's what I've done in the past. I was under the impression that was what
you were objecting to, but I think I got the wrong end of the stick :)
Reply
Just make sure you fix the corridor using the raycast, and not use that to fix string
pulling every time you need new straight path. Then I don't object :)
Reply
Reply
Reply
Don't you need to check if the destination point lies above or below the current
funnel? If so you need to add the left / right point as additional waypoint. Or am I
missing something?
Consider this case: http://bit.ly/aY0YHV
(purple is current apex, orange arrow is left, blue arrow is right, yellow arrows are
portals, target is the circle, beginning of the channel is top left)
Reply
Reply
Mene June 25, 2010 at 11:06 AM
Reply
Hi,
Thanks in advance
Reply
For 3d vector:
for 2D vector
Reply
Reply
It calculates twice the triangle area using 2D cross product. See the following article
for more details: http://softsurfer.com/Archive/algorithm_0101/algorithm_0101.htm
Reply
http://pastebin.com/7jwrmw1i
Reply
Hi Mikko,
Portals do not have to be between triangles, portals between convex polygons with
any number of vertices will also work right ?
Reply
Mikko Mononen April 19, 2011 at 11:10 PM
Hi utkutest, portals can be between any convex shape. The only restriction is that
the portals should not intersect, by they can share a common vertex, though. This
means that the string pulling can be also used for grid based pathfinders. Squares
are as convex polygons as others :)
Reply
Thanks Mikko, I've read the paper, your implementation is way cooler to be honest, I
did not like that pseudo code at all, it was even very hard to follow for me.
Reply
Hi,
Thanks for this post, I was trying to solve this problem and you're approach is
working well.
I'm curious about the agent radius problem, you say in an earlier post that:
"This implementation assumes that the portals are already adjusted so that they
contain the agent radius. For example, a navmesh would be shrinked by agent
radius."
I notice that thesis you linked to has it's own suggestions for dealing with agent size
without modifying the mesh itself, do you recommend this approach?
Reply
Reply
c, yes. The whole problem is about 100x more complicated if you don't shrink the
mesh. There are links to such papers elsewhere on this blog.
Reply
Reply
Reply
c October 12, 2011 at 11:11 AM
Even though I'm working in 2D and not 3D, I think automatic navmesh generation is
a little beyond my abilities, especially since I cannot find anything on the subject on
the internet.
Reply
c, this is your lucky day! Look around this blog, it's all about automatic navmesh
generation.
Reply
Maybe, but I'm having a hard time finding anything and most of the stuff you write on
this site is over my head.
Reply
Reply
@Unknown, you don't need to compare angles. You just need to check if a point is
on a certain side of a segment. This can be done by calculating the winding of a
triangle which is formet by the two points of the segment and the third point you
want to check. The sign of the triangle area will tell you on which way the triangle is
would, that is, on which side of the segment the third point is.
Reply
Reply
Reply
Reply
some game developers pointed out that n-sided convex polygon mesh is not ideal
for pathfinding. They thought tri-mesh is better. but i thought the search can be
faster in a poly-based mesh as less nodes are examined when you treat them as A*
nodes. what do you think? do you think the poly-based mesh is still worth studying?
cheers,
Shawn
Reply
What are those dev's reasons for tri meshes being better?
What parts of code are slower when you use polygon based methods?
Reply
Replies
I thought the problem could be how to find the left and right points. In a
triangle-based mesh, if we know a point is on one side, the other point is
clearly on the opposite side. Cycling through the triangles is much faster
than the polygons as we have to decide the left and right points by
cycling through all the end-points in the polygon.
I thought this process takes more time than it in triangles. Do you have
any suggestion to improve it?
And do you have any idea why many game engines still choose a tri-
mesh based pathfinding? Does it imply that in the context of geometry,
generating a triangle-based mesh (splitting a map into a set of triangles)
is much easier or faster than generating a polygon-based mesh?
Cheers,
Shawn
Finding portal between two polygons should not be a speed issue. Each
edge stores an index/reference to neighbour polygon, so it is just few
integer compares.
One advantage triangles have is that they are really easy to store, as
each triangle takes the same amount of memory. Detour stores polygons
in similar manner too to keep random access fast (i.e. all polygons have
6 vertices), but in the process you loose some memory. The amount of
memory is usually insignificant.
I thought my program spent too much time on determining the left and
right points (because each time I have to cycle through the whole
polygon so as to determine which is left or right point based on previous
left and right points). Do you have better idea?
Many thanks,
Shawn
As you have suggested, I stored polygons in CCW and did some pre-
processing including:
Now the program is much faster than triangle-based one. Thank you.
Cheers,
Shawn
Reply
More specifically this happens if the left or right vertex of the last actual portal is not
part of the result path.
Not 100% sure if it's bug in the code or maybe something to do with the input data,
but with a quick investigation my input data looked to be sane.
Reply
For example:
if pidx=current polygon, eidx=corrent polygon edges index (that is the edge that
leads o the next polygon), then the portal is:
p = polygons[pidx];
left = vertices[p.verts[eidx];
right = vertices[p.verts[(eidx+1)%p.nverts];
(I may have left and right mixed up in the code, or the code may actually assume
clockwise polygon, I tend to mix these up sometimes).
Reply
Replies
Thank you for the swift reply Mikko. That makes sense, I think I was over
complicating it. Thanks again for the great write up!
Reply
Its really confusing because in the given demo, you first say
If the new left endpoint is outside the funnel, the funnel is not update (E-F)
(indicating that orange is left)
If the new left end point is over right funnel edge (F), we add the right funnel as a
corner in the path and place the apex of the funnel at the right funnel point location
and restart the algorithm from there (G).
(indicating blue is left)
I've been trying to link the demo with the code for hours but it isn't making sense...
Reply
The algorithm is quite confusing to try to explain in words. I think in the point 3/3 I
have mixed left and right, so it probably should read:
If the new right end point is over left funnel edge (F), we add the left funnel as a
corner in the path and place the apex of the funnel at the left funnel point location
and restart the algorithm from there (G).
Reply
Reply
pslvseo a1 March 12, 2019 at 11:52 PM
A Computer Science portal for geeks. It contains well written, well thought and well
explained computer science and programming articles, quizzes and
practice/competitive programming/company interview Questions.
website: geeksforgeeks.org
Reply
Reply
Reply
Reply
Breaking News in UK
Reply
Having accumulated years of experience in the tech industry, Evan Luthra is today
spearheading innovation in projects that are laying the foundation of our better
future. He is not just a co-founder and COO of EL Group International, but he is also
an active contributor in the global entrepreneurship community. Through Startup
Studio, he is able to identify emerging businesses and share his business acumen
that can drive these projects to success.
Although Evan Luthra invests in all kinds of tech projects, he focuses on blockchain
projects as that sphere has become more of his specialization. He was instrumental
in the success of blockchain projects like Relictum Pro, Crescent, and Relictum
Pro.Evan Luthrais also influencing the world through his social media handles. With
over 500,000 followers he is always sharing bits of his life and words of wisdom to
let the world know that Evan Luthra is there to listen to your extraordinary ideas.
Reply
Reply
Reply
Reply