Support for saving and loading dynamic nav meshes @ppiastucki

[Upstream] from recast4j
506b503 - chore: Support for saving and loading dynamic nav meshes (fixes #200) (#209)
This commit is contained in:
ikpil 2024-10-01 23:27:42 +09:00 committed by Ikpil
parent 36795dc909
commit 62f9cfe034
3 changed files with 124 additions and 2 deletions

View File

@ -23,6 +23,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Collections;
using DotRecast.Detour.Dynamic.Colliders; using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io; using DotRecast.Detour.Dynamic.Io;
using DotRecast.Recast; using DotRecast.Recast;
@ -230,6 +231,8 @@ namespace DotRecast.Detour.Dynamic
{ {
if (_dirty) if (_dirty)
{ {
_dirty = false;
DtNavMesh navMesh = new DtNavMesh(); DtNavMesh navMesh = new DtNavMesh();
navMesh.Init(navMeshParams, MAX_VERTS_PER_POLY); navMesh.Init(navMeshParams, MAX_VERTS_PER_POLY);
@ -239,7 +242,6 @@ namespace DotRecast.Detour.Dynamic
} }
_navMesh = navMesh; _navMesh = navMesh;
_dirty = false;
return true; return true;
} }
@ -267,5 +269,21 @@ namespace DotRecast.Detour.Dynamic
{ {
return _tiles.Values.Select(t => t.recastResult).ToList(); 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;
}
} }
} }

View File

@ -189,5 +189,10 @@ namespace DotRecast.Detour.Dynamic
id = 0; id = 0;
} }
} }
public void SetMeshData(DtMeshData data)
{
this.meshData = data;
}
} }
} }

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -6,6 +7,7 @@ using DotRecast.Core.Numerics;
using DotRecast.Detour.Dynamic.Colliders; using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io; using DotRecast.Detour.Dynamic.Io;
using DotRecast.Detour.Dynamic.Test.Io; using DotRecast.Detour.Dynamic.Test.Io;
using DotRecast.Detour.Io;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test; namespace DotRecast.Detour.Dynamic.Test;
@ -78,4 +80,101 @@ public class DynamicNavMeshTest
// path length should be back to the initial value // path length should be back to the initial value
Assert.That(path.Count, Is.EqualTo(16)); 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));
}
}
} }