From 449923cb3ebc91039b44ccf20c793c2961db5e50 Mon Sep 17 00:00:00 2001 From: Doprez <73259914+Doprez@users.noreply.github.com> Date: Mon, 29 Dec 2025 17:01:46 -0700 Subject: [PATCH 1/5] compiling update --- sources/Directory.Packages.props | 10 ++--- .../Stride.Navigation/DtMeshDataSerializer.cs | 32 ++++++++------- .../DynamicNavigationMeshSystem.cs | 4 +- .../InternalNavigationMesh.cs | 25 ++++++------ .../Stride.Navigation/NavigationBuilder.cs | 40 +++++++++++++++++-- .../Stride.Navigation.csproj | 1 + 6 files changed, 76 insertions(+), 36 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index d80c39307b..4c59a529c8 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -5,10 +5,10 @@ - - - - + + + + @@ -148,4 +148,4 @@ - \ No newline at end of file + diff --git a/sources/engine/Stride.Navigation/DtMeshDataSerializer.cs b/sources/engine/Stride.Navigation/DtMeshDataSerializer.cs index 9c4b722a91..c1ee633279 100644 --- a/sources/engine/Stride.Navigation/DtMeshDataSerializer.cs +++ b/sources/engine/Stride.Navigation/DtMeshDataSerializer.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System.Diagnostics; using DotRecast.Core.Numerics; using DotRecast.Detour; using Stride.Core.Serialization; @@ -40,14 +41,14 @@ internal void WriteDtMeshBvTree(DtBVNode[] dataBvTree) if (isNull) continue; - for (int i = 0; i < 3; i++) - { - stream.Write(t.bmin[i]); - } - for (int i = 0; i < 3; i++) - { - stream.Write(t.bmax[i]); - } + stream.Write(t.bmin.X); + stream.Write(t.bmin.Y); + stream.Write(t.bmin.Z); + + stream.Write(t.bmax.X); + stream.Write(t.bmax.Y); + stream.Write(t.bmax.Z); + stream.Write(t.i); } } @@ -173,10 +174,13 @@ internal DtBVNode[] ReadDtMeshBvTree() continue; } var node = new DtBVNode(); - for (int j = 0; j < 3; j++) - node.bmin[j] = stream.Read(); - for (int j = 0; j < 3; j++) - node.bmax[j] = stream.Read(); + node.bmin.X = stream.Read(); + node.bmin.Y = stream.Read(); + node.bmin.Z = stream.Read(); + + node.bmax.X = stream.Read(); + node.bmax.Y = stream.Read(); + node.bmax.Z = stream.Read(); node.i = stream.Read(); arr[i] = node; @@ -207,7 +211,7 @@ internal DtPolyDetail[] ReadDtMeshDetailMeshes() int count = stream.Read(); var arr = new DtPolyDetail[count]; for (int i = 0; i < count; i++) - arr[i] = new DtPolyDetail(stream.Read(),stream.Read(),stream.Read(),stream.Read()); + arr[i] = new DtPolyDetail(stream.Read(),stream.Read(),stream.Read(),stream.Read()); return arr; } diff --git a/sources/engine/Stride.Navigation/DynamicNavigationMeshSystem.cs b/sources/engine/Stride.Navigation/DynamicNavigationMeshSystem.cs index 8728879126..e906ca708f 100644 --- a/sources/engine/Stride.Navigation/DynamicNavigationMeshSystem.cs +++ b/sources/engine/Stride.Navigation/DynamicNavigationMeshSystem.cs @@ -176,7 +176,7 @@ public async Task Rebuild() }); await result; - FinilizeRebuild(result); + FinalizeRebuild(result); return result.Result; } @@ -191,7 +191,7 @@ internal void InitializeSettingsFromNavigationSettings(NavigationSettings naviga pendingRebuild = true; } - private void FinilizeRebuild(Task resultTask) + private void FinalizeRebuild(Task resultTask) { var result = resultTask.Result; if (result.Success) diff --git a/sources/engine/Stride.Navigation/InternalNavigationMesh.cs b/sources/engine/Stride.Navigation/InternalNavigationMesh.cs index 3de1af7e15..d56b73da5a 100644 --- a/sources/engine/Stride.Navigation/InternalNavigationMesh.cs +++ b/sources/engine/Stride.Navigation/InternalNavigationMesh.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using DotRecast.Core.Numerics; using DotRecast.Detour; using Stride.Core.Mathematics; @@ -40,7 +41,8 @@ public InternalNavigationMesh(float cellTileSize) meshParams.maxPolys = 1 << polyBits; // Initialize the query object - navMesh = new DtNavMesh(meshParams, 2048); + navMesh = new DtNavMesh(); + navMesh.Init(meshParams, 2048); navQuery = new DtNavMeshQuery(navMesh); } @@ -51,7 +53,7 @@ public bool LoadTile(DtMeshData navData) if (navData == null) return false; - long tileRef = navMesh.AddTile(navData, 0, 0); + var status = navMesh.AddTile(navData, 0, 0, out long tileRef); return tileRef != 0; } @@ -75,13 +77,14 @@ public void DoPathFindQuery(PathFindQuery query, ref PathFindResult result) status = navQuery.FindNearestPoly(query.Target.ToDotRecastVector(), query.FindNearestPolyExtent.ToDotRecastVector(), filter, out long endPoly, out RcVec3f endPoint, out _); if (status.Failed()) return; - - List polys = new(query.MaxPathPoints); - status = navQuery.FindPath(startPoly, endPoly, startPoint, endPoint, filter, ref polys, DtFindPathOption.NoOption); + + long[] polys = new long[query.MaxPathPoints]; + status = navQuery.FindPath(startPoly, endPoly, startPoint, endPoint, filter, polys, out var pathCount, query.MaxPathPoints); if (status.Failed() || status.IsPartial()) return; - - status = navQuery.FindStraightPath(startPoint, endPoint, polys, ref result.PathPoints, query.MaxPathPoints, 0); + + var pathPointsSpan = CollectionsMarshal.AsSpan(result.PathPoints); + status = navQuery.FindStraightPath(startPoint, endPoint, polys, polys.Length, pathPointsSpan, out var straightPathCount, query.MaxPathPoints, 0); if (status.Failed()) return; result.PathFound = true; @@ -96,12 +99,10 @@ public void DoRaycastQuery(RaycastQuery query, out NavigationRaycastResult resul DtStatus status = navQuery.FindNearestPoly(query.Source.ToDotRecastVector(), query.FindNearestPolyExtent.ToDotRecastVector(), filter, out long startPoly, out _, out _); if (status.Failed()) return; - - List polys = new (query.MaxPathPoints); + + long[] polys = new long[query.MaxPathPoints]; var normal = result.Normal.ToDotRecastVector(); - status = navQuery.Raycast(startPoly, query.Source.ToDotRecastVector(), - query.Target.ToDotRecastVector(), filter, - out float t, out normal, ref polys); + status = navQuery.Raycast(startPoly, query.Source.ToDotRecastVector(), query.Target.ToDotRecastVector(), filter, out float t, out normal, polys, out var pathCount, query.MaxPathPoints); result.Normal = new(normal.X, normal.Y, normal.Z); if (status.Failed()) diff --git a/sources/engine/Stride.Navigation/NavigationBuilder.cs b/sources/engine/Stride.Navigation/NavigationBuilder.cs index 0bcbd120c9..380a7cbc19 100644 --- a/sources/engine/Stride.Navigation/NavigationBuilder.cs +++ b/sources/engine/Stride.Navigation/NavigationBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System; @@ -96,7 +96,7 @@ public GeneratedData BuildNavmesh(ref Vector3[] vertices, ref int[] indices) } // Find walkable triangles and rasterize into heightfield - triAreas = RcCommons.MarkWalkableTriangles(context, buildSettings.AgentMaxSlope, verts, indices, numTriangles, new RcAreaModification(RcAreaModification.RC_AREA_FLAGS_MASK)); + triAreas = MarkWalkableTriangles(context, buildSettings.AgentMaxSlope, verts, indices, numTriangles, new RcAreaModification(RcAreaModification.RC_AREA_FLAGS_MASK)); RcRasterizations.RasterizeTriangles(context, verts, indices, triAreas, numTriangles, solid, walkableClimb); // Filter walkable surfaces. @@ -129,7 +129,7 @@ public GeneratedData BuildNavmesh(ref Vector3[] vertices, ref int[] indices) // Update poly flags from areas. for (int i = 0; i < polyMesh.npolys; ++i) { - if (polyMesh.areas[i] == RcConstants.RC_WALKABLE_AREA) + if (polyMesh.areas[i] == 63) polyMesh.areas[i] = 0; if (polyMesh.areas[i] == 0) @@ -189,4 +189,38 @@ private bool CreateDetourMesh() return navmeshData != null; } + + private static int[] MarkWalkableTriangles(RcContext ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt, RcAreaModification areaMod) + { + int[] array = new int[nt]; + float num = MathF.Cos(walkableSlopeAngle / 180f * (float)Math.PI); + Vector3 norm = default(Vector3); + for (int i = 0; i < nt; i++) + { + int num2 = i * 3; + Vector3 v = ToVec3(verts, tris[num2] * 3); + Vector3 v2 = ToVec3(verts, tris[num2 + 1] * 3); + Vector3 v3 = ToVec3(verts, tris[num2 + 2] * 3); + CalcTriNormal(v, v2, v3, ref norm); + if (norm.Y > num) + { + array[i] = areaMod.Apply(array[i]); + } + } + + return array; + } + + private static void CalcTriNormal(Vector3 v0, Vector3 v1, Vector3 v2, ref Vector3 norm) + { + Vector3 v3 = v1 - v0; + Vector3 v4 = v2 - v0; + norm = Vector3.Cross(v3, v4); + norm = Vector3.Normalize(norm); + } + + private static Vector3 ToVec3(float[] values, int n) + { + return new Vector3(values[n], values[n + 1], values[n + 2]); + } } diff --git a/sources/engine/Stride.Navigation/Stride.Navigation.csproj b/sources/engine/Stride.Navigation/Stride.Navigation.csproj index 9378f597ca..b20ba7bb08 100644 --- a/sources/engine/Stride.Navigation/Stride.Navigation.csproj +++ b/sources/engine/Stride.Navigation/Stride.Navigation.csproj @@ -24,6 +24,7 @@ + From b085faa23e540deaa8fe5c5a4d24e3005b2c5bc9 Mon Sep 17 00:00:00 2001 From: Doprez <73259914+Doprez@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:23:14 -0700 Subject: [PATCH 2/5] Fixed missing constant --- sources/engine/Stride.Navigation/NavigationBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/engine/Stride.Navigation/NavigationBuilder.cs b/sources/engine/Stride.Navigation/NavigationBuilder.cs index 380a7cbc19..d962fb8606 100644 --- a/sources/engine/Stride.Navigation/NavigationBuilder.cs +++ b/sources/engine/Stride.Navigation/NavigationBuilder.cs @@ -129,7 +129,7 @@ public GeneratedData BuildNavmesh(ref Vector3[] vertices, ref int[] indices) // Update poly flags from areas. for (int i = 0; i < polyMesh.npolys; ++i) { - if (polyMesh.areas[i] == 63) + if (polyMesh.areas[i] == RcRecast.RC_WALKABLE_AREA) polyMesh.areas[i] = 0; if (polyMesh.areas[i] == 0) From 37e084ee0e2c87e56f6451ef596ff5c7fb346c7d Mon Sep 17 00:00:00 2001 From: Doprez <73259914+Doprez@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:35:04 -0700 Subject: [PATCH 3/5] use moved RcRecast helpers --- .../Stride.Navigation/NavigationBuilder.cs | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/sources/engine/Stride.Navigation/NavigationBuilder.cs b/sources/engine/Stride.Navigation/NavigationBuilder.cs index d962fb8606..26295c56fa 100644 --- a/sources/engine/Stride.Navigation/NavigationBuilder.cs +++ b/sources/engine/Stride.Navigation/NavigationBuilder.cs @@ -96,7 +96,7 @@ public GeneratedData BuildNavmesh(ref Vector3[] vertices, ref int[] indices) } // Find walkable triangles and rasterize into heightfield - triAreas = MarkWalkableTriangles(context, buildSettings.AgentMaxSlope, verts, indices, numTriangles, new RcAreaModification(RcAreaModification.RC_AREA_FLAGS_MASK)); + triAreas = RcRecast.MarkWalkableTriangles(context, buildSettings.AgentMaxSlope, verts, indices, numTriangles, new RcAreaModification(RcAreaModification.RC_AREA_FLAGS_MASK)); RcRasterizations.RasterizeTriangles(context, verts, indices, triAreas, numTriangles, solid, walkableClimb); // Filter walkable surfaces. @@ -189,38 +189,4 @@ private bool CreateDetourMesh() return navmeshData != null; } - - private static int[] MarkWalkableTriangles(RcContext ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt, RcAreaModification areaMod) - { - int[] array = new int[nt]; - float num = MathF.Cos(walkableSlopeAngle / 180f * (float)Math.PI); - Vector3 norm = default(Vector3); - for (int i = 0; i < nt; i++) - { - int num2 = i * 3; - Vector3 v = ToVec3(verts, tris[num2] * 3); - Vector3 v2 = ToVec3(verts, tris[num2 + 1] * 3); - Vector3 v3 = ToVec3(verts, tris[num2 + 2] * 3); - CalcTriNormal(v, v2, v3, ref norm); - if (norm.Y > num) - { - array[i] = areaMod.Apply(array[i]); - } - } - - return array; - } - - private static void CalcTriNormal(Vector3 v0, Vector3 v1, Vector3 v2, ref Vector3 norm) - { - Vector3 v3 = v1 - v0; - Vector3 v4 = v2 - v0; - norm = Vector3.Cross(v3, v4); - norm = Vector3.Normalize(norm); - } - - private static Vector3 ToVec3(float[] values, int n) - { - return new Vector3(values[n], values[n + 1], values[n + 2]); - } } From b92ac1f407385ae2f8edd5ae00a4943d5dfc3d20 Mon Sep 17 00:00:00 2001 From: Doprez <73259914+Doprez@users.noreply.github.com> Date: Wed, 31 Dec 2025 15:04:43 -0700 Subject: [PATCH 4/5] fixed broken path finding with new version --- .../InternalNavigationMesh.cs | 31 +++++++++++++++---- .../engine/Stride.Navigation/Navigation.cs | 2 +- .../Processors/RecastNavigationMesh.cs | 4 +-- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/sources/engine/Stride.Navigation/InternalNavigationMesh.cs b/sources/engine/Stride.Navigation/InternalNavigationMesh.cs index d56b73da5a..a7ed805dc7 100644 --- a/sources/engine/Stride.Navigation/InternalNavigationMesh.cs +++ b/sources/engine/Stride.Navigation/InternalNavigationMesh.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; using DotRecast.Core.Numerics; using DotRecast.Detour; @@ -79,14 +80,32 @@ public void DoPathFindQuery(PathFindQuery query, ref PathFindResult result) return; long[] polys = new long[query.MaxPathPoints]; - status = navQuery.FindPath(startPoly, endPoly, startPoint, endPoint, filter, polys, out var pathCount, query.MaxPathPoints); - if (status.Failed() || status.IsPartial()) - return; + navQuery.FindPath(startPoly, endPoly, startPoint, endPoint, filter, polys, out var pathCount, polys.Length); - var pathPointsSpan = CollectionsMarshal.AsSpan(result.PathPoints); - status = navQuery.FindStraightPath(startPoint, endPoint, polys, polys.Length, pathPointsSpan, out var straightPathCount, query.MaxPathPoints, 0); - if (status.Failed()) + if (0 >= pathCount) + { return; + } + + // In case of partial path, make sure the end point is clamped to the last polygon. + var endPosition = new RcVec3f(endPoint.X, endPoint.Y, endPoint.Z); + if (polys[pathCount - 1] != endPoly) + { + status = navQuery.ClosestPointOnPoly(polys[pathCount - 1], endPoint, out var closest, out var _); + if (status.Succeeded()) + { + endPosition = closest; + } + } + + // Due to Dotrecast using Spans, we need to allocate the array with the max size possible then resize it later to remove empty entries. + // TODO: By default we allocate 1024 points which is way more than enough for most cases and should maybe be defaulted to a smaller value in the future. + result.PathPoints = new DtStraightPath[query.MaxPathPoints]; + navQuery.FindStraightPath(startPoint, endPosition, polys, pathCount, result.PathPoints, out var straightPathCount, query.MaxPathPoints, 0); + + // cut out the empty entries + Array.Resize(ref result.PathPoints, straightPathCount); + result.PathFound = true; } diff --git a/sources/engine/Stride.Navigation/Navigation.cs b/sources/engine/Stride.Navigation/Navigation.cs index a873982379..8e712b35a0 100644 --- a/sources/engine/Stride.Navigation/Navigation.cs +++ b/sources/engine/Stride.Navigation/Navigation.cs @@ -27,7 +27,7 @@ internal struct PathFindQuery internal struct PathFindResult { public bool PathFound; - public List PathPoints; + public DtStraightPath[] PathPoints; } internal struct BuildSettings diff --git a/sources/engine/Stride.Navigation/Processors/RecastNavigationMesh.cs b/sources/engine/Stride.Navigation/Processors/RecastNavigationMesh.cs index f311af3c72..012a0a797a 100644 --- a/sources/engine/Stride.Navigation/Processors/RecastNavigationMesh.cs +++ b/sources/engine/Stride.Navigation/Processors/RecastNavigationMesh.cs @@ -86,12 +86,12 @@ public bool TryFindPath(Vector3 start, Vector3 end, ICollection path, N query.FindNearestPolyExtent = querySettings.FindNearestPolyExtent; PathFindResult queryResult = default; - queryResult.PathPoints = new List(querySettings.MaxPathPoints); + queryResult.PathPoints = new DtStraightPath[querySettings.MaxPathPoints]; navmesh.DoPathFindQuery(query, ref queryResult); if (!queryResult.PathFound) return false; - for (int i = 0; i < queryResult.PathPoints.Count; i++) + for (int i = 0; i < queryResult.PathPoints.Length; i++) { path.Add(queryResult.PathPoints[i].pos.ToStrideVector()); } From c56a314f46653ee45b7354ee43738d4bcda71ad7 Mon Sep 17 00:00:00 2001 From: Doprez <73259914+Doprez@users.noreply.github.com> Date: Mon, 5 Jan 2026 06:57:18 -0700 Subject: [PATCH 5/5] NavigationMeshAssetCompiler version bump --- .../Stride.Assets/Navigation/NavigationMeshAssetCompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/engine/Stride.Assets/Navigation/NavigationMeshAssetCompiler.cs b/sources/engine/Stride.Assets/Navigation/NavigationMeshAssetCompiler.cs index 1619501cbb..ee72ebe622 100644 --- a/sources/engine/Stride.Assets/Navigation/NavigationMeshAssetCompiler.cs +++ b/sources/engine/Stride.Assets/Navigation/NavigationMeshAssetCompiler.cs @@ -121,7 +121,7 @@ public NavmeshBuildCommand(string url, AssetItem assetItem, NavigationMeshAsset asset = value; assetUrl = url; - Version = 1; // Removed separate debug model stored in the navigation mesh + Version = 2; } protected override void ComputeParameterHash(BinarySerializationWriter writer)