diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f2e575..9216655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Changed data structure of 'neis' from List to byte[] for optimized memory usage and improved access speed in `DtLayerMonotoneRegion` - Changed new RcVec3f[3] to stackalloc RcVec3f[3] in DtNavMesh.GetPolyHeight() to reduce heap allocation - Changed memory handling to use stackalloc in DtNavMeshQuery.GetPolyWallSegments for reducing SOH +- Changed DtNavMeshQuery.GetPolyWallSegments() to use Span for enhanced performance, memory efficiency. ### Removed - Nothing diff --git a/src/DotRecast.Detour.Crowd/DtLocalBoundary.cs b/src/DotRecast.Detour.Crowd/DtLocalBoundary.cs index 85a3818..2b235ba 100644 --- a/src/DotRecast.Detour.Crowd/DtLocalBoundary.cs +++ b/src/DotRecast.Detour.Crowd/DtLocalBoundary.cs @@ -18,6 +18,7 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. */ +using System; using System.Collections.Generic; using DotRecast.Core; using DotRecast.Core.Numerics; @@ -90,6 +91,8 @@ namespace DotRecast.Detour.Crowd public void Update(long startRef, RcVec3f pos, float collisionQueryRange, DtNavMeshQuery navquery, IDtQueryFilter filter) { + const int MAX_SEGS_PER_POLY = DtDetour.DT_VERTS_PER_POLYGON * 3; + if (startRef == 0) { Reset(); @@ -104,18 +107,17 @@ namespace DotRecast.Detour.Crowd { // Secondly, store all polygon edges. m_segs.Clear(); - - var segmentVerts = new List(); - var segmentRefs = new List(); + Span segs = stackalloc RcSegmentVert[MAX_SEGS_PER_POLY]; + int nsegs = 0; for (int j = 0; j < m_polys.Count; ++j) { - var result = navquery.GetPolyWallSegments(m_polys[j], false, filter, ref segmentVerts, ref segmentRefs); + var result = navquery.GetPolyWallSegments(m_polys[j], filter, segs, null, ref nsegs, MAX_SEGS_PER_POLY); if (result.Succeeded()) { - for (int k = 0; k < segmentRefs.Count; ++k) + for (int k = 0; k < nsegs; ++k) { - RcSegmentVert s = segmentVerts[k]; + ref RcSegmentVert s = ref segs[k]; var s0 = s.vmin; var s3 = s.vmax; diff --git a/src/DotRecast.Detour/DtNavMeshQuery.cs b/src/DotRecast.Detour/DtNavMeshQuery.cs index 704a293..4d186ba 100644 --- a/src/DotRecast.Detour/DtNavMeshQuery.cs +++ b/src/DotRecast.Detour/DtNavMeshQuery.cs @@ -3138,12 +3138,12 @@ namespace DotRecast.Detour /// @param[out] segmentCount The number of segments returned. /// @param[in] maxSegments The maximum number of segments the result arrays can hold. /// @returns The status flags for the query. - public DtStatus GetPolyWallSegments(long refs, bool storePortals, IDtQueryFilter filter, - ref List segmentVerts, ref List segmentRefs) + public DtStatus GetPolyWallSegments(long refs, IDtQueryFilter filter, + Span segmentVerts, Span segmentRefs, ref int segmentCount, + int maxSegments) { - segmentVerts.Clear(); - segmentRefs.Clear(); - + segmentCount = 0; + DtStatus status = m_nav.GetTileAndPolyByRef(refs, out var tile, out var poly); if (status.Failed()) { @@ -3160,6 +3160,8 @@ namespace DotRecast.Detour Span ints = stackalloc DtSegInterval[MAX_INTERVAL]; int nints; + bool storePortals = segmentRefs != null; + status = DtStatus.DT_SUCCESS; for (int i = 0, j = poly.vertCount - 1; i < poly.vertCount; j = i++) @@ -3205,16 +3207,26 @@ namespace DotRecast.Detour continue; } - int ivj = poly.verts[j] * 3; - int ivi = poly.verts[i] * 3; - var seg = new RcSegmentVert(); - seg.vmin = RcVec.Create(tile.data.verts, ivj); - seg.vmax = RcVec.Create(tile.data.verts, ivi); - // RcArrays.Copy(tile.data.verts, ivj, seg, 0, 3); - // RcArrays.Copy(tile.data.verts, ivi, seg, 3, 3); - segmentVerts.Add(seg); - segmentRefs.Add(neiRef); - n++; + if (n < maxSegments) + { + int ivj = poly.verts[j] * 3; + int ivi = poly.verts[i] * 3; + var seg = new RcSegmentVert(); + seg.vmin = RcVec.Create(tile.data.verts, ivj); + seg.vmax = RcVec.Create(tile.data.verts, ivi); + segmentVerts[n] = seg; + if (null != segmentRefs) + { + segmentRefs[n] = neiRef; + } + + n++; + } + else + { + status |= DtStatus.DT_BUFFER_TOO_SMALL; + } + continue; } @@ -3232,12 +3244,25 @@ namespace DotRecast.Detour { float tmin = ints[k].tmin / 255.0f; float tmax = ints[k].tmax / 255.0f; - var seg = new RcSegmentVert(); - seg.vmin = RcVec.Lerp(tile.data.verts, vj, vi, tmin); - seg.vmax = RcVec.Lerp(tile.data.verts, vj, vi, tmax); - segmentVerts.Add(seg); - segmentRefs.Add(ints[k].refs); - n++; + + if (n < maxSegments) + { + var seg = new RcSegmentVert(); + seg.vmin = RcVec.Lerp(tile.data.verts, vj, vi, tmin); + seg.vmax = RcVec.Lerp(tile.data.verts, vj, vi, tmax); + segmentVerts[n] = seg; + + if (null != segmentRefs) + { + segmentRefs[n] = ints[k].refs; + } + + n++; + } + else + { + status |= DtStatus.DT_BUFFER_TOO_SMALL; + } } // Wall segment. @@ -3247,16 +3272,31 @@ namespace DotRecast.Detour { float tmin = imin / 255.0f; float tmax = imax / 255.0f; - var seg = new RcSegmentVert(); - seg.vmin = RcVec.Lerp(tile.data.verts, vj, vi, tmin); - seg.vmax = RcVec.Lerp(tile.data.verts, vj, vi, tmax); - segmentVerts.Add(seg); - segmentRefs.Add(0L); - n++; + + if (n < maxSegments) + { + var seg = new RcSegmentVert(); + seg.vmin = RcVec.Lerp(tile.data.verts, vj, vi, tmin); + seg.vmax = RcVec.Lerp(tile.data.verts, vj, vi, tmax); + segmentVerts[n] = seg; + + if (null != segmentRefs) + { + segmentRefs[n] = 0; + } + + n++; + } + else + { + status |= DtStatus.DT_BUFFER_TOO_SMALL; + } } } } + segmentCount = n; + return status; } diff --git a/src/DotRecast.Recast.Demo/Tools/TestNavmeshSampleTool.cs b/src/DotRecast.Recast.Demo/Tools/TestNavmeshSampleTool.cs index 5d78eda..7b4d364 100644 --- a/src/DotRecast.Recast.Demo/Tools/TestNavmeshSampleTool.cs +++ b/src/DotRecast.Recast.Demo/Tools/TestNavmeshSampleTool.cs @@ -472,8 +472,9 @@ public class TestNavmeshSampleTool : ISampleTool { if (m_polys != null) { - var segmentVerts = new List(); - var segmentRefs = new List(); + const int MAX_SEGS = DtDetour.DT_VERTS_PER_POLYGON * 4; + Span segs = stackalloc RcSegmentVert[MAX_SEGS]; + Span refs = stackalloc long[MAX_SEGS]; for (int i = 0; i < m_polys.Count; i++) { @@ -491,18 +492,20 @@ public class TestNavmeshSampleTool : ISampleTool dd.DepthMask(true); if (_sample.GetNavMeshQuery() != null) { + int nsegs = 0; var result = _sample .GetNavMeshQuery() - .GetPolyWallSegments(m_polys[i], false, m_filter, ref segmentVerts, ref segmentRefs); + .GetPolyWallSegments(m_polys[i], m_filter, segs, refs, ref nsegs, MAX_SEGS); if (result.Succeeded()) { dd.Begin(LINES, 2.0f); - for (int j = 0; j < segmentVerts.Count; ++j) + for (int j = 0; j < nsegs; ++j) { - RcSegmentVert s = segmentVerts[j]; + ref RcSegmentVert s = ref segs[j]; var v0 = s.vmin; var s3 = s.vmax; + // Skip too distant segments. var distSqr = DtUtils.DistancePtSegSqr2D(m_spos, v0, s3, out var tseg); if (distSqr > RcMath.Sqr(m_neighbourhoodRadius)) @@ -515,8 +518,9 @@ public class TestNavmeshSampleTool : ISampleTool RcVec3f norm = new RcVec3f(delta.Z, 0, -delta.X); norm = RcVec3f.Normalize(norm); RcVec3f p1 = RcVec.Mad(p0, norm, agentRadius * 0.5f); + // Skip backfacing segments. - if (segmentRefs[j] != 0) + if (refs[j] != 0) { int col = DuRGBA(255, 255, 255, 32); dd.Vertex(s.vmin.X, s.vmin.Y + agentClimb, s.vmin.Z, col); diff --git a/test/DotRecast.Detour.Test/GetPolyWallSegmentsTest.cs b/test/DotRecast.Detour.Test/GetPolyWallSegmentsTest.cs index c24b405..d5b3873 100644 --- a/test/DotRecast.Detour.Test/GetPolyWallSegmentsTest.cs +++ b/test/DotRecast.Detour.Test/GetPolyWallSegmentsTest.cs @@ -17,13 +17,13 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. */ +using System; using System.Collections.Generic; using DotRecast.Core; using NUnit.Framework; namespace DotRecast.Detour.Test; - public class GetPolyWallSegmentsTest : AbstractDetourTest { private static readonly RcSegmentVert[][] VERTICES = @@ -83,28 +83,30 @@ public class GetPolyWallSegmentsTest : AbstractDetourTest [Test] public void TestFindDistanceToWall() { - var segmentVerts = new List(); - var segmentRefs = new List(); + const int MAX_SEGS = DtDetour.DT_VERTS_PER_POLYGON * 4; + Span segs = stackalloc RcSegmentVert[MAX_SEGS]; + Span refs = stackalloc long[MAX_SEGS]; + int nsegs = 0; IDtQueryFilter filter = new DtQueryDefaultFilter(); for (int i = 0; i < startRefs.Length; i++) { - var result = query.GetPolyWallSegments(startRefs[i], true, filter, ref segmentVerts, ref segmentRefs); - Assert.That(segmentVerts.Count, Is.EqualTo(VERTICES[i].Length)); - Assert.That(segmentRefs.Count, Is.EqualTo(REFS[i].Length)); + var result = query.GetPolyWallSegments(startRefs[i], filter, segs, refs, ref nsegs, MAX_SEGS); + Assert.That(nsegs, Is.EqualTo(VERTICES[i].Length)); + Assert.That(nsegs, Is.EqualTo(REFS[i].Length)); for (int v = 0; v < VERTICES[i].Length / 6; v++) { - Assert.That(segmentVerts[v].vmin.X, Is.EqualTo(VERTICES[i][v].vmin.X).Within(0.001f)); - Assert.That(segmentVerts[v].vmin.Y, Is.EqualTo(VERTICES[i][v].vmin.Y).Within(0.001f)); - Assert.That(segmentVerts[v].vmin.Z, Is.EqualTo(VERTICES[i][v].vmin.Z).Within(0.001f)); - Assert.That(segmentVerts[v].vmax.X, Is.EqualTo(VERTICES[i][v].vmax.X).Within(0.001f)); - Assert.That(segmentVerts[v].vmax.Y, Is.EqualTo(VERTICES[i][v].vmax.Y).Within(0.001f)); - Assert.That(segmentVerts[v].vmax.Z, Is.EqualTo(VERTICES[i][v].vmax.Z).Within(0.001f)); + Assert.That(segs[v].vmin.X, Is.EqualTo(VERTICES[i][v].vmin.X).Within(0.001f)); + Assert.That(segs[v].vmin.Y, Is.EqualTo(VERTICES[i][v].vmin.Y).Within(0.001f)); + Assert.That(segs[v].vmin.Z, Is.EqualTo(VERTICES[i][v].vmin.Z).Within(0.001f)); + Assert.That(segs[v].vmax.X, Is.EqualTo(VERTICES[i][v].vmax.X).Within(0.001f)); + Assert.That(segs[v].vmax.Y, Is.EqualTo(VERTICES[i][v].vmax.Y).Within(0.001f)); + Assert.That(segs[v].vmax.Z, Is.EqualTo(VERTICES[i][v].vmax.Z).Within(0.001f)); } for (int v = 0; v < REFS[i].Length; v++) { - Assert.That(segmentRefs[v], Is.EqualTo(REFS[i][v])); + Assert.That(refs[v], Is.EqualTo(REFS[i][v])); } } }