The Recast Resource
by Daniel Buckmaster · 12/04/2011 (2:56 pm) · 130 comments
A* pathfinding on automatically-generated navmeshes using the Recast/Detour library. Finally: stable, easy pathfinding for Torque!
Overview
This resource provides source code to integrate the excellent Recast/Detour library with T3D 1.1 final as described in this blog. I have received word that the resource works under 1.2 as well.Recast automatically generates navigation meshes from arbitrary source geometry. This resource allows you to define a region using a NavMesh object that will be parsed and turned into a navmesh. You have access to all Recast's navmesh generation parameters to tweak our navmesh to suit different geometry and character types.
Detour finds paths across a navmesh. The NavPath class allows you to find a path between two specified points. The path can be looping or one-way, and optionally allows you to specify a set of waypoints to visit between your source and destination.
This resource uses code from Lethal Concepts' great Recast meshloader resource to parse Torque's geometry into a format Recast will accept. Kudos to them for writing the code and allowing me to share it and my modifications!

Pathfinding through an invisible Station mesh.
Installation
- Download the source code from HNGames' public Torque file system.
- If you're looking for the beta release, you can find it here or here.
- Rename the folder called Project in RecastResource_T3D1.1F/My Projects to the name of the project you wish to install the resource into. (There really aren't many changes on this side of things - just some editor icons and a new mission - so this step is optional.)
- Copy the contents of the RecastResource_T3D1.1F folder into your Torque 3D install folder (i.e., C:/Torque/Torque 3D Pro 1.1). When prompted, allow the folders to be merged. All of the files in this resource are new, so you shouldn't have to worry about overwriting anything.
- Add the new files in the Engine/source/T3D/nav directory to your ProjectName DLL Visual Studio project. All of them. Even the ones in the nav/recast directory, which are the Recast/Detour source files.
- Make the three code and script changes below:
Replace the header block at the top of primBuilder.cpp with this:
//*****************************************************************************
// Primitive Builder
//*****************************************************************************
namespace PrimBuild
{
Vector<GFXVertexPCT> mTempVertBuff;
GFXVertexBufferHandle<GFXVertexPCT> mVertBuff;
GFXPrimitiveType mType;
U32 mCurVertIndex;
ColorI mCurColor( 255, 255, 255 );
Point2F mCurTexCoord;
const ColorI _colWhite( 255, 255, 255, 255 );
U32 mMaxVerts;
static void CheckVertexBounds()
{
// Grow vertex buffer
if(mCurVertIndex == mMaxVerts)
{
mMaxVerts *= 2;
mTempVertBuff.setSize(mMaxVerts);
}
}
#ifdef TORQUE_DEBUG
#define INIT_VERTEX_SIZE(x) mMaxVerts = x;
//#define VERTEX_BOUNDS_CHECK() AssertFatal( mCurVertIndex < mMaxVerts, "PrimBuilder encountered an out of bounds vertex! Break and debug!" );
#define VERTEX_BOUNDS_CHECK() CheckVertexBounds()
// This next check shouldn't really be used a lot unless you are tracking down
// a specific bug. -pw
#define VERTEX_SIZE_CHECK() AssertFatal( mCurVertIndex <= mMaxVerts, "PrimBuilder allocated more verts than you used! Break and debug or rendering artifacts could occur." );
#else
#define INIT_VERTEX_SIZE(x) mMaxVerts = x;
#define VERTEX_BOUNDS_CHECK() CheckVertexBounds()
#define VERTEX_SIZE_CHECK()
#endifIn Engine/source/T3D/convexShape.cpp, we need to modify the buildPolyList method to return triangles. At the moment, it seems to return some odd six-vertex face that messes up the geometry-parsing step of generating a NavMesh. This change is actually optional; however, if you don't make it, ConvexShape objects will not contribute any geometry to NavMesh generation.
Replace the ConvexShape::buildPolyList method with this:
bool ConvexShape::buildPolyList( PolyListContext context, AbstractPolyList *plist, const Box3F &box, const SphereF &sphere )
{
if ( mGeometry.points.empty() )
return false;
// If we're exporting deal with that first.
if ( context == PLC_Export )
{
AssertFatal( dynamic_cast<OptimizedPolyList*>( plist ), "ConvexShape::buildPolyList - Bad polylist for export!" );
_export( (OptimizedPolyList*)plist, box, sphere );
return true;
}
plist->setTransform( &mObjToWorld, mObjScale );
plist->setObject( this );
// Add points...
const Vector< Point3F > pointList = mGeometry.points;
S32 base = plist->addPoint( pointList[0] );
for ( S32 i = 1; i < pointList.size(); i++ )
plist->addPoint( pointList[i] );
// Add Surfaces...
const Vector< ConvexShape::Face > faceList = mGeometry.faces;
for ( S32 i = 0; i < faceList.size(); i++ )
{
const ConvexShape::Face &face = faceList[i];
S32 s = face.triangles.size();
for ( S32 j = 0; j < s; j++ )
{
plist->begin( 0, s*i + j );
plist->plane( PlaneF( face.centroid, face.normal ) );
plist->vertex( base + face.points[ face.triangles[j].p0 ] );
plist->vertex( base + face.points[ face.triangles[j].p1 ] );
plist->vertex( base + face.points[ face.triangles[j].p2 ] );
plist->end();
}
}
return true;
}In My Projects/Project/game/tools/worldEditor/scripts/editors/creator.ed.cs, we need to add a few small hooks to make our new AI classes show up in the editor.
Have a look for this block of code:
%this.beginGroup( "System" );
%this.registerMissionObject( "SimGroup" );
%this.endGroup();Before it, add:%this.beginGroup("Navigation");
%this.registerMissionObject("NavMesh", "Navigation mesh");
%this.registerMissionObject("NavPath", "Path");
%this.endGroup();That's it for the installation! You should now be able to recompile and start navigating.
Use
This video gives a good overview of how to use the NavMesh class. However, there have been some additions to the code since it was published:- You can now build the NavMesh from within the editor! Click on the 'build' checkbox under the 'NavMesh Build' section of the object inspector pane to build the mesh with its currently-selected settings.
- If you tick the 'buildThreaded' box, the NavMesh will build in a separate thread, leaving you to continue editing. The NavMesh will render in red while this process is ongoing.
- NavMesh objects with a fileName specified will automatically save themselves to this file name when the level is saved, and load from it when a level is loaded. It's always a good idea to use this function! (Unless you expect the geometry inside the NavMesh to change, or you're generating it on-the-fly.)
- Advanced options are presented beneath the build options.
- You can create a NavPath object from the editor in the same way as a NavMesh.
- A NavPath must be assigned a NavMesh object to operate on. SImply type the name of the NavMesh object in the NavPath's 'mesh' field.
- Once given a 'from' location and a 'to' location, a NavPath will automatically plan, and the result should be visible as a pink line in the editor.
- If you're creating a NavPath in scripts, you might need to call %path.plan() to create the path.
- You can optionally specify a regular Path object whose objects will be visited by the path between its 'from' and 'to' points. There is an example set up for you in navtest.mis.
- NavPaths automatically replan when their associated NavMesh is modified. Give it a go!
- You can access the points in a NavPath with the same scripting interface used with the regular Path class. This means NavPath objects can be plugged right into existing script functions like AIPlayer::spawnOnPath.
- The member 'isSliced' will allow the NavPath to operate over a short length of time. The amount of planning that is done each tick is controlled by the 'maxIterations' member.
%path = new NavPath() {
from = "0 1 1";
to = "10 1 1";
mesh = TheNavMesh;
};
// The path now contains all the points to navigate between from and to.
// Let's change the destination:
%path.to = %obj.getPosition();
%path.plan();
// Now the updated path points to the location of %objKnown issues
- Paths on large navmeshes will fail due to the maximum path length setting. You can modify this value, at the top of navPath.h (static const U32 MaxPathLen = 1024;).
- In navMesh.cpp, you'll need to remove the line #include "coverPoint.h"
- Steve Acaster has all the fun. Save some for us!
About the author
Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!
#42
Sean: Actually, I usually use separate spawn points for bots. I mimicked the Path interface in terms of getObject() syntax (which is actually a huge hack), but I forgot to check the rest of the scripts. I'll look into it and see what I can fix.
EDIT: Apparently stock AI code is terrible... I'm getting hangups everywhere where the AI character gets stuck spinning, not realising it's reached its destination. I think this was fixed on the forums, and in 1.2? Anyway, onto the release build...
Sean: Check out Gibby's code - using AIPlayer::spawnOnPath seems to work for both of us, except when the pathfinding actually fails (the problem Gibby's having in release builds). Does your NavPath actually get planned? If not, it will report its position as 0 0 0. If it does get planned, it will report its position as either the average of all node positions, or the position of the selected node (getObject() selects nodes).
EDIT: Okay, ]Gibby's issue is caused by NavPath::getTransform. You can see it's supposed to return a handle, but I just give it a local MatrixF object. This might be safeguarded against in debug mode, but in release mode SceneObject's getPosition ConsoleMethod, which returns the position of the transform instead of the result of getPosition() (go figure...), is just getting garbage back because the reference goes out of scope.
Long story short:
01/20/2012 (11:34 am)
Hmm, okay, I'll look into it on my side. Sorry, I should have remembered that breakpoints will do no good in a Release build :P. Thanks for the great reproduction steps!Sean: Actually, I usually use separate spawn points for bots. I mimicked the Path interface in terms of getObject() syntax (which is actually a huge hack), but I forgot to check the rest of the scripts. I'll look into it and see what I can fix.
EDIT: Apparently stock AI code is terrible... I'm getting hangups everywhere where the AI character gets stuck spinning, not realising it's reached its destination. I think this was fixed on the forums, and in 1.2? Anyway, onto the release build...
Sean: Check out Gibby's code - using AIPlayer::spawnOnPath seems to work for both of us, except when the pathfinding actually fails (the problem Gibby's having in release builds). Does your NavPath actually get planned? If not, it will report its position as 0 0 0. If it does get planned, it will report its position as either the average of all node positions, or the position of the selected node (getObject() selects nodes).
EDIT: Okay, ]Gibby's issue is caused by NavPath::getTransform. You can see it's supposed to return a handle, but I just give it a local MatrixF object. This might be safeguarded against in debug mode, but in release mode SceneObject's getPosition ConsoleMethod, which returns the position of the transform instead of the result of getPosition() (go figure...), is just getting garbage back because the reference goes out of scope.
Long story short:
- Remove the definitions of getPosition and getTransform in navPath.h
- Remove the declarations of these methods in navPath.cpp
- Change the definition of getObject in navPath.cpp to look like this:
S32 NavPath::getObject(S32 idx)
{
if(idx < mPoints.size())
{
setPosition(mPoints[idx]);
mCurIndex = idx;
}
return getId();
}
#43
Bienvenidos a mi Mundo, Guey!
BTW Can you suggest any further reading into GOAP? I've been trying to implement a master Planner that would control all the AI in a game and farm off individual actions to behavior trees. I've had some success with Flying/Wheeled vehicles, but I think I need more theory before I get much further...
01/21/2012 (6:15 am)
@Daniel: Thanks! That works!Quote:
Apparently stock AI code is terrible...
Bienvenidos a mi Mundo, Guey!
BTW Can you suggest any further reading into GOAP? I've been trying to implement a master Planner that would control all the AI in a game and farm off individual actions to behavior trees. I've had some success with Flying/Wheeled vehicles, but I think I need more theory before I get much further...
#44
If you haven't yet checked out Jeff Orkin's site, do so. It's where I learned everything I know about GOAP ;P. Jeff's papers are worth their weight in gold. Aside from that, I've been able to find very little in the way of literature. Nor are there many implementations floating around out there. You might want to check out my open-source planner project, where I've already managed to implement a basic GOAP-like planner (though I've since made some dramatic changes to the codebase to extend its feature set that render it nonfunctional... you'll need to check out revision 77 or so). Feel free to shoot me an email if you need any specific help.
01/21/2012 (7:20 am)
Great! The giveaway was that if you added dewdPath to the MissionGroup and selected it in the editor, you could see that the path had actually been created correctly. I should probably make paths render by default instead of needing to be selected, eh?If you haven't yet checked out Jeff Orkin's site, do so. It's where I learned everything I know about GOAP ;P. Jeff's papers are worth their weight in gold. Aside from that, I've been able to find very little in the way of literature. Nor are there many implementations floating around out there. You might want to check out my open-source planner project, where I've already managed to implement a basic GOAP-like planner (though I've since made some dramatic changes to the codebase to extend its feature set that render it nonfunctional... you'll need to check out revision 77 or so). Feel free to shoot me an email if you need any specific help.
#45
one. Any good ideas for networking this in terms of client/server.
So if I hack my client to open up a path that does not really
exist the server will not let me pathfind where I can not go.
Might mean server has to calc the path and send the way points
to the client. or they both do it, or more simply the client
does it and the server just checks against its collision structure
so you cannot walk through the object that you hacked out on your client.
two. any slick solution to figuring out the path for different size objects? for example a path calculation for a player is one thing
and that player can squeeze through lots of small places, but what
if the object is a tank, it can not go through the same places
even though the A* path will say you can since generally path finding
solutions do not take into account how big the object is versus
how wide the available path is.
I figure getting those two nailed down would be good for almost any game and are not necessarily specific to a game type.
Thoughts?
01/21/2012 (2:05 pm)
Very cool resource. I have a couple questions though.one. Any good ideas for networking this in terms of client/server.
So if I hack my client to open up a path that does not really
exist the server will not let me pathfind where I can not go.
Might mean server has to calc the path and send the way points
to the client. or they both do it, or more simply the client
does it and the server just checks against its collision structure
so you cannot walk through the object that you hacked out on your client.
two. any slick solution to figuring out the path for different size objects? for example a path calculation for a player is one thing
and that player can squeeze through lots of small places, but what
if the object is a tank, it can not go through the same places
even though the A* path will say you can since generally path finding
solutions do not take into account how big the object is versus
how wide the available path is.
I figure getting those two nailed down would be good for almost any game and are not necessarily specific to a game type.
Thoughts?
#46
If there were enough interest for client-side pathfinding I could try to work something up, but it's not very useful unless you have an object class that moves entirely on the client.
Different-sized objects is a toughie, and recast/detour doesn't support that in an organic way yet. Mikko's recommendation, which I think is a perfectly good one, is that you create a navmesh for each creature size, and select the appropriate one when pathfinding. So, you'd have a 'human' NavMesh with a small actor radius and a 'vehicle' NavMesh, covering the same area, with a large actor radius. After that, you just need to script your characters to select the NavMesh appropriate for them. Maybe something along the lines of:
01/21/2012 (3:04 pm)
Well, the navigation objects are strictly server-side. NavPath sends its points to the client object if I remember right (I may have changed it in my own codebase since I released the resource...), strictly for rendering purposes. NavMesh just grabs the server-side object on the local machine to render (so, no rendering navmeshes unless you're the host). AIPlayer is also entirely server-side, so the distinction matters little.If there were enough interest for client-side pathfinding I could try to work something up, but it's not very useful unless you have an object class that moves entirely on the client.
Different-sized objects is a toughie, and recast/detour doesn't support that in an organic way yet. Mikko's recommendation, which I think is a perfectly good one, is that you create a navmesh for each creature size, and select the appropriate one when pathfinding. So, you'd have a 'human' NavMesh with a small actor radius and a 'vehicle' NavMesh, covering the same area, with a large actor radius. After that, you just need to script your characters to select the NavMesh appropriate for them. Maybe something along the lines of:
function ShapeBase::getNavMesh(%this)
{
error("Not implemented!");
}
function AIPlayer::getNavMesh(%this)
{
return HumanNavMesh;
}
function AIWheeledVehicle::getNavMesh(%this)
{
return VehicleNavMesh;
}
function ShapeBase::goToTarget(%this, %obj)
{
%path = new NavPath() {
from = %this.getPosition();
to = %obj.getPosition();
mesh = %this.getNavMesh();
};
%this.followPath(%path);
}Now you can call goToTarget on either an AIPlayer or an AIWheeledVehicle and have them automatically use the appropriate mesh. Provided, of course, followPath is implemented for AIWheeledVehicle as well. And that you have NavMeshes named HumanNavMesh and VehicleNavMesh in your levels.
#47
01/23/2012 (5:28 pm)
Dan, I think I found at least part of my issue. I noticed that before I saved and reloaded everything worked perfectly, after I would need to rebuild my mesh. However, your included file works everytime without issue and the Navpath is offset from 0 0 0. Then I noticed that included with your navtest mission is a .nm file. I assume this is the navmesh that is generated out for the level and then loaded with the mission. This is not being generated in my case, which is why I believe the navmesh isn't loading?
#48
01/23/2012 (6:04 pm)
Kids, reading is fundamental! I missed the step about setting the filename in the listed items above. Once I did that, everything went smoothly.
#49
(It might be helpful in cases of dynamic geometry, where the NavMesh will be different on every mission load - I'm just worried about it adding significantly to mission load times.)
01/24/2012 (4:30 am)
Heh, that would do it. I originally intended to have the NavMesh attempt to load from a file, then build itself if that failed, at the start of every mission load. I think I removed it because I got annoyed at waiting for it to load, but I can add it in again if you think it's worthwhile!(It might be helpful in cases of dynamic geometry, where the NavMesh will be different on every mission load - I'm just worried about it adding significantly to mission load times.)
#50
01/30/2012 (1:10 pm)
Final release is out, I decree! I'll write it up in a blog soon, but for now here's the approximate changelog:- NavMeshes can build in a separate thread.
- NavPaths can use sliced planning.
- Bugs fixed and general code cleanup.
- Added a function to get a path's length.
#51
01/30/2012 (1:27 pm)
This sounds fantastic =)
#52
01/31/2012 (5:27 am)
Dan, I was having some issues with the file upload script, didn't look like the resource made it up there. I ended up replacing it last night with a user centered one, allowing you guys to maintain (upload/delete) your files. If you create an account you can share the resource with that! I need to add a bit of security for the site, but it's ready for uploads!
#53
01/31/2012 (5:47 am)
Thanks for the heads-up - and the hosting! Love the new system!
#54
Beta Recast Resource
01/31/2012 (9:03 am)
No problem! I added the beta resource and attached it to your account. You should see it in the files now. Beta Recast Resource
#55
01/31/2012 (1:27 pm)
Oh right - should have updated the old link ;P. Thanks!
#56
in NavMesh.cpp it has an #include "coverPoint.h" but there is no coverPoint.h included in the resource.
02/12/2012 (11:04 am)
Daniel, in NavMesh.cpp it has an #include "coverPoint.h" but there is no coverPoint.h included in the resource.
#57
I have everything implemented, navmeshes are being generated, I can even get navpaths working and using UAISK get the ai to spawn and use the paths.
Now my question is this, as a noncoder, how do I tie this resource into something like UAISK or Tactical AI Kit? I know this is out of the scope of what you are doing with the resource, but I am trying to learn how to put it all together, since building my own Action tree is out of the question due to my lack of skill
02/13/2012 (6:35 pm)
Dan,I have everything implemented, navmeshes are being generated, I can even get navpaths working and using UAISK get the ai to spawn and use the paths.
Now my question is this, as a noncoder, how do I tie this resource into something like UAISK or Tactical AI Kit? I know this is out of the scope of what you are doing with the resource, but I am trying to learn how to put it all together, since building my own Action tree is out of the question due to my lack of skill
#58
As for UAISK/TAIK integration, I don't own either of those products so I'm not sure how they already handle navigation. I understand that TAIK uses a handcrafted waypoint graph for pathfinding, whereas UAISK avoids pathfinding altogether, using LOS queries for obstacle avoidance.
What I would recommend doing is creating some sort of AIPlayer::goTo function as I show above, which creates a NavPath and uses the existing followPath functionality. Then, in any places where the AI code sends an AI character to a location, substitute your goTo method. UAISK might take a bit more modification, since it may be designed to avoid long-distance go-to instructions. You'd need to modify the default behaviours to take into account the characters' new ability to navigate the terrain.
02/13/2012 (10:44 pm)
Hey Donald - sorry about the slow reply, my internet access is a bit irregular at the moment. About the coverPoint.h, sorry about that, it was leftover from other work I was doing. Feel free to remove it, and I'll get around to updating the files when I can.As for UAISK/TAIK integration, I don't own either of those products so I'm not sure how they already handle navigation. I understand that TAIK uses a handcrafted waypoint graph for pathfinding, whereas UAISK avoids pathfinding altogether, using LOS queries for obstacle avoidance.
What I would recommend doing is creating some sort of AIPlayer::goTo function as I show above, which creates a NavPath and uses the existing followPath functionality. Then, in any places where the AI code sends an AI character to a location, substitute your goTo method. UAISK might take a bit more modification, since it may be designed to avoid long-distance go-to instructions. You'd need to modify the default behaviours to take into account the characters' new ability to navigate the terrain.
#59
02/14/2012 (4:29 am)
this is amazing! Will this work well for Zombie Pathfinding?
#60
02/14/2012 (4:53 am)
also - will this generate paths for terrain/forests etc?
Torque Owner Gibby
faderboy digital media
(edit)
I'm using VC2008, and 'Clean' the entire solution, verify the files have been deleted and then 'Build' the solution. After verifying that the binaries have been re-built, I run the .exe via Torsion, and in 'Debug' it runs perfectly. In release, it still can't find the recast code. Granted, I'm much more fluent in xCode, but have been using VC++ for several years and haven't seen anything like this before...
(edit)
I modded your navtest.mis by adding this:
new WayPoint(destination) { team = "0"; isAIControlled = "0"; dataBlock = "WayPointMarker"; position = "0.824108 17.4247 5.78479"; rotation = "-0.091989 -0.0545322 -0.994266 61.6095"; scale = "1 1 1"; canSave = "1"; canSaveDynamicFields = "1"; };Then added this to aiPlayer.cs:
function spawnRecast() { // Use a node from 'Patrol' path as a random start point %tempPath = "MissionGroup/Patrol"; if (!isObject(%tempPath)) return 0; %node = %tempPath.getObject(1); //'destination' is a Waypoint marker in navtest.mis %dest = "MissionGroup/destination"; // Here's our from and to... %start = %node.getPosition(); %end = %dest.getPosition(); %path = new NavPath(dewdPath) { from = %start; to = %end; mesh = Nav; }; %player = AIPlayer::spawnOnPath("Dewd", %path); //echo("**************** %start is " @ %start @ " %end is " @ %end @ " %path is " @ %path @ " %player is " @ %player); if (isObject(%player)) { %player.followPath(%path, -1); // slow this sucker down, I'm tired of chasing him! %player.setMoveSpeed(0.5); return; } else return; // Let's change the destination: //%path.to = %obj.getPosition(); //%path.plan(); }In the Debug build, this will spawn an aiPlayer on top of the smaller group of blocks and the bot will follow the path to the waypoint. In Release, it spawns at 0 0 0 inside the blocks and spins like a dervish, as it can't find a node/path to go to...