Monday, August 16, 2010

Heads Up, API Changes Coming Soon

I try to get two API breaking changes done this week.

1) Recast Context: This change will add additional parameter to most of the recast calls. I have yet to decide if the parameter will be the first or last. The idea behind this change is to remove platform dependancy of error/warning logging, timer and profiling code from Recast. See:

2) dtNavMesh & dtNavMeshQuery: This change will allow multiple simultaneous navmesh queries to happen at the same time. Basically it will strip dtNavMesh to just the data management, and add new class which has all the query functions. This makes Detour more concurrency friendly. See:
I will make separate posts describing the changes once I get things checked in.

Thursday, August 12, 2010

Some More Movement Progress



Since last time, I implemented smooth steering, drunken movement style and sampled velocity obstacles.

The features are coming together, but the code is still huge pile of mess. I checked in first version (R193) so that the change list does not become too big.

When using tiled navmeshes and many agents, I got quite a bit of problems with agents trying to move backwards to catch up with their path. This happens mostly around the vertices which are at tile corners.

For the time being, I added that raycast trick which tries to optimize the path corridor by checking if there is line of sight from the current location to the corner after the next corner. If so, then the beginning of the path is patched to use the results from the raycast.

The performance is still realtime (it chokes when you have large group next to each other), but I bet it is actually really horrible. Once I get the structure of the code better, I'll start looking at the optimizations.

Tuesday, August 10, 2010

Oh, it moves!



The first test of a couple of agents moving around in a test level, yay!

I have implemented the navigation-loop stuff, simple steering, simple dynamic constraint, and simple collision handling. I need to rethink the way stuff is organized so that it is easier to grab the necessary code to your own projects.

It's always so amazing when it works the first time, even if it just barely does.

Monday, August 9, 2010

Detour Agent Movement Progress


I'm moving forward with the agent movement code for Detour. Today I added a couple of functions to Detour help me realize the goal. Check NavMeshTesterTool.cpp how to use the new functionality.
  • dtNavMesh.moveAlongSurface() - This function tries to move an agent from start position to target position along the navigation mesh and constraints the position to be inside the navigation mesh at all times. The function is Detour implementation of the algorithm I explained in a previous post. In the process I removed the old moveAlongPathCorridor(), which supported only subset of the functionality anyways.
  • dtNavMesh.findLocalNeighbourhood() - This function finds non-overlapping (in 2D) neighbourhood around a specified point. It is useful for local navigation. I have explained the algorithm earlier too.
  • dtNavMesh.getPolyWallSegments() - This functions returns wall segments of a specified navmesh polygon. In conjunction with findLocalNeighbourhood() it can be used to generate 2D collision segments for local obstacle avoidance.
The image at the top of the post shows local neighbourhood segments queried using the new functions.

All the above functions assume that they operate on really small set of polygons. To achieve a bit better cache coherence, I added another tiny node pool, which currently only contains 64 nodes. The aim was to improve cache coherence. The node pool uses hash table to lookup nodes based on ID. When dealing with small number of nodes this actually slows things down because of the random access through the hash table.

Additionally, instead of using priority queue, the above functions use fixed size small stack and breath first search. The random access of the navmesh polygons is quite cache pooper already, but the above gives around 30-50% speed up compared to using the existing p-queue and larger node pool.

I'm planning to spend some more time this week in the agent movement stuff. The above functions are bound to change as I try to find my way through the implementation details.

Thursday, July 29, 2010

Finding Local 2D Neighborhood of Navmesh


As part of my quest to move the multi-agent navigation to Detour I have been testing a little how to find local 2D neighbour of a navmesh. The method should answer the question that given a point on the navmesh and certain small radius, what are the potential navmesh edges to avoid. Since the Detour navmesh is sort of 2.5D structure, it is possible that the returned polygons may curl over itself close to bridge like structures.

My initial idea was to find visibility polygon from a single source point, but practical implementations are prone to the usual floating point accuracy issues, plus you would need to do that query every time an agent moves, which has the potential to become the bottleneck of the whole local avoidance.

Instead, I though I'd try to find a good way to eliminate the polygons which cause the problems. Finding the nearest polygons is simple using dtNavMesh.findPolysAroundCircle(), finding overlapping polygons is quick using SAT. But in case of overlap which polygon to keep?

One interesting feature of findPolysAroundCircle() function is that it returns the polygons from nearest distance to the furthest. Under the hood the method uses Dijkstra to find the polygons around. The method was designed to return the most relevant information up to the buffer limit the caller passes.

Since we have this information around, we can make the polygon culling so that if two polygons overlap, the latter–or furthest from the current location—polygon is always removed. So depending on situation the most irrelevant polygon is removed.

This seems to work great in practice as you can see from the image at the top of the post. If the start location is close to the ground plane, the ground polygons are chosen the the bridge is culled and vice versa.

Below is the code that I used for testing. The function arePolysNeighbour(), which check if there is a link from the first poly to the next poly, since they are two convex polygons, they cannot overlap if they are neighbours. The function testPolyCollision() does 2D poly-poly SAT collision check on XZ-plane.
float pa[DT_VERTS_PER_POLYGON*3];
float pb[DT_VERTS_PER_POLYGON*3];
npolys = m_navMesh->findPolysAroundCircle(startRef, startPos, radius, &filter, polys, m_parent, 0, MAX_POLYS);
for (int i = 0; i < npolys; ++i)
{
bool valid = true;
const int npa = getPolyVerts(m_navMesh, polys[i], pa);
for (int j = 0; j < i; ++j)
{
if (arePolysNeighbour(m_navMesh, polys[i], polys[j]))
continue;
const int npb = getPolyVerts(m_navMesh, polys[j], pb);
if (testPolyCollision(pa,npa, pb,npb))
{
valid = false;
break;
}
}
polyflag[i] = valid;
}

I'm a little worried about the performance, but it is not necessary to update the neighbourhood every tick, so several optimization methods may apply. I have to do some more testing, whether there are some corner cases which are not yet handled using the code. And I will need to check the performance more in detail too.

All in all, I think I have pretty much all the ingredients to build multi-agent navigation for Detour. The rest is just API design and hard work :)

Wednesday, July 21, 2010

Finding Navmesh Polygons Touching a Convex Shape


I added new query method to dtNavMesh to find polygons which touch a convex shape, findPolysAroundShape().

The function is similar to findPolysAround(), but with different boundary condition. As a matter of fact, I also renamed the findPolysAround() to findPolysAroundCircle() to make it less confusing. The function can be useful for creating some gameplay features such as doors.

The new method is somewhat slower than findPolysAroundCircle() because it uses polygon-segment intersection test to see if an edge should be visited.

Available in R186.

Monday, July 19, 2010

Constrained Movement Along Navmesh pt. 3


The most boring topic of the year makes a comeback! I have been struggling a bit to make robust movement along navigation mesh, but I think I finally nailed it.

Why should you care?

The basis of solid navigation loop is good collision handling. For navigation meshes this means a way to handle movement along connected graph of convex polygons. That is, how to move from one polygon to another and how to handle the case when the desired move collides with blocking navigation mesh edge.

The method should also return the polygons it walked through during the movement. This is used to fixup the path corridor so that on next update we have valid path to follow again.

First idea: Raycast



My very first implementation of movement code used raycasts. I used similar iterative code as the good 'ol ellipsoid vs. trimesh collision detection. The problem with that solution was that the rays end up being parallel to the polygon edges when sliding or hit vertices when aiming for next corner. I ended up running into a lot of floating point accuracy errors and quite interesting special case hacks to get rid of them.

Second idea: Point in Polygon


My second attempt changed the whole idea upside down. I can calculate the sliding movement inside a convex polygon by clamping the target point to be the nearest point in the polygon. To handle the connected graph of convex polygons (like navmesh) we can check if the nearest point would lie on an edge which leads to neighbor polygon, and follow that until the goal polygon has been found.

While the basic idea is sound, here area a couple of cases where it does not produce desired result. Firstly there are some corner cases where it will miss potential move to neighbour polygon because it does not check visibility (see my previous post). Interestingly this is not a problem when using triangles.

Second, nasty problem is that it does not handle well the case that the nearest point is at a polygon vertex. In this case either edge are equally good candidate to follow and I could not find goo heuristic which would handle all the corner cases I had in mind.

This method is a bit similar to the jump-and-walk algorithm which is used to locate triangles in triangulation. It is known to work on triangulations which have convex boundary.

Third idea: Search


This idea extends the previous by making it breath first search instead of iterative loop. That nicely solves the problem of trying to choose good branching at each iteration, all branches are followed instead.

The search expands to all edges which touch circle containing the start and goal location (start+delta). The search is stopped when the goal polygon is found or no more polygons can be visited. In case the goal is inside a polygon, the goal location is accepted, else the nearest point to all visited blocked polygon edges is chosen.

In pseudo code it looks something like this:

// Applies constrained movement from startPos to goalPos.
// The result is stored in clampedPos.
// Returns number of visited polygons
.
int Navmesh::navmeshMoveAlong(Vec3 startPos, PolyRef startRef, Vec3 goalPos,
PolyRef* visited, Vec3& clampedPos)
{
Stack stack(16); // Tiny stack
ClosedList closed(32); // Tiny closed list
Vec3 bestPos, p;
float bestDist = FLT_MAX, d;
PolyRef bestRef = UNDEF;
// Search constraint
Vec3 searchPos = (startPos+goalPos)/2;
const float searchRadius = vdist(startPos,goalPos)/2;
// Init
bestp = start;
stack.push(startRef);
closed.add(startRef,startRef); // Self ref, start maker.

while (!stack.empty())
{
// Pop front.
PolyRef cur = stack.pop();
Poly* poly = getPoly(cur);
// If target is inside the poly, stop search.
if (pointInPoly(goalPos, poly))
{
bestRef = cur;
bestPos = goalPos;
break;
}
// Follow edges or keep track of nearest point on blocking edge.
for (int i=0, j=poly->nv; i<poly->nv; j=i++)
{
const PolyRef neiRef = poly->nei[j];
const Vec3& sp = getVertex(poly->v[j]);
const Vec3& sq = getVertex(poly->v[i]);

if (neiRef == UNDEF)
{
// Blocked edge, calc distance.
p = closestPtPtSeg(goalPos, sp,sq);
d = vdist(p,goalPos);
if (d < bestd)
{
// Update nearest distance.
bestPos = p;
bestDist = d;
bestRef = cur;
}
}
else
{
// Skip already visited.
if (closed.find(neiRef)) continue;
// Store to closed with parent for trace back.
closed.add(neiRef,cur);
// Non-blocked edge, follow if within search radius.
p = closestPtPtSeg(searchPos, sp,sq);
d = vdist(p, searchPos);
if (d <= searchRadius)
stack.push(neiRef);
}
}
}
// Trace back and store visited polygons.
followVisited(bestRef,visited,closed);
// Store best movement position.
clampedPos = bestPos;
// Return number of visited polys.
return visited.size();
}

So it is a tiny special case pathfinder. The result is movement which can quite liberally move along the navmesh and if it encounters walls it will slide. The slack is necessary when you need to shortcut a little for example when very close to the next corner along the path.

The code above is meant to perform well when you do really small increments. It aims to be faster than regular pathfinder which is tuned to perform well with potentially thousands of nodes.

For such local case you may maintain the open list (stack) and closed lists using simple arrays. There are many cases in the code which may trash your cache (accessing the navmesh data), but by using small arrays for open and closed lists you can avoid some of it. Since we expect the lists to be small, we can potentially use simple linear searches which simplifies the code.

Next Step

My next task is to figure out nice way to calculate 2D local collision environment from 2.5D navmesh. This is to be used with velocity obstacles. Then I should be ready move on porting the VO code to Detour.