Ok so i am able to generate the navigation data, FindNearestPoly and GeneratePath works.
I make use of wowmapper to extract ADTs, CritterAI to generate InputGeometry and RecastManaged to generate NavMeshData.
The problem is that path generated is kinda weird. So here my code:
Read an .obj file, TriangleMesh
Code:
public class MeshLoader
{
private static int parseFace(string[] chunks, int[] data, int n, int vcnt)
{
int j = 0;
for (int i = 0; i < chunks.Length; i++)
{
var s = chunks[i];
if (s == "f") continue;
int vi = Convert.ToInt32(s);
data[j++] = vi < 0 ? vi + vcnt : vi - 1;
if (j >= n)
return j;
}
return j;
}
public static TriangleMesh LoadMesh(string filepath)
{
var verts = new List<Vector3>();
var tris = new List<int>();
var lines = File.ReadAllLines(filepath);
foreach (var line in lines)
{
var chunks = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (chunks[0] == "v")
{
Vector3 v3 = new Vector3(float.Parse(chunks[1], System.Globalization.CultureInfo.InvariantCulture), float.Parse(chunks[2], System.Globalization.CultureInfo.InvariantCulture), float.Parse(chunks[3], System.Globalization.CultureInfo.InvariantCulture));
verts.Add(v3);
}
if (chunks[0] == "f")
{
int[] face = new int[32];
var m_vertCount = verts.Count;
//// Faces
var nv = parseFace(chunks, face, 32, m_vertCount);
for (int i = 2; i < nv; ++i)
{
int a = face[0];
int b = face[i - 1];
int c = face[i];
if (a < 0 || a >= m_vertCount || b < 0 || b >= m_vertCount || c < 0 || c >= m_vertCount || a == b || b == c || c == a)
{
continue;
}
tris.AddRange(new int[3] { a, b, c });
}
}
}
//var m_triCount = tris.Count;
//var m_normals = new float[m_triCount * 3];
//for (int i = 0; i < m_triCount * 3; i += 3)
//{
// var v0 = verts[tris[i] * 3];
// var v1 = verts[tris[i + 1] * 3];
// var v2 = verts[tris[i + 2] * 3];
// var e0 = new Vector3(v1.x - v0.x, v1.y - v0.y, v1.x - v0.z);
// var e1 = new Vector3(v2.x - v0.x, v2.y - v0.y, v2.x - v0.z);
// //float[] n = m_normals[i];
// //n[0] = e0[1] * e1[2] - e0[2] * e1[1];
// //n[1] = e0[2] * e1[0] - e0[0] * e1[2];
// //n[2] = e0[0] * e1[1] - e0[1] * e1[0];
// //var d = Math.Sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
// //if (d > 0)
// //{
// // d = 1.0 / d;
// // n[0] *= d;
// // n[1] *= d;
// // n[2] *= d;
// //}
//}
var mesh = new TriangleMesh(verts.ToArray(), verts.Count, tris.ToArray(), tris.Count / 3);
return mesh;
}
}
Build InputGeometry
Code:
var mesh = MeshLoader.LoadMesh(inputFile);
//// Create an area buffer that assigns the default area id to all triangles.
byte[] areas = NMGen.CreateDefaultAreaBuffer(mesh.triCount);
//// Create the builder.
//// All triangles with a slope over 45.5f will be re-assigned to NMGen.NullArea.
InputGeometryBuilder gbuilder = InputGeometryBuilder.Create(mesh, areas, 0);
//// Build in a single step.
gbuilder.BuildAll();
//// Get the result.
InputGeometry geom = gbuilder.Result;
var data = TiledNavigationMesh.BuildAllTiles(geom, tileX, tileY);
Build 4x4 NavMeshData[,] (16 tiles per ADT)
Code:
NavMesh = new NavMesh();
QueryFilter = new QueryFilter();
InputGeom = inputGeom;
NavMeshParams parameters = new NavMeshParams();
var bounds = new Vector3(inputGeom.BoundsMin.x, inputGeom.BoundsMin.y, inputGeom.BoundsMin.z);
parameters.Origin = bounds;// new Vector3(-(32 * BuildConfig.TileSize), -(32 * BuildConfig.TileSize), -(32 * BuildConfig.TileSize));//new Vector3(inputGeom.BoundsMin.x, inputGeom.BoundsMin.y, inputGeom.BoundsMin.z);//WorldOrigin; //new Vector3(-(32 * BuildConfig.TileSize), -(32 * BuildConfig.TileSize), -(32 * BuildConfig.TileSize)); //new Vector3(inputGeom.BoundsMin.x, inputGeom.BoundsMin.y, inputGeom.BoundsMin.z);
parameters.TileWidth = BuildConfig.TileSize * BuildConfig.CellSize;
parameters.TileHeight = BuildConfig.TileSize * BuildConfig.CellSize;
parameters.MaxTiles = 16;// BuildConfig.MaxTiles;
parameters.MaxPolys = 262144;// BuildConfig.MaxPolysPerTile;
parameters.MaxNodes = NavMeshParamsMaxNodes;
//parameters.
var status = NavMesh.Init(parameters);
if (!status)
{
Console.WriteLine("Failure to init NavMesh.");
}
float[] bmin = new[] { InputGeom.BoundsMin.x, InputGeom.BoundsMin.y, InputGeom.BoundsMin.z };
float[] bmax = new[] { InputGeom.BoundsMax.x, InputGeom.BoundsMax.y, InputGeom.BoundsMax.z };
//bmin = new float[3];
//bmax = new float[3];
//bmin[0] = WorldOrigin.X + (BuildConfig.TileSize * tileX);
//bmin[2] = WorldOrigin.Y + (BuildConfig.TileSize * tileY);
//bmax[0] = WorldOrigin.X + (BuildConfig.TileSize * (tileX + 1));
//bmax[2] = WorldOrigin.Y + (BuildConfig.TileSize * (tileY + 1));
int gw = 0;
int gh = 0;
Recast.CalcGridSize(bmin, bmax, BuildConfig.CellSize, out gw, out gh);
int ts = (int)BuildConfig.TileSize;
int tw = (gw + ts - 1) / ts;
int th = (gh + ts - 1) / ts;
float tcs = BuildConfig.TileSize * BuildConfig.CellSize;
float[] tileBMin = new float[3];
float[] tileBMax = new float[3];
var parent = new TileIdentifier(tileX, tileY);
NavMeshData[,] allData = new NavMeshData[4, 4];
for (int y = 0; y < th; y++)
{
for (int x = 0; x < tw; x++)
{
tileBMin[0] = bmin[0] + x * tcs;
tileBMin[1] = bmin[1];
tileBMin[2] = bmin[2] + y * tcs;
tileBMax[0] = bmin[0] + (x + 1) * tcs;
tileBMax[1] = bmax[1];
tileBMax[2] = bmin[2] + (y + 1) * tcs;
TileIdentifier identifier = TileIdentifier.GetSubTile(parent, x, y);
//Build the tile mesh and return the data to add to the Nav Mesh
NavMeshData data = buildTileMesh(identifier.X, identifier.Y, tileBMin, tileBMax);
if (data != null)
{
NavMesh.RemoveTile(NavMesh.GetTileRefAt(identifier.X, identifier.Y));
TileReference result = NavMesh.AddTile(data);
allData[x, y] = data;
//TODO: Figure out if we need to use the TileReference thats returned.
//data.Dispose();
}
}
}
return GetTileGroupData(allData);
}
Building NavMeshData
Code:
private static unsafe NavMeshData buildTileMesh(int tx, int ty, float[] bmin, float[] bmax)
{
NavMeshData data = null;
Heightfield Heightfield;
CompactHeightfield CompactHeightfield;
ContourSet ContourSet;
PolyMesh PolyMesh;
PolyMeshDetail PolyMeshDetail;
byte[] areas;
var trimesh = InputGeom.ExtractMesh(out areas);
float[] verts = Vector3Util.Flatten(trimesh.verts, trimesh.vertCount);
int nverts = trimesh.vertCount;
int ntris = trimesh.triCount;
int[] tris = trimesh.tris;
//TODO: Make 2 defaults for Build Config (BuildConfig.DefaultSimple && BuildCongif.DefaultTiled)
BuildConfig.BMin = bmin;
BuildConfig.BMax = bmax;
BuildConfig.BMin[0] -= BuildConfig.BorderSize * BuildConfig.CellSize;
BuildConfig.BMin[2] -= BuildConfig.BorderSize * BuildConfig.CellSize;
BuildConfig.BMax[0] += BuildConfig.BorderSize * BuildConfig.CellSize;
BuildConfig.BMax[2] += BuildConfig.BorderSize * BuildConfig.CellSize;
if (!Recast.CreateHeightfield(BuildConfig.Width, BuildConfig.Height, BuildConfig.BMin, BuildConfig.BMax, BuildConfig.CellSize, BuildConfig.CellHeight, out Heightfield))
return null;
var TriAreas = new byte[ntris];
Recast.MarkWalkableTriangles(BuildConfig.WalkableSlopeAngle, verts, tris, out TriAreas);
Recast.RasterizeTriangles(verts, tris, TriAreas, Heightfield);
Recast.FilterLowHangingWalkableObstacles(BuildConfig.WalkableClimb, Heightfield);
Recast.FilterLedgeSpans(BuildConfig.WalkableHeight, BuildConfig.WalkableClimb, Heightfield);
Recast.FilterWalkableLowHeightSpans(BuildConfig.WalkableHeight, Heightfield);
if (!Recast.BuildCompactHeightfield(BuildConfig.WalkableHeight, BuildConfig.WalkableClimb, Heightfield, out CompactHeightfield))
return null;
if (!Recast.ErodeWalkableArea(BuildConfig.WalkableRadius, CompactHeightfield))
return null;
if (!Recast.BuildDistanceField(CompactHeightfield))
return null;
if (!Recast.BuildRegions(CompactHeightfield, BuildConfig.BorderSize, BuildConfig.MinRegionArea, BuildConfig.MergeRegionArea))
return null;
if (!Recast.BuildContours(CompactHeightfield, BuildConfig.MaxSimplificationError, BuildConfig.MaxEdgeLen, out ContourSet))
return null;
if (ContourSet.NConts == 0)
return null;
if (!Recast.BuildPolyMesh(ContourSet, BuildConfig.MaxVertsPerPoly, out PolyMesh))
return null;
if (!Recast.BuildPolyMeshDetail(PolyMesh, CompactHeightfield, BuildConfig.DetailSampleDistance, BuildConfig.DetailSampleMaxError, out PolyMeshDetail))
return null;
if (BuildConfig.MaxVertsPerPoly <= Detour.MaxVertsPerPolygon)
{
for (int i = 0; i < PolyMesh.NVerts; ++i)
{
PolyMesh.Verts[i * 3] -= (ushort)BuildConfig.BorderSize;
PolyMesh.Verts[(i * 3) + 2] -= (ushort)BuildConfig.BorderSize;
}
if (PolyMesh.NVerts >= 0xffff)
return null;
for (int i = 0; i < PolyMesh.NPolys; ++i)
{
if (PolyMesh.Areas[i] == (byte)DefaultAreaType.RC_WALKABLE_AREA)
PolyMesh.Areas[i] = (byte)SamplePolyAreas.SAMPLE_POLYAREA_GROUND;
if (PolyMesh.Areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_GROUND || PolyMesh.Areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_GROUND || PolyMesh.Areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_GROUND)
PolyMesh.Flags[i] = (byte)SamplePolyFlags.SAMPLE_POLYFLAGS_WALK;
else if (PolyMesh.Areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_DOOR)
PolyMesh.Flags[i] = (byte)SamplePolyFlags.SAMPLE_POLYFLAGS_WALK | (byte)SamplePolyFlags.SAMPLE_POLYFLAGS_DOOR;
}
NavMeshCreateParams parameters = new NavMeshCreateParams();
parameters.Verts = PolyMesh.Verts;
parameters.VertCount = PolyMesh.NVerts;
parameters.Polys = PolyMesh.Polys;
parameters.PolyAreas = PolyMesh.Areas;
parameters.PolyFlags = PolyMesh.Flags;
parameters.PolyCount = PolyMesh.NPolys;
parameters.NVP = PolyMesh.NVP;
parameters.DetailMeshes = PolyMeshDetail.Meshes;
parameters.DetailVerts = PolyMeshDetail.Verts;
parameters.DetailVertsCount = PolyMeshDetail.NVerts;
parameters.DetailTris = PolyMeshDetail.Tris;
parameters.DetailTriCount = PolyMeshDetail.NTris;
parameters.OffMeshConCount = 0;
parameters.WalkableHeight = BuildConfig.WalkableHeight;
parameters.WalkableRadius = BuildConfig.WalkableRadius;
parameters.WalkableClimb = BuildConfig.WalkableClimb;
parameters.BMin = new Vector3(bmin[0], bmin[1], bmin[2]);
parameters.BMax = new Vector3(bmax[0], bmax[1], bmax[2]);
parameters.CellSize = BuildConfig.CellSize;
parameters.CellHeight = BuildConfig.CellHeight;
parameters.TileX = tx;
parameters.TileY = ty;
parameters.TileSize = (int)BuildConfig.TileSize;
if (!Detour.CreateNavMeshData(parameters, out data))
return null;
}
//Dispose of temporary Recast object's (Can save them if/when needed)
TriAreas = null;
Heightfield.Dispose();
CompactHeightfield.Dispose();
ContourSet.Dispose();
PolyMesh.Dispose();
PolyMeshDetail.Dispose();
return data;
}
Code:
public class BuildConfig
{
public float[] BMax { get; set; }
public float[] BMin { get; set; }
public const int MaxTiles = 4096;
public const int MaxPolysPerTile = 1024;
public const int Width = (int)TileSize;
public const int Height = (int)TileSize;
public const int MaxVertsPerPoly = 6;
public const int TilesPerMapSide = 64;
public const float TileSize = (1600.0f / 3.0f);
private const int TileVoxelSize = 1800;
/// <summary>
/// The Center of a full 64x64 map
/// </summary>
public const float CenterPoint = (TilesPerMapSide / 2f) * TileSize;
public static readonly int MinRegionArea = (int)Math.Pow(6, 2);//20;
public static readonly int MergeRegionArea = (int)Math.Pow(12, 2);//40;
public const float CellSize = TileSize / TileVoxelSize;
public const float CellHeight = 0.3f;
public const float WalkableSlopeAngle = 50f;
public const float MaxSimplificationError = 1.3f;
public const float DetailSampleDistance = 3f;
public const float DetailSampleMaxError = 1.25f;
/// <summary>
/// Height of wow toon
/// </summary>
private const float WorldUnitWalkableHeight = 2.1f;//1.652778f;
/// <summary>
/// Maximum height where slope is not considered
/// </summary>
private const float WorldUnitWalkableClimb = 1.0f;
/// <summary>
/// Radius of wow toon
/// </summary>
private const float WorldUnitWalkableRadius = 0.6f;//0.2951389f;
public static readonly int WalkableHeight = (int)Math.Round(WorldUnitWalkableHeight / CellHeight);
public static readonly int WalkableClimb = (int)Math.Round(WorldUnitWalkableClimb / CellHeight);
public static readonly int WalkableRadius = (int)Math.Round(WorldUnitWalkableRadius / CellSize);
public static readonly int MaxEdgeLen = WalkableRadius * 8;
public static readonly int BorderSize = WalkableRadius + 4;
public static readonly int TileWidth = TileVoxelSize + (BorderSize * 2);
}
To initialize NavMesh in game:
Code:
private static readonly int NavMeshParamsMaxNodes = 2048;//0x12d2d3;
private static readonly int NavMeshParamsMaxPolys = 1024;//0x800;
private static readonly int NavMeshParamsMaxTiles = 4096;//0x4000;
NavMeshParams navMeshParams = new NavMeshParams();
navMeshParams.MaxTiles = NavMeshParamsMaxTiles;
navMeshParams.MaxPolys = NavMeshParamsMaxPolys;
navMeshParams.MaxNodes = NavMeshParamsMaxNodes;
navMeshParams.TileWidth = 133.3333f;
navMeshParams.TileHeight = 133.3333f;
navMeshParams.Origin = Vector3.Zero;
Then i add 16 tiles per ADT into this NavMesh, and generate path.
Detour generate path across that column (see Video) and not straight. What am i doing wrong?
Video link: http://www.dailymotion.com/video/xut...ence-01-1_auto