Skip to content

Commit

Permalink
Support for saving and loading dynamic nav meshes @ppiastucki
Browse files Browse the repository at this point in the history
[Upstream] from recast4j
506b503 - chore: Support for saving and loading dynamic nav meshes (fixes #200) (#209)
  • Loading branch information
ikpil committed Oct 1, 2024
1 parent 36795dc commit 62f9cfe
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 2 deletions.
22 changes: 20 additions & 2 deletions src/DotRecast.Detour.Dynamic/DtDynamicNavMesh.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ 3. This notice may not be removed or altered from any source distribution.
using System.Linq;
using System.Threading.Tasks;
using DotRecast.Core;
using DotRecast.Core.Collections;
using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io;
using DotRecast.Recast;
Expand Down Expand Up @@ -63,7 +64,7 @@ public DtDynamicNavMesh(DtVoxelFile voxelFile)
navMeshParams.orig.Y = voxelFile.bounds[1];
navMeshParams.orig.Z = voxelFile.bounds[2];
navMeshParams.tileWidth = voxelFile.useTiles ? voxelFile.cellSize * voxelFile.tileSizeX : voxelFile.bounds[3] - voxelFile.bounds[0];
navMeshParams.tileHeight = voxelFile.useTiles ? voxelFile.cellSize * voxelFile.tileSizeZ: voxelFile.bounds[5] - voxelFile.bounds[2];
navMeshParams.tileHeight = voxelFile.useTiles ? voxelFile.cellSize * voxelFile.tileSizeZ : voxelFile.bounds[5] - voxelFile.bounds[2];
navMeshParams.maxTiles = voxelFile.tiles.Count;
navMeshParams.maxPolys = 0x8000;
foreach (var t in voxelFile.tiles)
Expand Down Expand Up @@ -230,6 +231,8 @@ private bool UpdateNavMesh()
{
if (_dirty)
{
_dirty = false;

DtNavMesh navMesh = new DtNavMesh();
navMesh.Init(navMeshParams, MAX_VERTS_PER_POLY);

Expand All @@ -239,7 +242,6 @@ private bool UpdateNavMesh()
}

_navMesh = navMesh;
_dirty = false;
return true;
}

Expand Down Expand Up @@ -267,5 +269,21 @@ public List<RcBuilderResult> RecastResults()
{
return _tiles.Values.Select(t => t.recastResult).ToList();
}

public void NavMesh(DtNavMesh mesh)
{
_tiles.Values.ForEach(t =>
{
const int MAX_NEIS = 32;
DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
int nneis = mesh.GetTilesAt(t.voxelTile.tileX, t.voxelTile.tileZ, tiles, MAX_NEIS);
if (0 < nneis)
{
t.SetMeshData(tiles[0].data);
}
});
_navMesh = mesh;
_dirty = false;
}
}
}
5 changes: 5 additions & 0 deletions src/DotRecast.Detour.Dynamic/DtDynamicTile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,10 @@ public void AddTo(DtNavMesh navMesh)
id = 0;
}
}

public void SetMeshData(DtMeshData data)
{
this.meshData = data;
}
}
}
99 changes: 99 additions & 0 deletions test/DotRecast.Detour.Dynamic.Test/DynamicNavMeshTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
Expand All @@ -6,6 +7,7 @@
using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io;
using DotRecast.Detour.Dynamic.Test.Io;
using DotRecast.Detour.Io;
using NUnit.Framework;

namespace DotRecast.Detour.Dynamic.Test;
Expand Down Expand Up @@ -78,4 +80,101 @@ public void E2eTest()
// path length should be back to the initial value
Assert.That(path.Count, Is.EqualTo(16));
}


[Test]
public void ShouldSaveAndLoadDynamicNavMesh()
{
using var writerMs = new MemoryStream();
using var bw = new BinaryWriter(writerMs);


int maxVertsPerPoly = 6;
// load voxels from file

{
byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels");
using var readMs = new MemoryStream(bytes);
using var br = new BinaryReader(readMs);

DtVoxelFileReader reader = new DtVoxelFileReader(DtVoxelTileLZ4ForTestCompressor.Shared);
DtVoxelFile f = reader.Read(br);

// create dynamic navmesh
DtDynamicNavMesh mesh = new DtDynamicNavMesh(f);

// build navmesh asynchronously using multiple threads
mesh.Build(Task.Factory);

// Save the resulting nav mesh and re-use it
new DtMeshSetWriter().Write(bw, mesh.NavMesh(), RcByteOrder.LITTLE_ENDIAN, true);
maxVertsPerPoly = mesh.NavMesh().GetMaxVertsPerPoly();
}

{
byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels");
using var readMs = new MemoryStream(bytes);
using var br = new BinaryReader(readMs);

// load voxels from file
DtVoxelFileReader reader = new DtVoxelFileReader(DtVoxelTileLZ4ForTestCompressor.Shared);
DtVoxelFile f = reader.Read(br);

// create dynamic navmesh
DtDynamicNavMesh mesh = new DtDynamicNavMesh(f);
// use the saved nav mesh instead of building from scratch
DtNavMesh navMesh = new DtMeshSetReader().Read(new RcByteBuffer(writerMs.ToArray()), maxVertsPerPoly);
mesh.NavMesh(navMesh);

DtNavMeshQuery query = new DtNavMeshQuery(mesh.NavMesh());
IDtQueryFilter filter = new DtQueryDefaultFilter();

// find path
_ = query.FindNearestPoly(START_POS, EXTENT, filter, out var startNearestRef, out var startNearestPos, out var _);
_ = query.FindNearestPoly(END_POS, EXTENT, filter, out var endNearestRef, out var endNearestPos, out var _);

List<long> path = new List<long>();
query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);

// check path length without any obstacles
Assert.That(path.Count, Is.EqualTo(16));

// place obstacle
DtCollider colldier = new DtSphereCollider(SPHERE_POS, 20, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND, 0.1f);
long colliderId = mesh.AddCollider(colldier);

// update navmesh asynchronously
mesh.Update(Task.Factory);

// create new query
query = new DtNavMeshQuery(mesh.NavMesh());

// find path again
_ = query.FindNearestPoly(START_POS, EXTENT, filter, out startNearestRef, out startNearestPos, out var _);
_ = query.FindNearestPoly(END_POS, EXTENT, filter, out endNearestRef, out endNearestPos, out var _);

path = new List<long>();
query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);

// check path length with obstacles
Assert.That(path.Count, Is.EqualTo(19));

// remove obstacle
mesh.RemoveCollider(colliderId);
// update navmesh asynchronously
mesh.Update(Task.Factory);

// create new query
query = new DtNavMeshQuery(mesh.NavMesh());
// find path one more time
_ = query.FindNearestPoly(START_POS, EXTENT, filter, out startNearestRef, out startNearestPos, out var _);
_ = query.FindNearestPoly(END_POS, EXTENT, filter, out endNearestRef, out endNearestPos, out var _);

path = new List<long>();
query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);

// path length should be back to the initial value
Assert.That(path.Count, Is.EqualTo(16));
}
}
}

0 comments on commit 62f9cfe

Please sign in to comment.