From ea437ef02073e45d94c3d6dc21b9ef84432c8ff4 Mon Sep 17 00:00:00 2001
From: ikpil <ikpil0@gmail.com>
Date: Sun, 13 Oct 2024 16:27:24 +0900
Subject: [PATCH] Changed bmin/bmax from int[] to RcVec3i for improved memory
 efficiency

---
 CHANGELOG.md                                  |   1 +
 src/DotRecast.Core/Numerics/RcVec3i.cs        |   9 ++
 src/DotRecast.Detour.Extras/BVTreeBuilder.cs  |  12 +-
 src/DotRecast.Detour/BVItem.cs                |   6 +-
 src/DotRecast.Detour/BVItemXComparer.cs       |   2 +-
 src/DotRecast.Detour/BVItemYComparer.cs       |   2 +-
 src/DotRecast.Detour/BVItemZComparer.cs       |   2 +-
 src/DotRecast.Detour/DtBVNode.cs              |   6 +-
 src/DotRecast.Detour/DtNavMesh.cs             |  18 +--
 src/DotRecast.Detour/DtNavMeshBuilder.cs      | 108 ++++++++----------
 src/DotRecast.Detour/DtNavMeshQuery.cs        |  18 +--
 src/DotRecast.Detour/DtUtils.cs               |   8 +-
 src/DotRecast.Detour/Io/DtMeshDataReader.cs   |  32 +++---
 src/DotRecast.Detour/Io/DtMeshDataWriter.cs   |  32 +++---
 .../Draw/RecastDebugDraw.cs                   |  10 +-
 .../Io/MeshDataReaderWriterTest.cs            |   7 +-
 16 files changed, 132 insertions(+), 141 deletions(-)
 create mode 100644 src/DotRecast.Core/Numerics/RcVec3i.cs

diff --git a/CHANGELOG.md b/CHANGELOG.md
index febdd1e3..f9fb2160 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - 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<T> for enhanced performance, memory efficiency.
+- Changed bmin/bmax from int[] to RcVec3i for improved memory efficiency
 
 ### Removed
 - Nothing
diff --git a/src/DotRecast.Core/Numerics/RcVec3i.cs b/src/DotRecast.Core/Numerics/RcVec3i.cs
new file mode 100644
index 00000000..673edf26
--- /dev/null
+++ b/src/DotRecast.Core/Numerics/RcVec3i.cs
@@ -0,0 +1,9 @@
+namespace DotRecast.Core.Numerics
+{
+    public struct RcVec3i
+    {
+        public int X;
+        public int Y;
+        public int Z;
+    }
+}
\ No newline at end of file
diff --git a/src/DotRecast.Detour.Extras/BVTreeBuilder.cs b/src/DotRecast.Detour.Extras/BVTreeBuilder.cs
index 577effd0..c06cfe8e 100644
--- a/src/DotRecast.Detour.Extras/BVTreeBuilder.cs
+++ b/src/DotRecast.Detour.Extras/BVTreeBuilder.cs
@@ -48,12 +48,12 @@ private static int CreateBVTree(DtMeshData data, DtBVNode[] nodes, float quantFa
                     bmax = RcVec3f.Max(bmax, RcVec.Create(data.verts, data.polys[i].verts[j] * 3));
                 }
 
-                it.bmin[0] = Math.Clamp((int)((bmin.X - data.header.bmin.X) * quantFactor), 0, 0x7fffffff);
-                it.bmin[1] = Math.Clamp((int)((bmin.Y - data.header.bmin.Y) * quantFactor), 0, 0x7fffffff);
-                it.bmin[2] = Math.Clamp((int)((bmin.Z - data.header.bmin.Z) * quantFactor), 0, 0x7fffffff);
-                it.bmax[0] = Math.Clamp((int)((bmax.X - data.header.bmin.X) * quantFactor), 0, 0x7fffffff);
-                it.bmax[1] = Math.Clamp((int)((bmax.Y - data.header.bmin.Y) * quantFactor), 0, 0x7fffffff);
-                it.bmax[2] = Math.Clamp((int)((bmax.Z - data.header.bmin.Z) * quantFactor), 0, 0x7fffffff);
+                it.bmin.X = Math.Clamp((int)((bmin.X - data.header.bmin.X) * quantFactor), 0, 0x7fffffff);
+                it.bmin.Y = Math.Clamp((int)((bmin.Y - data.header.bmin.Y) * quantFactor), 0, 0x7fffffff);
+                it.bmin.Z = Math.Clamp((int)((bmin.Z - data.header.bmin.Z) * quantFactor), 0, 0x7fffffff);
+                it.bmax.X = Math.Clamp((int)((bmax.X - data.header.bmin.X) * quantFactor), 0, 0x7fffffff);
+                it.bmax.Y = Math.Clamp((int)((bmax.Y - data.header.bmin.Y) * quantFactor), 0, 0x7fffffff);
+                it.bmax.Z = Math.Clamp((int)((bmax.Z - data.header.bmin.Z) * quantFactor), 0, 0x7fffffff);
             }
 
             return DtNavMeshBuilder.Subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes);
diff --git a/src/DotRecast.Detour/BVItem.cs b/src/DotRecast.Detour/BVItem.cs
index aebbaea7..d34b7a1e 100644
--- a/src/DotRecast.Detour/BVItem.cs
+++ b/src/DotRecast.Detour/BVItem.cs
@@ -1,9 +1,11 @@
+using DotRecast.Core.Numerics;
+
 namespace DotRecast.Detour
 {
     public class BVItem
     {
-        public readonly int[] bmin = new int[3];
-        public readonly int[] bmax = new int[3];
+        public RcVec3i bmin;
+        public RcVec3i bmax;
         public int i;
     };
 }
\ No newline at end of file
diff --git a/src/DotRecast.Detour/BVItemXComparer.cs b/src/DotRecast.Detour/BVItemXComparer.cs
index c3f51ca0..3bc1565d 100644
--- a/src/DotRecast.Detour/BVItemXComparer.cs
+++ b/src/DotRecast.Detour/BVItemXComparer.cs
@@ -12,7 +12,7 @@ private BVItemXComparer()
 
         public int Compare(BVItem a, BVItem b)
         {
-            return a.bmin[0].CompareTo(b.bmin[0]);
+            return a.bmin.X.CompareTo(b.bmin.X);
         }
     }
 }
\ No newline at end of file
diff --git a/src/DotRecast.Detour/BVItemYComparer.cs b/src/DotRecast.Detour/BVItemYComparer.cs
index e52aae8d..e475e1ae 100644
--- a/src/DotRecast.Detour/BVItemYComparer.cs
+++ b/src/DotRecast.Detour/BVItemYComparer.cs
@@ -12,7 +12,7 @@ private BVItemYComparer()
 
         public int Compare(BVItem a, BVItem b)
         {
-            return a.bmin[1].CompareTo(b.bmin[1]);
+            return a.bmin.Y.CompareTo(b.bmin.Y);
         }
     }
 }
\ No newline at end of file
diff --git a/src/DotRecast.Detour/BVItemZComparer.cs b/src/DotRecast.Detour/BVItemZComparer.cs
index cd78da7c..3babe658 100644
--- a/src/DotRecast.Detour/BVItemZComparer.cs
+++ b/src/DotRecast.Detour/BVItemZComparer.cs
@@ -12,7 +12,7 @@ private BVItemZComparer()
 
         public int Compare(BVItem a, BVItem b)
         {
-            return a.bmin[2].CompareTo(b.bmin[2]);
+            return a.bmin.Z.CompareTo(b.bmin.Z);
         }
     }
 }
\ No newline at end of file
diff --git a/src/DotRecast.Detour/DtBVNode.cs b/src/DotRecast.Detour/DtBVNode.cs
index 97681dda..dc57d5ea 100644
--- a/src/DotRecast.Detour/DtBVNode.cs
+++ b/src/DotRecast.Detour/DtBVNode.cs
@@ -18,6 +18,8 @@ misrepresented as being the original software.
 3. This notice may not be removed or altered from any source distribution.
 */
 
+using DotRecast.Core.Numerics;
+
 namespace DotRecast.Detour
 {
     /// Bounding volume node.
@@ -25,8 +27,8 @@ namespace DotRecast.Detour
     /// @see dtMeshTile
     public class DtBVNode
     {
-        public int[] bmin = new int[3]; //< Minimum bounds of the node's AABB. [(x, y, z)]
-        public int[] bmax = new int[3]; //< Maximum bounds of the node's AABB. [(x, y, z)]
+        public RcVec3i bmin; //< Minimum bounds of the node's AABB. [(x, y, z)]
+        public RcVec3i bmax; //< Maximum bounds of the node's AABB. [(x, y, z)]
         public int i; //< The node's index. (Negative for escape sequence.)
     }
 }
\ No newline at end of file
diff --git a/src/DotRecast.Detour/DtNavMesh.cs b/src/DotRecast.Detour/DtNavMesh.cs
index d31cbac0..a4791238 100644
--- a/src/DotRecast.Detour/DtNavMesh.cs
+++ b/src/DotRecast.Detour/DtNavMesh.cs
@@ -259,8 +259,8 @@ List<long> QueryPolygonsInTile(DtMeshTile tile, RcVec3f qmin, RcVec3f qmax)
                 var tbmax = tile.data.header.bmax;
                 float qfac = tile.data.header.bvQuantFactor;
                 // Calculate quantized box
-                Span<int> bmin = stackalloc int[3];
-                Span<int> bmax = stackalloc int[3];
+                RcVec3i bmin;
+                RcVec3i bmax;
                 // dtClamp query box to world box.
                 float minx = Math.Clamp(qmin.X, tbmin.X, tbmax.X) - tbmin.X;
                 float miny = Math.Clamp(qmin.Y, tbmin.Y, tbmax.Y) - tbmin.Y;
@@ -269,12 +269,12 @@ List<long> QueryPolygonsInTile(DtMeshTile tile, RcVec3f qmin, RcVec3f qmax)
                 float maxy = Math.Clamp(qmax.Y, tbmin.Y, tbmax.Y) - tbmin.Y;
                 float maxz = Math.Clamp(qmax.Z, tbmin.Z, tbmax.Z) - tbmin.Z;
                 // Quantize
-                bmin[0] = (int)(qfac * minx) & 0x7ffffffe;
-                bmin[1] = (int)(qfac * miny) & 0x7ffffffe;
-                bmin[2] = (int)(qfac * minz) & 0x7ffffffe;
-                bmax[0] = (int)(qfac * maxx + 1) | 1;
-                bmax[1] = (int)(qfac * maxy + 1) | 1;
-                bmax[2] = (int)(qfac * maxz + 1) | 1;
+                bmin.X = (int)(qfac * minx) & 0x7ffffffe;
+                bmin.Y = (int)(qfac * miny) & 0x7ffffffe;
+                bmin.Z = (int)(qfac * minz) & 0x7ffffffe;
+                bmax.X = (int)(qfac * maxx + 1) | 1;
+                bmax.Y = (int)(qfac * maxy + 1) | 1;
+                bmax.Z = (int)(qfac * maxz + 1) | 1;
 
                 // Traverse tree
                 long @base = GetPolyRefBase(tile);
@@ -282,7 +282,7 @@ List<long> QueryPolygonsInTile(DtMeshTile tile, RcVec3f qmin, RcVec3f qmax)
                 while (nodeIndex < end)
                 {
                     DtBVNode node = tile.data.bvTree[nodeIndex];
-                    bool overlap = DtUtils.OverlapQuantBounds(bmin, bmax, node.bmin, node.bmax);
+                    bool overlap = DtUtils.OverlapQuantBounds(ref bmin, ref bmax, ref node.bmin, ref node.bmax);
                     bool isLeafNode = node.i >= 0;
 
                     if (isLeafNode && overlap)
diff --git a/src/DotRecast.Detour/DtNavMeshBuilder.cs b/src/DotRecast.Detour/DtNavMeshBuilder.cs
index 632d89a2..7f5c863f 100644
--- a/src/DotRecast.Detour/DtNavMeshBuilder.cs
+++ b/src/DotRecast.Detour/DtNavMeshBuilder.cs
@@ -31,37 +31,28 @@ public static class DtNavMeshBuilder
         const int MESH_NULL_IDX = 0xffff;
 
 
-        private static int[][] CalcExtends(BVItem[] items, int nitems, int imin, int imax)
+        private static void CalcExtends(BVItem[] items, int nitems, int imin, int imax, ref RcVec3i bmin, ref RcVec3i bmax)
         {
-            int[] bmin = new int[3];
-            int[] bmax = new int[3];
-            bmin[0] = items[imin].bmin[0];
-            bmin[1] = items[imin].bmin[1];
-            bmin[2] = items[imin].bmin[2];
-
-            bmax[0] = items[imin].bmax[0];
-            bmax[1] = items[imin].bmax[1];
-            bmax[2] = items[imin].bmax[2];
+            bmin = items[imin].bmin;
+            bmax = items[imin].bmax;
 
             for (int i = imin + 1; i < imax; ++i)
             {
                 BVItem it = items[i];
-                if (it.bmin[0] < bmin[0])
-                    bmin[0] = it.bmin[0];
-                if (it.bmin[1] < bmin[1])
-                    bmin[1] = it.bmin[1];
-                if (it.bmin[2] < bmin[2])
-                    bmin[2] = it.bmin[2];
-
-                if (it.bmax[0] > bmax[0])
-                    bmax[0] = it.bmax[0];
-                if (it.bmax[1] > bmax[1])
-                    bmax[1] = it.bmax[1];
-                if (it.bmax[2] > bmax[2])
-                    bmax[2] = it.bmax[2];
+                if (it.bmin.X < bmin.X)
+                    bmin.X = it.bmin.X;
+                if (it.bmin.Y < bmin.Y)
+                    bmin.Y = it.bmin.Y;
+                if (it.bmin.Z < bmin.Z)
+                    bmin.Z = it.bmin.Z;
+
+                if (it.bmax.X > bmax.X)
+                    bmax.X = it.bmax.X;
+                if (it.bmax.Y > bmax.Y)
+                    bmax.Y = it.bmax.Y;
+                if (it.bmax.Z > bmax.Z)
+                    bmax.Z = it.bmax.Z;
             }
-
-            return new int[][] { bmin, bmax };
         }
 
         private static int LongestAxis(int x, int y, int z)
@@ -94,27 +85,20 @@ public static int Subdivide(BVItem[] items, int nitems, int imin, int imax, int
             if (inum == 1)
             {
                 // Leaf
-                node.bmin[0] = items[imin].bmin[0];
-                node.bmin[1] = items[imin].bmin[1];
-                node.bmin[2] = items[imin].bmin[2];
-
-                node.bmax[0] = items[imin].bmax[0];
-                node.bmax[1] = items[imin].bmax[1];
-                node.bmax[2] = items[imin].bmax[2];
+                node.bmin = items[imin].bmin;
+                node.bmax = items[imin].bmax;
 
                 node.i = items[imin].i;
             }
             else
             {
                 // Split
-                int[][] minmax = CalcExtends(items, nitems, imin, imax);
-                node.bmin = minmax[0];
-                node.bmax = minmax[1];
+                CalcExtends(items, nitems, imin, imax, ref node.bmin, ref node.bmax);
 
                 int axis = LongestAxis(
-                    node.bmax[0] - node.bmin[0],
-                    node.bmax[1] - node.bmin[1],
-                    node.bmax[2] - node.bmin[2]
+                    node.bmax.X - node.bmin.X,
+                    node.bmax.Y - node.bmin.Y,
+                    node.bmax.Z - node.bmin.Z
                 );
 
                 if (axis == 0)
@@ -173,20 +157,20 @@ private static int CreateBVTree(DtNavMeshCreateParams option, DtBVNode[] nodes)
                     }
 
                     // BV-tree uses cs for all dimensions
-                    it.bmin[0] = Math.Clamp((int)((bmin.X - option.bmin.X) * quantFactor), 0, int.MaxValue);
-                    it.bmin[1] = Math.Clamp((int)((bmin.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue);
-                    it.bmin[2] = Math.Clamp((int)((bmin.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue);
+                    it.bmin.X = Math.Clamp((int)((bmin.X - option.bmin.X) * quantFactor), 0, int.MaxValue);
+                    it.bmin.Y = Math.Clamp((int)((bmin.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue);
+                    it.bmin.Z = Math.Clamp((int)((bmin.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue);
 
-                    it.bmax[0] = Math.Clamp((int)((bmax.X - option.bmin.X) * quantFactor), 0, int.MaxValue);
-                    it.bmax[1] = Math.Clamp((int)((bmax.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue);
-                    it.bmax[2] = Math.Clamp((int)((bmax.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue);
+                    it.bmax.X = Math.Clamp((int)((bmax.X - option.bmin.X) * quantFactor), 0, int.MaxValue);
+                    it.bmax.Y = Math.Clamp((int)((bmax.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue);
+                    it.bmax.Z = Math.Clamp((int)((bmax.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue);
                 }
                 else
                 {
                     int p = i * option.nvp * 2;
-                    it.bmin[0] = it.bmax[0] = option.verts[option.polys[p] * 3 + 0];
-                    it.bmin[1] = it.bmax[1] = option.verts[option.polys[p] * 3 + 1];
-                    it.bmin[2] = it.bmax[2] = option.verts[option.polys[p] * 3 + 2];
+                    it.bmin.X = it.bmax.X = option.verts[option.polys[p] * 3 + 0];
+                    it.bmin.Y = it.bmax.Y = option.verts[option.polys[p] * 3 + 1];
+                    it.bmin.Z = it.bmax.Z = option.verts[option.polys[p] * 3 + 2];
 
                     for (int j = 1; j < option.nvp; ++j)
                     {
@@ -196,24 +180,24 @@ private static int CreateBVTree(DtNavMeshCreateParams option, DtBVNode[] nodes)
                         int y = option.verts[option.polys[p + j] * 3 + 1];
                         int z = option.verts[option.polys[p + j] * 3 + 2];
 
-                        if (x < it.bmin[0])
-                            it.bmin[0] = x;
-                        if (y < it.bmin[1])
-                            it.bmin[1] = y;
-                        if (z < it.bmin[2])
-                            it.bmin[2] = z;
-
-                        if (x > it.bmax[0])
-                            it.bmax[0] = x;
-                        if (y > it.bmax[1])
-                            it.bmax[1] = y;
-                        if (z > it.bmax[2])
-                            it.bmax[2] = z;
+                        if (x < it.bmin.X)
+                            it.bmin.X = x;
+                        if (y < it.bmin.Y)
+                            it.bmin.Y = y;
+                        if (z < it.bmin.Z)
+                            it.bmin.Z = z;
+
+                        if (x > it.bmax.X)
+                            it.bmax.X = x;
+                        if (y > it.bmax.Y)
+                            it.bmax.Y = y;
+                        if (z > it.bmax.Z)
+                            it.bmax.Z = z;
                     }
 
                     // Remap y
-                    it.bmin[1] = (int)MathF.Floor(it.bmin[1] * option.ch * quantFactor);
-                    it.bmax[1] = (int)MathF.Ceiling(it.bmax[1] * option.ch * quantFactor);
+                    it.bmin.Y = (int)MathF.Floor(it.bmin.Y * option.ch * quantFactor);
+                    it.bmax.Y = (int)MathF.Ceiling(it.bmax.Y * option.ch * quantFactor);
                 }
             }
 
diff --git a/src/DotRecast.Detour/DtNavMeshQuery.cs b/src/DotRecast.Detour/DtNavMeshQuery.cs
index 0a83738d..ee0e699a 100644
--- a/src/DotRecast.Detour/DtNavMeshQuery.cs
+++ b/src/DotRecast.Detour/DtNavMeshQuery.cs
@@ -609,8 +609,8 @@ protected void QueryPolygonsInTile(DtMeshTile tile, RcVec3f qmin, RcVec3f qmax,
                 float qfac = tile.data.header.bvQuantFactor;
 
                 // Calculate quantized box
-                Span<int> bmin = stackalloc int[3];
-                Span<int> bmax = stackalloc int[3];
+                RcVec3i bmin;
+                RcVec3i bmax;
                 // dtClamp query box to world box.
                 float minx = Math.Clamp(qmin.X, tbmin.X, tbmax.X) - tbmin.X;
                 float miny = Math.Clamp(qmin.Y, tbmin.Y, tbmax.Y) - tbmin.Y;
@@ -619,19 +619,19 @@ protected void QueryPolygonsInTile(DtMeshTile tile, RcVec3f qmin, RcVec3f qmax,
                 float maxy = Math.Clamp(qmax.Y, tbmin.Y, tbmax.Y) - tbmin.Y;
                 float maxz = Math.Clamp(qmax.Z, tbmin.Z, tbmax.Z) - tbmin.Z;
                 // Quantize
-                bmin[0] = (int)(qfac * minx) & 0x7ffffffe;
-                bmin[1] = (int)(qfac * miny) & 0x7ffffffe;
-                bmin[2] = (int)(qfac * minz) & 0x7ffffffe;
-                bmax[0] = (int)(qfac * maxx + 1) | 1;
-                bmax[1] = (int)(qfac * maxy + 1) | 1;
-                bmax[2] = (int)(qfac * maxz + 1) | 1;
+                bmin.X = (int)(qfac * minx) & 0x7ffffffe;
+                bmin.Y = (int)(qfac * miny) & 0x7ffffffe;
+                bmin.Z = (int)(qfac * minz) & 0x7ffffffe;
+                bmax.X = (int)(qfac * maxx + 1) | 1;
+                bmax.Y = (int)(qfac * maxy + 1) | 1;
+                bmax.Z = (int)(qfac * maxz + 1) | 1;
 
                 // Traverse tree
                 long @base = m_nav.GetPolyRefBase(tile);
                 while (nodeIndex < end)
                 {
                     DtBVNode node = tile.data.bvTree[nodeIndex];
-                    bool overlap = DtUtils.OverlapQuantBounds(bmin, bmax, node.bmin, node.bmax);
+                    bool overlap = DtUtils.OverlapQuantBounds(ref bmin, ref bmax, ref node.bmin, ref node.bmax);
                     bool isLeafNode = node.i >= 0;
 
                     if (isLeafNode && overlap)
diff --git a/src/DotRecast.Detour/DtUtils.cs b/src/DotRecast.Detour/DtUtils.cs
index 9b65bf79..14fc2919 100644
--- a/src/DotRecast.Detour/DtUtils.cs
+++ b/src/DotRecast.Detour/DtUtils.cs
@@ -43,12 +43,12 @@ public static int Ilog2(int v)
         /// @param[in] bmax Maximum bounds of box B. [(x, y, z)]
         /// @return True if the two AABB's overlap.
         /// @see dtOverlapBounds
-        public static bool OverlapQuantBounds(Span<int> amin, Span<int> amax, Span<int> bmin, Span<int> bmax)
+        public static bool OverlapQuantBounds(ref RcVec3i amin, ref RcVec3i amax, ref RcVec3i bmin, ref RcVec3i bmax)
         {
             bool overlap = true;
-            overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
-            overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
-            overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
+            overlap = (amin.X > bmax.X || amax.X < bmin.X) ? false : overlap;
+            overlap = (amin.Y > bmax.Y || amax.Y < bmin.Y) ? false : overlap;
+            overlap = (amin.Z > bmax.Z || amax.Z < bmin.Z) ? false : overlap;
             return overlap;
         }
 
diff --git a/src/DotRecast.Detour/Io/DtMeshDataReader.cs b/src/DotRecast.Detour/Io/DtMeshDataReader.cs
index 7c837ede..0afe1619 100644
--- a/src/DotRecast.Detour/Io/DtMeshDataReader.cs
+++ b/src/DotRecast.Detour/Io/DtMeshDataReader.cs
@@ -204,27 +204,23 @@ private DtBVNode[] ReadBVTree(RcByteBuffer buf, DtMeshHeader header)
                 nodes[i] = new DtBVNode();
                 if (header.version < DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE)
                 {
-                    for (int j = 0; j < 3; j++)
-                    {
-                        nodes[i].bmin[j] = buf.GetShort() & 0xFFFF;
-                    }
-
-                    for (int j = 0; j < 3; j++)
-                    {
-                        nodes[i].bmax[j] = buf.GetShort() & 0xFFFF;
-                    }
+                    nodes[i].bmin.X = buf.GetShort() & 0xFFFF;
+                    nodes[i].bmin.Y = buf.GetShort() & 0xFFFF;
+                    nodes[i].bmin.Z = buf.GetShort() & 0xFFFF;
+
+                    nodes[i].bmax.X = buf.GetShort() & 0xFFFF;
+                    nodes[i].bmax.Y = buf.GetShort() & 0xFFFF;
+                    nodes[i].bmax.Z = buf.GetShort() & 0xFFFF;
                 }
                 else
                 {
-                    for (int j = 0; j < 3; j++)
-                    {
-                        nodes[i].bmin[j] = buf.GetInt();
-                    }
-
-                    for (int j = 0; j < 3; j++)
-                    {
-                        nodes[i].bmax[j] = buf.GetInt();
-                    }
+                    nodes[i].bmin.X = buf.GetInt();
+                    nodes[i].bmin.Y = buf.GetInt();
+                    nodes[i].bmin.Z = buf.GetInt();
+
+                    nodes[i].bmax.X = buf.GetInt();
+                    nodes[i].bmax.Y = buf.GetInt();
+                    nodes[i].bmax.Z = buf.GetInt();
                 }
 
                 nodes[i].i = buf.GetInt();
diff --git a/src/DotRecast.Detour/Io/DtMeshDataWriter.cs b/src/DotRecast.Detour/Io/DtMeshDataWriter.cs
index fa70e7bb..75ab3c72 100644
--- a/src/DotRecast.Detour/Io/DtMeshDataWriter.cs
+++ b/src/DotRecast.Detour/Io/DtMeshDataWriter.cs
@@ -130,27 +130,23 @@ private void WriteBVTree(BinaryWriter stream, DtMeshData data, RcByteOrder order
             {
                 if (cCompatibility)
                 {
-                    for (int j = 0; j < 3; j++)
-                    {
-                        RcIO.Write(stream, (short)data.bvTree[i].bmin[j], order);
-                    }
-
-                    for (int j = 0; j < 3; j++)
-                    {
-                        RcIO.Write(stream, (short)data.bvTree[i].bmax[j], order);
-                    }
+                    RcIO.Write(stream, (short)data.bvTree[i].bmin.X, order);
+                    RcIO.Write(stream, (short)data.bvTree[i].bmin.Y, order);
+                    RcIO.Write(stream, (short)data.bvTree[i].bmin.Z, order);
+
+                    RcIO.Write(stream, (short)data.bvTree[i].bmax.X, order);
+                    RcIO.Write(stream, (short)data.bvTree[i].bmax.Y, order);
+                    RcIO.Write(stream, (short)data.bvTree[i].bmax.Z, order);
                 }
                 else
                 {
-                    for (int j = 0; j < 3; j++)
-                    {
-                        RcIO.Write(stream, data.bvTree[i].bmin[j], order);
-                    }
-
-                    for (int j = 0; j < 3; j++)
-                    {
-                        RcIO.Write(stream, data.bvTree[i].bmax[j], order);
-                    }
+                    RcIO.Write(stream, data.bvTree[i].bmin.X, order);
+                    RcIO.Write(stream, data.bvTree[i].bmin.Y, order);
+                    RcIO.Write(stream, data.bvTree[i].bmin.Z, order);
+
+                    RcIO.Write(stream, data.bvTree[i].bmax.X, order);
+                    RcIO.Write(stream, data.bvTree[i].bmax.Y, order);
+                    RcIO.Write(stream, data.bvTree[i].bmax.Z, order);
                 }
 
                 RcIO.Write(stream, data.bvTree[i].i, order);
diff --git a/src/DotRecast.Recast.Demo/Draw/RecastDebugDraw.cs b/src/DotRecast.Recast.Demo/Draw/RecastDebugDraw.cs
index d26b7d36..b20fdac7 100644
--- a/src/DotRecast.Recast.Demo/Draw/RecastDebugDraw.cs
+++ b/src/DotRecast.Recast.Demo/Draw/RecastDebugDraw.cs
@@ -465,9 +465,13 @@ private void DrawMeshTileBVTree(DtMeshTile tile)
                 continue;
             }
 
-            AppendBoxWire(tile.data.header.bmin.X + n.bmin[0] * cs, tile.data.header.bmin.Y + n.bmin[1] * cs,
-                tile.data.header.bmin.Z + n.bmin[2] * cs, tile.data.header.bmin.X + n.bmax[0] * cs,
-                tile.data.header.bmin.Y + n.bmax[1] * cs, tile.data.header.bmin.Z + n.bmax[2] * cs,
+            AppendBoxWire(
+                tile.data.header.bmin.X + n.bmin.X * cs, 
+                tile.data.header.bmin.Y + n.bmin.Y * cs,
+                tile.data.header.bmin.Z + n.bmin.Z * cs, 
+                tile.data.header.bmin.X + n.bmax.X * cs,
+                tile.data.header.bmin.Y + n.bmax.Y * cs, 
+                tile.data.header.bmin.Z + n.bmax.Z * cs,
                 DuRGBA(255, 255, 255, 128));
         }
 
diff --git a/test/DotRecast.Detour.Test/Io/MeshDataReaderWriterTest.cs b/test/DotRecast.Detour.Test/Io/MeshDataReaderWriterTest.cs
index c5872bad..2cdd4ed0 100644
--- a/test/DotRecast.Detour.Test/Io/MeshDataReaderWriterTest.cs
+++ b/test/DotRecast.Detour.Test/Io/MeshDataReaderWriterTest.cs
@@ -117,11 +117,8 @@ public void Test(bool cCompatibility, RcByteOrder order)
         for (int i = 0; i < meshData.header.bvNodeCount; i++)
         {
             Assert.That(readData.bvTree[i].i, Is.EqualTo(meshData.bvTree[i].i));
-            for (int j = 0; j < 3; j++)
-            {
-                Assert.That(readData.bvTree[i].bmin[j], Is.EqualTo(meshData.bvTree[i].bmin[j]));
-                Assert.That(readData.bvTree[i].bmax[j], Is.EqualTo(meshData.bvTree[i].bmax[j]));
-            }
+            Assert.That(readData.bvTree[i].bmin, Is.EqualTo(meshData.bvTree[i].bmin));
+            Assert.That(readData.bvTree[i].bmax, Is.EqualTo(meshData.bvTree[i].bmax));
         }
 
         for (int i = 0; i < meshData.header.offMeshConCount; i++)