BBTree.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. //#define ASTARDEBUG //"BBTree Debug" If enables, some queries to the tree will show debug lines. Turn off multithreading when using this since DrawLine calls cannot be called from a different thread
  2. using System;
  3. using UnityEngine;
  4. namespace Pathfinding {
  5. using Pathfinding.Util;
  6. /// <summary>
  7. /// Axis Aligned Bounding Box Tree.
  8. /// Holds a bounding box tree of triangles.
  9. /// </summary>
  10. public class BBTree : IAstarPooledObject {
  11. /// <summary>Holds all tree nodes</summary>
  12. BBTreeBox[] tree = null;
  13. TriangleMeshNode[] nodeLookup = null;
  14. int count;
  15. int leafNodes;
  16. const int MaximumLeafSize = 4;
  17. public Rect Size {
  18. get {
  19. if (count == 0) {
  20. return new Rect(0, 0, 0, 0);
  21. } else {
  22. var rect = tree[0].rect;
  23. return Rect.MinMaxRect(rect.xmin*Int3.PrecisionFactor, rect.ymin*Int3.PrecisionFactor, rect.xmax*Int3.PrecisionFactor, rect.ymax*Int3.PrecisionFactor);
  24. }
  25. }
  26. }
  27. /// <summary>
  28. /// Clear the tree.
  29. /// Note that references to old nodes will still be intact so the GC cannot immediately collect them.
  30. /// </summary>
  31. public void Clear () {
  32. count = 0;
  33. leafNodes = 0;
  34. if (tree != null) ArrayPool<BBTreeBox>.Release(ref tree);
  35. if (nodeLookup != null) {
  36. // Prevent memory leaks as the pool does not clear the array
  37. for (int i = 0; i < nodeLookup.Length; i++) nodeLookup[i] = null;
  38. ArrayPool<TriangleMeshNode>.Release(ref nodeLookup);
  39. }
  40. tree = ArrayPool<BBTreeBox>.Claim(0);
  41. nodeLookup = ArrayPool<TriangleMeshNode>.Claim(0);
  42. }
  43. void IAstarPooledObject.OnEnterPool () {
  44. Clear();
  45. }
  46. void EnsureCapacity (int c) {
  47. if (c > tree.Length) {
  48. var newArr = ArrayPool<BBTreeBox>.Claim(c);
  49. tree.CopyTo(newArr, 0);
  50. ArrayPool<BBTreeBox>.Release(ref tree);
  51. tree = newArr;
  52. }
  53. }
  54. void EnsureNodeCapacity (int c) {
  55. if (c > nodeLookup.Length) {
  56. var newArr = ArrayPool<TriangleMeshNode>.Claim(c);
  57. nodeLookup.CopyTo(newArr, 0);
  58. ArrayPool<TriangleMeshNode>.Release(ref nodeLookup);
  59. nodeLookup = newArr;
  60. }
  61. }
  62. int GetBox (IntRect rect) {
  63. if (count >= tree.Length) EnsureCapacity(count+1);
  64. tree[count] = new BBTreeBox(rect);
  65. count++;
  66. return count-1;
  67. }
  68. /// <summary>Rebuilds the tree using the specified nodes</summary>
  69. public void RebuildFrom (TriangleMeshNode[] nodes) {
  70. Clear();
  71. if (nodes.Length == 0) return;
  72. // We will use approximately 2N tree nodes
  73. EnsureCapacity(Mathf.CeilToInt(nodes.Length * 2.1f));
  74. // We will use approximately N node references
  75. EnsureNodeCapacity(Mathf.CeilToInt(nodes.Length * 1.1f));
  76. // This will store the order of the nodes while the tree is being built
  77. // It turns out that it is a lot faster to do this than to actually modify
  78. // the nodes and nodeBounds arrays (presumably since that involves shuffling
  79. // around 20 bytes of memory (sizeof(pointer) + sizeof(IntRect)) per node
  80. // instead of 4 bytes (sizeof(int)).
  81. // It also means we don't have to make a copy of the nodes array since
  82. // we do not modify it
  83. var permutation = ArrayPool<int>.Claim(nodes.Length);
  84. for (int i = 0; i < nodes.Length; i++) {
  85. permutation[i] = i;
  86. }
  87. // Precalculate the bounds of the nodes in XZ space.
  88. // It turns out that calculating the bounds is a bottleneck and precalculating
  89. // the bounds makes it around 3 times faster to build a tree
  90. var nodeBounds = ArrayPool<IntRect>.Claim(nodes.Length);
  91. for (int i = 0; i < nodes.Length; i++) {
  92. Int3 v0, v1, v2;
  93. nodes[i].GetVertices(out v0, out v1, out v2);
  94. var rect = new IntRect(v0.x, v0.z, v0.x, v0.z);
  95. rect = rect.ExpandToContain(v1.x, v1.z);
  96. rect = rect.ExpandToContain(v2.x, v2.z);
  97. nodeBounds[i] = rect;
  98. }
  99. RebuildFromInternal(nodes, permutation, nodeBounds, 0, nodes.Length, false);
  100. ArrayPool<int>.Release(ref permutation);
  101. ArrayPool<IntRect>.Release(ref nodeBounds);
  102. }
  103. static int SplitByX (TriangleMeshNode[] nodes, int[] permutation, int from, int to, int divider) {
  104. int mx = to;
  105. for (int i = from; i < mx; i++) {
  106. if (nodes[permutation[i]].position.x > divider) {
  107. mx--;
  108. // Swap items i and mx
  109. var tmp = permutation[mx];
  110. permutation[mx] = permutation[i];
  111. permutation[i] = tmp;
  112. i--;
  113. }
  114. }
  115. return mx;
  116. }
  117. static int SplitByZ (TriangleMeshNode[] nodes, int[] permutation, int from, int to, int divider) {
  118. int mx = to;
  119. for (int i = from; i < mx; i++) {
  120. if (nodes[permutation[i]].position.z > divider) {
  121. mx--;
  122. // Swap items i and mx
  123. var tmp = permutation[mx];
  124. permutation[mx] = permutation[i];
  125. permutation[i] = tmp;
  126. i--;
  127. }
  128. }
  129. return mx;
  130. }
  131. int RebuildFromInternal (TriangleMeshNode[] nodes, int[] permutation, IntRect[] nodeBounds, int from, int to, bool odd) {
  132. var rect = NodeBounds(permutation, nodeBounds, from, to);
  133. int box = GetBox(rect);
  134. if (to - from <= MaximumLeafSize) {
  135. var nodeOffset = tree[box].nodeOffset = leafNodes*MaximumLeafSize;
  136. EnsureNodeCapacity(nodeOffset + MaximumLeafSize);
  137. leafNodes++;
  138. // Assign all nodes to the array. Note that we also need clear unused slots as the array from the pool may contain any information
  139. for (int i = 0; i < MaximumLeafSize; i++) {
  140. nodeLookup[nodeOffset + i] = i < to - from ? nodes[permutation[from + i]] : null;
  141. }
  142. return box;
  143. }
  144. int splitIndex;
  145. if (odd) {
  146. // X
  147. int divider = (rect.xmin + rect.xmax)/2;
  148. splitIndex = SplitByX(nodes, permutation, from, to, divider);
  149. } else {
  150. // Y/Z
  151. int divider = (rect.ymin + rect.ymax)/2;
  152. splitIndex = SplitByZ(nodes, permutation, from, to, divider);
  153. }
  154. if (splitIndex == from || splitIndex == to) {
  155. // All nodes were on one side of the divider
  156. // Try to split along the other axis
  157. if (!odd) {
  158. // X
  159. int divider = (rect.xmin + rect.xmax)/2;
  160. splitIndex = SplitByX(nodes, permutation, from, to, divider);
  161. } else {
  162. // Y/Z
  163. int divider = (rect.ymin + rect.ymax)/2;
  164. splitIndex = SplitByZ(nodes, permutation, from, to, divider);
  165. }
  166. if (splitIndex == from || splitIndex == to) {
  167. // All nodes were on one side of the divider
  168. // Just pick one half
  169. splitIndex = (from+to)/2;
  170. }
  171. }
  172. tree[box].left = RebuildFromInternal(nodes, permutation, nodeBounds, from, splitIndex, !odd);
  173. tree[box].right = RebuildFromInternal(nodes, permutation, nodeBounds, splitIndex, to, !odd);
  174. return box;
  175. }
  176. /// <summary>Calculates the bounding box in XZ space of all nodes between from (inclusive) and to (exclusive)</summary>
  177. static IntRect NodeBounds (int[] permutation, IntRect[] nodeBounds, int from, int to) {
  178. var rect = nodeBounds[permutation[from]];
  179. for (int j = from + 1; j < to; j++) {
  180. var otherRect = nodeBounds[permutation[j]];
  181. // Equivalent to rect = IntRect.Union(rect, otherRect)
  182. // but manually inlining is approximately
  183. // 25% faster when building an entire tree.
  184. // This code is hot when using navmesh cutting.
  185. rect.xmin = Math.Min(rect.xmin, otherRect.xmin);
  186. rect.ymin = Math.Min(rect.ymin, otherRect.ymin);
  187. rect.xmax = Math.Max(rect.xmax, otherRect.xmax);
  188. rect.ymax = Math.Max(rect.ymax, otherRect.ymax);
  189. }
  190. return rect;
  191. }
  192. [System.Diagnostics.Conditional("ASTARDEBUG")]
  193. static void DrawDebugRect (IntRect rect) {
  194. Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymin), new Vector3(rect.xmax, 0, rect.ymin), Color.white);
  195. Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymax), new Vector3(rect.xmax, 0, rect.ymax), Color.white);
  196. Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymin), new Vector3(rect.xmin, 0, rect.ymax), Color.white);
  197. Debug.DrawLine(new Vector3(rect.xmax, 0, rect.ymin), new Vector3(rect.xmax, 0, rect.ymax), Color.white);
  198. }
  199. [System.Diagnostics.Conditional("ASTARDEBUG")]
  200. static void DrawDebugNode (TriangleMeshNode node, float yoffset, Color color) {
  201. Debug.DrawLine((Vector3)node.GetVertex(1) + Vector3.up*yoffset, (Vector3)node.GetVertex(2) + Vector3.up*yoffset, color);
  202. Debug.DrawLine((Vector3)node.GetVertex(0) + Vector3.up*yoffset, (Vector3)node.GetVertex(1) + Vector3.up*yoffset, color);
  203. Debug.DrawLine((Vector3)node.GetVertex(2) + Vector3.up*yoffset, (Vector3)node.GetVertex(0) + Vector3.up*yoffset, color);
  204. }
  205. /// <summary>
  206. /// Queries the tree for the closest node to p constrained by the NNConstraint.
  207. /// Note that this function will only fill in the constrained node.
  208. /// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
  209. /// </summary>
  210. public NNInfoInternal QueryClosest (Vector3 p, NNConstraint constraint, out float distance) {
  211. distance = float.PositiveInfinity;
  212. return QueryClosest(p, constraint, ref distance, new NNInfoInternal(null));
  213. }
  214. /// <summary>
  215. /// Queries the tree for the closest node to p constrained by the NNConstraint trying to improve an existing solution.
  216. /// Note that this function will only fill in the constrained node.
  217. /// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
  218. ///
  219. /// This method will completely ignore any Y-axis differences in positions.
  220. /// </summary>
  221. /// <param name="p">Point to search around</param>
  222. /// <param name="constraint">Optionally set to constrain which nodes to return</param>
  223. /// <param name="distance">The best distance for the previous solution. Will be updated with the best distance
  224. /// after this search. Will be positive infinity if no node could be found.
  225. /// Set to positive infinity if there was no previous solution.</param>
  226. /// <param name="previous">This search will start from the previous NNInfo and improve it if possible.
  227. /// Even if the search fails on this call, the solution will never be worse than previous.
  228. /// Note that the distance parameter need to be configured with the distance for the previous result
  229. /// otherwise it may get overwritten even though it was actually closer.</param>
  230. public NNInfoInternal QueryClosestXZ (Vector3 p, NNConstraint constraint, ref float distance, NNInfoInternal previous) {
  231. var sqrDistance = distance*distance;
  232. var origSqrDistance = sqrDistance;
  233. if (count > 0 && SquaredRectPointDistance(tree[0].rect, p) < sqrDistance) {
  234. SearchBoxClosestXZ(0, p, ref sqrDistance, constraint, ref previous);
  235. // Only update the distance if the squared distance changed as otherwise #distance
  236. // might change due to rounding errors even if no better solution was found
  237. if (sqrDistance < origSqrDistance) distance = Mathf.Sqrt(sqrDistance);
  238. }
  239. return previous;
  240. }
  241. void SearchBoxClosestXZ (int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) {
  242. BBTreeBox box = tree[boxi];
  243. if (box.IsLeaf) {
  244. var nodes = nodeLookup;
  245. for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
  246. var node = nodes[box.nodeOffset+i];
  247. // Update the NNInfo
  248. DrawDebugNode(node, 0.2f, Color.red);
  249. if (constraint == null || constraint.Suitable(node)) {
  250. Vector3 closest = node.ClosestPointOnNodeXZ(p);
  251. // XZ squared distance
  252. float dist = (closest.x-p.x)*(closest.x-p.x)+(closest.z-p.z)*(closest.z-p.z);
  253. // There's a theoretical case when the closest point is on the edge of a node which may cause the
  254. // closest point's xz coordinates to not line up perfectly with p's xz coordinates even though they should
  255. // (because floating point errors are annoying). So use a tiny margin to cover most of those cases.
  256. const float fuzziness = 0.000001f;
  257. if (nnInfo.constrainedNode == null || dist < closestSqrDist - fuzziness || (dist <= closestSqrDist + fuzziness && Mathf.Abs(closest.y - p.y) < Mathf.Abs(nnInfo.constClampedPosition.y - p.y))) {
  258. nnInfo.constrainedNode = node;
  259. nnInfo.constClampedPosition = closest;
  260. closestSqrDist = dist;
  261. }
  262. }
  263. }
  264. } else {
  265. DrawDebugRect(box.rect);
  266. int first = box.left, second = box.right;
  267. float firstDist, secondDist;
  268. GetOrderedChildren(ref first, ref second, out firstDist, out secondDist, p);
  269. // Search children (closest box first to improve performance)
  270. if (firstDist <= closestSqrDist) {
  271. SearchBoxClosestXZ(first, p, ref closestSqrDist, constraint, ref nnInfo);
  272. }
  273. if (secondDist <= closestSqrDist) {
  274. SearchBoxClosestXZ(second, p, ref closestSqrDist, constraint, ref nnInfo);
  275. }
  276. }
  277. }
  278. /// <summary>
  279. /// Queries the tree for the closest node to p constrained by the NNConstraint trying to improve an existing solution.
  280. /// Note that this function will only fill in the constrained node.
  281. /// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
  282. /// </summary>
  283. /// <param name="p">Point to search around</param>
  284. /// <param name="constraint">Optionally set to constrain which nodes to return</param>
  285. /// <param name="distance">The best distance for the previous solution. Will be updated with the best distance
  286. /// after this search. Will be positive infinity if no node could be found.
  287. /// Set to positive infinity if there was no previous solution.</param>
  288. /// <param name="previous">This search will start from the previous NNInfo and improve it if possible.
  289. /// Even if the search fails on this call, the solution will never be worse than previous.</param>
  290. public NNInfoInternal QueryClosest (Vector3 p, NNConstraint constraint, ref float distance, NNInfoInternal previous) {
  291. var sqrDistance = distance*distance;
  292. var origSqrDistance = sqrDistance;
  293. if (count > 0 && SquaredRectPointDistance(tree[0].rect, p) < sqrDistance) {
  294. SearchBoxClosest(0, p, ref sqrDistance, constraint, ref previous);
  295. // Only update the distance if the squared distance changed as otherwise #distance
  296. // might change due to rounding errors even if no better solution was found
  297. if (sqrDistance < origSqrDistance) distance = Mathf.Sqrt(sqrDistance);
  298. }
  299. return previous;
  300. }
  301. void SearchBoxClosest (int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) {
  302. BBTreeBox box = tree[boxi];
  303. if (box.IsLeaf) {
  304. var nodes = nodeLookup;
  305. for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
  306. var node = nodes[box.nodeOffset+i];
  307. Vector3 closest = node.ClosestPointOnNode(p);
  308. float dist = (closest-p).sqrMagnitude;
  309. if (dist < closestSqrDist) {
  310. DrawDebugNode(node, 0.2f, Color.red);
  311. if (constraint == null || constraint.Suitable(node)) {
  312. // Update the NNInfo
  313. nnInfo.constrainedNode = node;
  314. nnInfo.constClampedPosition = closest;
  315. closestSqrDist = dist;
  316. }
  317. } else {
  318. DrawDebugNode(node, 0.0f, Color.blue);
  319. }
  320. }
  321. } else {
  322. DrawDebugRect(box.rect);
  323. int first = box.left, second = box.right;
  324. float firstDist, secondDist;
  325. GetOrderedChildren(ref first, ref second, out firstDist, out secondDist, p);
  326. // Search children (closest box first to improve performance)
  327. if (firstDist < closestSqrDist) {
  328. SearchBoxClosest(first, p, ref closestSqrDist, constraint, ref nnInfo);
  329. }
  330. if (secondDist < closestSqrDist) {
  331. SearchBoxClosest(second, p, ref closestSqrDist, constraint, ref nnInfo);
  332. }
  333. }
  334. }
  335. /// <summary>Orders the box indices first and second by the approximate distance to the point p</summary>
  336. void GetOrderedChildren (ref int first, ref int second, out float firstDist, out float secondDist, Vector3 p) {
  337. firstDist = SquaredRectPointDistance(tree[first].rect, p);
  338. secondDist = SquaredRectPointDistance(tree[second].rect, p);
  339. if (secondDist < firstDist) {
  340. // Swap
  341. var tmp = first;
  342. first = second;
  343. second = tmp;
  344. var tmp2 = firstDist;
  345. firstDist = secondDist;
  346. secondDist = tmp2;
  347. }
  348. }
  349. /// <summary>
  350. /// Searches for a node which contains the specified point.
  351. /// If there are multiple nodes that contain the point any one of them
  352. /// may be returned.
  353. ///
  354. /// See: TriangleMeshNode.ContainsPoint
  355. /// </summary>
  356. public TriangleMeshNode QueryInside (Vector3 p, NNConstraint constraint) {
  357. return count != 0 && tree[0].Contains(p) ? SearchBoxInside(0, p, constraint) : null;
  358. }
  359. TriangleMeshNode SearchBoxInside (int boxi, Vector3 p, NNConstraint constraint) {
  360. BBTreeBox box = tree[boxi];
  361. if (box.IsLeaf) {
  362. var nodes = nodeLookup;
  363. for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
  364. var node = nodes[box.nodeOffset+i];
  365. if (node.ContainsPoint((Int3)p)) {
  366. DrawDebugNode(node, 0.2f, Color.red);
  367. if (constraint == null || constraint.Suitable(node)) {
  368. return node;
  369. }
  370. } else {
  371. DrawDebugNode(node, 0.0f, Color.blue);
  372. }
  373. }
  374. } else {
  375. DrawDebugRect(box.rect);
  376. //Search children
  377. if (tree[box.left].Contains(p)) {
  378. var result = SearchBoxInside(box.left, p, constraint);
  379. if (result != null) return result;
  380. }
  381. if (tree[box.right].Contains(p)) {
  382. var result = SearchBoxInside(box.right, p, constraint);
  383. if (result != null) return result;
  384. }
  385. }
  386. return null;
  387. }
  388. struct BBTreeBox {
  389. public IntRect rect;
  390. public int nodeOffset;
  391. public int left, right;
  392. public bool IsLeaf {
  393. get {
  394. return nodeOffset >= 0;
  395. }
  396. }
  397. public BBTreeBox (IntRect rect) {
  398. nodeOffset = -1;
  399. this.rect = rect;
  400. left = right = -1;
  401. }
  402. public BBTreeBox (int nodeOffset, IntRect rect) {
  403. this.nodeOffset = nodeOffset;
  404. this.rect = rect;
  405. left = right = -1;
  406. }
  407. public bool Contains (Vector3 point) {
  408. var pi = (Int3)point;
  409. return rect.Contains(pi.x, pi.z);
  410. }
  411. }
  412. public void OnDrawGizmos () {
  413. Gizmos.color = new Color(1, 1, 1, 0.5F);
  414. if (count == 0) return;
  415. OnDrawGizmos(0, 0);
  416. }
  417. void OnDrawGizmos (int boxi, int depth) {
  418. BBTreeBox box = tree[boxi];
  419. var min = (Vector3) new Int3(box.rect.xmin, 0, box.rect.ymin);
  420. var max = (Vector3) new Int3(box.rect.xmax, 0, box.rect.ymax);
  421. Vector3 center = (min+max)*0.5F;
  422. Vector3 size = (max-center)*2;
  423. size = new Vector3(size.x, 1, size.z);
  424. center.y += depth * 2;
  425. Gizmos.color = AstarMath.IntToColor(depth, 1f);
  426. Gizmos.DrawCube(center, size);
  427. if (!box.IsLeaf) {
  428. OnDrawGizmos(box.left, depth + 1);
  429. OnDrawGizmos(box.right, depth + 1);
  430. }
  431. }
  432. static bool NodeIntersectsCircle (TriangleMeshNode node, Vector3 p, float radius) {
  433. if (float.IsPositiveInfinity(radius)) return true;
  434. /// <summary>Bug: Is not correct on the Y axis</summary>
  435. return (p - node.ClosestPointOnNode(p)).sqrMagnitude < radius*radius;
  436. }
  437. /// <summary>
  438. /// Returns true if p is within radius from r.
  439. /// Correctly handles cases where radius is positive infinity.
  440. /// </summary>
  441. static bool RectIntersectsCircle (IntRect r, Vector3 p, float radius) {
  442. if (float.IsPositiveInfinity(radius)) return true;
  443. Vector3 po = p;
  444. p.x = Math.Max(p.x, r.xmin*Int3.PrecisionFactor);
  445. p.x = Math.Min(p.x, r.xmax*Int3.PrecisionFactor);
  446. p.z = Math.Max(p.z, r.ymin*Int3.PrecisionFactor);
  447. p.z = Math.Min(p.z, r.ymax*Int3.PrecisionFactor);
  448. // XZ squared magnitude comparison
  449. return (p.x-po.x)*(p.x-po.x) + (p.z-po.z)*(p.z-po.z) < radius*radius;
  450. }
  451. /// <summary>Returns distance from p to the rectangle r</summary>
  452. static float SquaredRectPointDistance (IntRect r, Vector3 p) {
  453. Vector3 po = p;
  454. p.x = Math.Max(p.x, r.xmin*Int3.PrecisionFactor);
  455. p.x = Math.Min(p.x, r.xmax*Int3.PrecisionFactor);
  456. p.z = Math.Max(p.z, r.ymin*Int3.PrecisionFactor);
  457. p.z = Math.Min(p.z, r.ymax*Int3.PrecisionFactor);
  458. // XZ squared magnitude comparison
  459. return (p.x-po.x)*(p.x-po.x) + (p.z-po.z)*(p.z-po.z);
  460. }
  461. }
  462. }