123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547 |
- //#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
- using System;
- using UnityEngine;
- namespace Pathfinding {
- using Pathfinding.Util;
- /// <summary>
- /// Axis Aligned Bounding Box Tree.
- /// Holds a bounding box tree of triangles.
- /// </summary>
- public class BBTree : IAstarPooledObject {
- /// <summary>Holds all tree nodes</summary>
- BBTreeBox[] tree = null;
- TriangleMeshNode[] nodeLookup = null;
- int count;
- int leafNodes;
- const int MaximumLeafSize = 4;
- public Rect Size {
- get {
- if (count == 0) {
- return new Rect(0, 0, 0, 0);
- } else {
- var rect = tree[0].rect;
- return Rect.MinMaxRect(rect.xmin*Int3.PrecisionFactor, rect.ymin*Int3.PrecisionFactor, rect.xmax*Int3.PrecisionFactor, rect.ymax*Int3.PrecisionFactor);
- }
- }
- }
- /// <summary>
- /// Clear the tree.
- /// Note that references to old nodes will still be intact so the GC cannot immediately collect them.
- /// </summary>
- public void Clear () {
- count = 0;
- leafNodes = 0;
- if (tree != null) ArrayPool<BBTreeBox>.Release(ref tree);
- if (nodeLookup != null) {
- // Prevent memory leaks as the pool does not clear the array
- for (int i = 0; i < nodeLookup.Length; i++) nodeLookup[i] = null;
- ArrayPool<TriangleMeshNode>.Release(ref nodeLookup);
- }
- tree = ArrayPool<BBTreeBox>.Claim(0);
- nodeLookup = ArrayPool<TriangleMeshNode>.Claim(0);
- }
- void IAstarPooledObject.OnEnterPool () {
- Clear();
- }
- void EnsureCapacity (int c) {
- if (c > tree.Length) {
- var newArr = ArrayPool<BBTreeBox>.Claim(c);
- tree.CopyTo(newArr, 0);
- ArrayPool<BBTreeBox>.Release(ref tree);
- tree = newArr;
- }
- }
- void EnsureNodeCapacity (int c) {
- if (c > nodeLookup.Length) {
- var newArr = ArrayPool<TriangleMeshNode>.Claim(c);
- nodeLookup.CopyTo(newArr, 0);
- ArrayPool<TriangleMeshNode>.Release(ref nodeLookup);
- nodeLookup = newArr;
- }
- }
- int GetBox (IntRect rect) {
- if (count >= tree.Length) EnsureCapacity(count+1);
- tree[count] = new BBTreeBox(rect);
- count++;
- return count-1;
- }
- /// <summary>Rebuilds the tree using the specified nodes</summary>
- public void RebuildFrom (TriangleMeshNode[] nodes) {
- Clear();
- if (nodes.Length == 0) return;
- // We will use approximately 2N tree nodes
- EnsureCapacity(Mathf.CeilToInt(nodes.Length * 2.1f));
- // We will use approximately N node references
- EnsureNodeCapacity(Mathf.CeilToInt(nodes.Length * 1.1f));
- // This will store the order of the nodes while the tree is being built
- // It turns out that it is a lot faster to do this than to actually modify
- // the nodes and nodeBounds arrays (presumably since that involves shuffling
- // around 20 bytes of memory (sizeof(pointer) + sizeof(IntRect)) per node
- // instead of 4 bytes (sizeof(int)).
- // It also means we don't have to make a copy of the nodes array since
- // we do not modify it
- var permutation = ArrayPool<int>.Claim(nodes.Length);
- for (int i = 0; i < nodes.Length; i++) {
- permutation[i] = i;
- }
- // Precalculate the bounds of the nodes in XZ space.
- // It turns out that calculating the bounds is a bottleneck and precalculating
- // the bounds makes it around 3 times faster to build a tree
- var nodeBounds = ArrayPool<IntRect>.Claim(nodes.Length);
- for (int i = 0; i < nodes.Length; i++) {
- Int3 v0, v1, v2;
- nodes[i].GetVertices(out v0, out v1, out v2);
- var rect = new IntRect(v0.x, v0.z, v0.x, v0.z);
- rect = rect.ExpandToContain(v1.x, v1.z);
- rect = rect.ExpandToContain(v2.x, v2.z);
- nodeBounds[i] = rect;
- }
- RebuildFromInternal(nodes, permutation, nodeBounds, 0, nodes.Length, false);
- ArrayPool<int>.Release(ref permutation);
- ArrayPool<IntRect>.Release(ref nodeBounds);
- }
- static int SplitByX (TriangleMeshNode[] nodes, int[] permutation, int from, int to, int divider) {
- int mx = to;
- for (int i = from; i < mx; i++) {
- if (nodes[permutation[i]].position.x > divider) {
- mx--;
- // Swap items i and mx
- var tmp = permutation[mx];
- permutation[mx] = permutation[i];
- permutation[i] = tmp;
- i--;
- }
- }
- return mx;
- }
- static int SplitByZ (TriangleMeshNode[] nodes, int[] permutation, int from, int to, int divider) {
- int mx = to;
- for (int i = from; i < mx; i++) {
- if (nodes[permutation[i]].position.z > divider) {
- mx--;
- // Swap items i and mx
- var tmp = permutation[mx];
- permutation[mx] = permutation[i];
- permutation[i] = tmp;
- i--;
- }
- }
- return mx;
- }
- int RebuildFromInternal (TriangleMeshNode[] nodes, int[] permutation, IntRect[] nodeBounds, int from, int to, bool odd) {
- var rect = NodeBounds(permutation, nodeBounds, from, to);
- int box = GetBox(rect);
- if (to - from <= MaximumLeafSize) {
- var nodeOffset = tree[box].nodeOffset = leafNodes*MaximumLeafSize;
- EnsureNodeCapacity(nodeOffset + MaximumLeafSize);
- leafNodes++;
- // Assign all nodes to the array. Note that we also need clear unused slots as the array from the pool may contain any information
- for (int i = 0; i < MaximumLeafSize; i++) {
- nodeLookup[nodeOffset + i] = i < to - from ? nodes[permutation[from + i]] : null;
- }
- return box;
- }
- int splitIndex;
- if (odd) {
- // X
- int divider = (rect.xmin + rect.xmax)/2;
- splitIndex = SplitByX(nodes, permutation, from, to, divider);
- } else {
- // Y/Z
- int divider = (rect.ymin + rect.ymax)/2;
- splitIndex = SplitByZ(nodes, permutation, from, to, divider);
- }
- if (splitIndex == from || splitIndex == to) {
- // All nodes were on one side of the divider
- // Try to split along the other axis
- if (!odd) {
- // X
- int divider = (rect.xmin + rect.xmax)/2;
- splitIndex = SplitByX(nodes, permutation, from, to, divider);
- } else {
- // Y/Z
- int divider = (rect.ymin + rect.ymax)/2;
- splitIndex = SplitByZ(nodes, permutation, from, to, divider);
- }
- if (splitIndex == from || splitIndex == to) {
- // All nodes were on one side of the divider
- // Just pick one half
- splitIndex = (from+to)/2;
- }
- }
- tree[box].left = RebuildFromInternal(nodes, permutation, nodeBounds, from, splitIndex, !odd);
- tree[box].right = RebuildFromInternal(nodes, permutation, nodeBounds, splitIndex, to, !odd);
- return box;
- }
- /// <summary>Calculates the bounding box in XZ space of all nodes between from (inclusive) and to (exclusive)</summary>
- static IntRect NodeBounds (int[] permutation, IntRect[] nodeBounds, int from, int to) {
- var rect = nodeBounds[permutation[from]];
- for (int j = from + 1; j < to; j++) {
- var otherRect = nodeBounds[permutation[j]];
- // Equivalent to rect = IntRect.Union(rect, otherRect)
- // but manually inlining is approximately
- // 25% faster when building an entire tree.
- // This code is hot when using navmesh cutting.
- rect.xmin = Math.Min(rect.xmin, otherRect.xmin);
- rect.ymin = Math.Min(rect.ymin, otherRect.ymin);
- rect.xmax = Math.Max(rect.xmax, otherRect.xmax);
- rect.ymax = Math.Max(rect.ymax, otherRect.ymax);
- }
- return rect;
- }
- [System.Diagnostics.Conditional("ASTARDEBUG")]
- static void DrawDebugRect (IntRect rect) {
- Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymin), new Vector3(rect.xmax, 0, rect.ymin), Color.white);
- Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymax), new Vector3(rect.xmax, 0, rect.ymax), Color.white);
- Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymin), new Vector3(rect.xmin, 0, rect.ymax), Color.white);
- Debug.DrawLine(new Vector3(rect.xmax, 0, rect.ymin), new Vector3(rect.xmax, 0, rect.ymax), Color.white);
- }
- [System.Diagnostics.Conditional("ASTARDEBUG")]
- static void DrawDebugNode (TriangleMeshNode node, float yoffset, Color color) {
- Debug.DrawLine((Vector3)node.GetVertex(1) + Vector3.up*yoffset, (Vector3)node.GetVertex(2) + Vector3.up*yoffset, color);
- Debug.DrawLine((Vector3)node.GetVertex(0) + Vector3.up*yoffset, (Vector3)node.GetVertex(1) + Vector3.up*yoffset, color);
- Debug.DrawLine((Vector3)node.GetVertex(2) + Vector3.up*yoffset, (Vector3)node.GetVertex(0) + Vector3.up*yoffset, color);
- }
- /// <summary>
- /// Queries the tree for the closest node to p constrained by the NNConstraint.
- /// Note that this function will only fill in the constrained node.
- /// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
- /// </summary>
- public NNInfoInternal QueryClosest (Vector3 p, NNConstraint constraint, out float distance) {
- distance = float.PositiveInfinity;
- return QueryClosest(p, constraint, ref distance, new NNInfoInternal(null));
- }
- /// <summary>
- /// Queries the tree for the closest node to p constrained by the NNConstraint trying to improve an existing solution.
- /// Note that this function will only fill in the constrained node.
- /// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
- ///
- /// This method will completely ignore any Y-axis differences in positions.
- /// </summary>
- /// <param name="p">Point to search around</param>
- /// <param name="constraint">Optionally set to constrain which nodes to return</param>
- /// <param name="distance">The best distance for the previous solution. Will be updated with the best distance
- /// after this search. Will be positive infinity if no node could be found.
- /// Set to positive infinity if there was no previous solution.</param>
- /// <param name="previous">This search will start from the previous NNInfo and improve it if possible.
- /// Even if the search fails on this call, the solution will never be worse than previous.
- /// Note that the distance parameter need to be configured with the distance for the previous result
- /// otherwise it may get overwritten even though it was actually closer.</param>
- public NNInfoInternal QueryClosestXZ (Vector3 p, NNConstraint constraint, ref float distance, NNInfoInternal previous) {
- var sqrDistance = distance*distance;
- var origSqrDistance = sqrDistance;
- if (count > 0 && SquaredRectPointDistance(tree[0].rect, p) < sqrDistance) {
- SearchBoxClosestXZ(0, p, ref sqrDistance, constraint, ref previous);
- // Only update the distance if the squared distance changed as otherwise #distance
- // might change due to rounding errors even if no better solution was found
- if (sqrDistance < origSqrDistance) distance = Mathf.Sqrt(sqrDistance);
- }
- return previous;
- }
- void SearchBoxClosestXZ (int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) {
- BBTreeBox box = tree[boxi];
- if (box.IsLeaf) {
- var nodes = nodeLookup;
- for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
- var node = nodes[box.nodeOffset+i];
- // Update the NNInfo
- DrawDebugNode(node, 0.2f, Color.red);
- if (constraint == null || constraint.Suitable(node)) {
- Vector3 closest = node.ClosestPointOnNodeXZ(p);
- // XZ squared distance
- float dist = (closest.x-p.x)*(closest.x-p.x)+(closest.z-p.z)*(closest.z-p.z);
- // There's a theoretical case when the closest point is on the edge of a node which may cause the
- // closest point's xz coordinates to not line up perfectly with p's xz coordinates even though they should
- // (because floating point errors are annoying). So use a tiny margin to cover most of those cases.
- const float fuzziness = 0.000001f;
- if (nnInfo.constrainedNode == null || dist < closestSqrDist - fuzziness || (dist <= closestSqrDist + fuzziness && Mathf.Abs(closest.y - p.y) < Mathf.Abs(nnInfo.constClampedPosition.y - p.y))) {
- nnInfo.constrainedNode = node;
- nnInfo.constClampedPosition = closest;
- closestSqrDist = dist;
- }
- }
- }
- } else {
- DrawDebugRect(box.rect);
- int first = box.left, second = box.right;
- float firstDist, secondDist;
- GetOrderedChildren(ref first, ref second, out firstDist, out secondDist, p);
- // Search children (closest box first to improve performance)
- if (firstDist <= closestSqrDist) {
- SearchBoxClosestXZ(first, p, ref closestSqrDist, constraint, ref nnInfo);
- }
- if (secondDist <= closestSqrDist) {
- SearchBoxClosestXZ(second, p, ref closestSqrDist, constraint, ref nnInfo);
- }
- }
- }
- /// <summary>
- /// Queries the tree for the closest node to p constrained by the NNConstraint trying to improve an existing solution.
- /// Note that this function will only fill in the constrained node.
- /// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
- /// </summary>
- /// <param name="p">Point to search around</param>
- /// <param name="constraint">Optionally set to constrain which nodes to return</param>
- /// <param name="distance">The best distance for the previous solution. Will be updated with the best distance
- /// after this search. Will be positive infinity if no node could be found.
- /// Set to positive infinity if there was no previous solution.</param>
- /// <param name="previous">This search will start from the previous NNInfo and improve it if possible.
- /// Even if the search fails on this call, the solution will never be worse than previous.</param>
- public NNInfoInternal QueryClosest (Vector3 p, NNConstraint constraint, ref float distance, NNInfoInternal previous) {
- var sqrDistance = distance*distance;
- var origSqrDistance = sqrDistance;
- if (count > 0 && SquaredRectPointDistance(tree[0].rect, p) < sqrDistance) {
- SearchBoxClosest(0, p, ref sqrDistance, constraint, ref previous);
- // Only update the distance if the squared distance changed as otherwise #distance
- // might change due to rounding errors even if no better solution was found
- if (sqrDistance < origSqrDistance) distance = Mathf.Sqrt(sqrDistance);
- }
- return previous;
- }
- void SearchBoxClosest (int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) {
- BBTreeBox box = tree[boxi];
- if (box.IsLeaf) {
- var nodes = nodeLookup;
- for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
- var node = nodes[box.nodeOffset+i];
- Vector3 closest = node.ClosestPointOnNode(p);
- float dist = (closest-p).sqrMagnitude;
- if (dist < closestSqrDist) {
- DrawDebugNode(node, 0.2f, Color.red);
- if (constraint == null || constraint.Suitable(node)) {
- // Update the NNInfo
- nnInfo.constrainedNode = node;
- nnInfo.constClampedPosition = closest;
- closestSqrDist = dist;
- }
- } else {
- DrawDebugNode(node, 0.0f, Color.blue);
- }
- }
- } else {
- DrawDebugRect(box.rect);
- int first = box.left, second = box.right;
- float firstDist, secondDist;
- GetOrderedChildren(ref first, ref second, out firstDist, out secondDist, p);
- // Search children (closest box first to improve performance)
- if (firstDist < closestSqrDist) {
- SearchBoxClosest(first, p, ref closestSqrDist, constraint, ref nnInfo);
- }
- if (secondDist < closestSqrDist) {
- SearchBoxClosest(second, p, ref closestSqrDist, constraint, ref nnInfo);
- }
- }
- }
- /// <summary>Orders the box indices first and second by the approximate distance to the point p</summary>
- void GetOrderedChildren (ref int first, ref int second, out float firstDist, out float secondDist, Vector3 p) {
- firstDist = SquaredRectPointDistance(tree[first].rect, p);
- secondDist = SquaredRectPointDistance(tree[second].rect, p);
- if (secondDist < firstDist) {
- // Swap
- var tmp = first;
- first = second;
- second = tmp;
- var tmp2 = firstDist;
- firstDist = secondDist;
- secondDist = tmp2;
- }
- }
- /// <summary>
- /// Searches for a node which contains the specified point.
- /// If there are multiple nodes that contain the point any one of them
- /// may be returned.
- ///
- /// See: TriangleMeshNode.ContainsPoint
- /// </summary>
- public TriangleMeshNode QueryInside (Vector3 p, NNConstraint constraint) {
- return count != 0 && tree[0].Contains(p) ? SearchBoxInside(0, p, constraint) : null;
- }
- TriangleMeshNode SearchBoxInside (int boxi, Vector3 p, NNConstraint constraint) {
- BBTreeBox box = tree[boxi];
- if (box.IsLeaf) {
- var nodes = nodeLookup;
- for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
- var node = nodes[box.nodeOffset+i];
- if (node.ContainsPoint((Int3)p)) {
- DrawDebugNode(node, 0.2f, Color.red);
- if (constraint == null || constraint.Suitable(node)) {
- return node;
- }
- } else {
- DrawDebugNode(node, 0.0f, Color.blue);
- }
- }
- } else {
- DrawDebugRect(box.rect);
- //Search children
- if (tree[box.left].Contains(p)) {
- var result = SearchBoxInside(box.left, p, constraint);
- if (result != null) return result;
- }
- if (tree[box.right].Contains(p)) {
- var result = SearchBoxInside(box.right, p, constraint);
- if (result != null) return result;
- }
- }
- return null;
- }
- struct BBTreeBox {
- public IntRect rect;
- public int nodeOffset;
- public int left, right;
- public bool IsLeaf {
- get {
- return nodeOffset >= 0;
- }
- }
- public BBTreeBox (IntRect rect) {
- nodeOffset = -1;
- this.rect = rect;
- left = right = -1;
- }
- public BBTreeBox (int nodeOffset, IntRect rect) {
- this.nodeOffset = nodeOffset;
- this.rect = rect;
- left = right = -1;
- }
- public bool Contains (Vector3 point) {
- var pi = (Int3)point;
- return rect.Contains(pi.x, pi.z);
- }
- }
- public void OnDrawGizmos () {
- Gizmos.color = new Color(1, 1, 1, 0.5F);
- if (count == 0) return;
- OnDrawGizmos(0, 0);
- }
- void OnDrawGizmos (int boxi, int depth) {
- BBTreeBox box = tree[boxi];
- var min = (Vector3) new Int3(box.rect.xmin, 0, box.rect.ymin);
- var max = (Vector3) new Int3(box.rect.xmax, 0, box.rect.ymax);
- Vector3 center = (min+max)*0.5F;
- Vector3 size = (max-center)*2;
- size = new Vector3(size.x, 1, size.z);
- center.y += depth * 2;
- Gizmos.color = AstarMath.IntToColor(depth, 1f);
- Gizmos.DrawCube(center, size);
- if (!box.IsLeaf) {
- OnDrawGizmos(box.left, depth + 1);
- OnDrawGizmos(box.right, depth + 1);
- }
- }
- static bool NodeIntersectsCircle (TriangleMeshNode node, Vector3 p, float radius) {
- if (float.IsPositiveInfinity(radius)) return true;
- /// <summary>Bug: Is not correct on the Y axis</summary>
- return (p - node.ClosestPointOnNode(p)).sqrMagnitude < radius*radius;
- }
- /// <summary>
- /// Returns true if p is within radius from r.
- /// Correctly handles cases where radius is positive infinity.
- /// </summary>
- static bool RectIntersectsCircle (IntRect r, Vector3 p, float radius) {
- if (float.IsPositiveInfinity(radius)) return true;
- Vector3 po = p;
- p.x = Math.Max(p.x, r.xmin*Int3.PrecisionFactor);
- p.x = Math.Min(p.x, r.xmax*Int3.PrecisionFactor);
- p.z = Math.Max(p.z, r.ymin*Int3.PrecisionFactor);
- p.z = Math.Min(p.z, r.ymax*Int3.PrecisionFactor);
- // XZ squared magnitude comparison
- return (p.x-po.x)*(p.x-po.x) + (p.z-po.z)*(p.z-po.z) < radius*radius;
- }
- /// <summary>Returns distance from p to the rectangle r</summary>
- static float SquaredRectPointDistance (IntRect r, Vector3 p) {
- Vector3 po = p;
- p.x = Math.Max(p.x, r.xmin*Int3.PrecisionFactor);
- p.x = Math.Min(p.x, r.xmax*Int3.PrecisionFactor);
- p.z = Math.Max(p.z, r.ymin*Int3.PrecisionFactor);
- p.z = Math.Min(p.z, r.ymax*Int3.PrecisionFactor);
- // XZ squared magnitude comparison
- return (p.x-po.x)*(p.x-po.x) + (p.z-po.z)*(p.z-po.z);
- }
- }
- }
|