2025-07-08 10:46:31 +00:00
//#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 System.Collections.Generic ;
using UnityEngine ;
using Unity.Mathematics ;
using Unity.Burst ;
using Unity.Profiling ;
using Unity.Collections ;
using Unity.Collections.LowLevel.Unsafe ;
using Pathfinding.Drawing ;
namespace Pathfinding.Collections {
using Pathfinding.Util ;
/// <summary>
/// Axis Aligned Bounding Box Tree.
/// Holds a bounding box tree of triangles.
/// </summary>
[BurstCompile]
public struct BBTree : IDisposable {
/// <summary>Holds all tree nodes</summary>
UnsafeList < BBTreeBox > tree ;
UnsafeList < int > nodePermutation ;
const int MaximumLeafSize = 4 ;
public IntRect Size = > tree . Length = = 0 ? default : tree [ 0 ] . rect ;
// We need a stack while searching the tree.
// We use a stack allocated array for this to avoid allocations.
// A tile can at most contain NavmeshBase.VertexIndexMask triangles.
// This works out to about a million. A perfectly balanced tree can fit this in log2(1000000/4) = 18 levels.
// but we add a few more levels just to be safe, in case the tree is not perfectly balanced.
const int MAX_TREE_HEIGHT = 26 ;
public void Dispose ( ) {
nodePermutation . Dispose ( ) ;
tree . Dispose ( ) ;
}
/// <summary>Build a BBTree from a list of triangles.</summary>
/// <param name="triangles">The triangles. Each triplet of 3 indices represents a node. The triangles are assumed to be in clockwise order.</param>
/// <param name="vertices">The vertices of the triangles.</param>
public BBTree ( UnsafeSpan < int > triangles , UnsafeSpan < Int3 > vertices ) {
if ( triangles . Length % 3 ! = 0 ) throw new ArgumentException ( "triangles must be a multiple of 3 in length" ) ;
Build ( ref triangles , ref vertices , out this ) ;
}
[BurstCompile]
static void Build ( ref UnsafeSpan < int > triangles , ref UnsafeSpan < Int3 > vertices , out BBTree bbTree ) {
var nodeCount = triangles . Length / 3 ;
// We will use approximately 2N tree nodes
var tree = new UnsafeList < BBTreeBox > ( ( int ) ( nodeCount * 2.1f ) , Allocator . Persistent , NativeArrayOptions . UninitializedMemory ) ;
// We will use approximately N node references
var nodes = new UnsafeList < int > ( ( int ) ( nodeCount * 1.1f ) , Allocator . Persistent , NativeArrayOptions . UninitializedMemory ) ;
// 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 = new NativeArray < int > ( nodeCount , Allocator . Temp ) ;
for ( int i = 0 ; i < nodeCount ; 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 = new NativeArray < IntRect > ( nodeCount , Allocator . Temp ) ;
for ( int i = 0 ; i < nodeCount ; i + + ) {
var v0 = ( ( int3 ) vertices [ triangles [ i * 3 + 0 ] ] ) . xz ;
var v1 = ( ( int3 ) vertices [ triangles [ i * 3 + 1 ] ] ) . xz ;
var v2 = ( ( int3 ) vertices [ triangles [ i * 3 + 2 ] ] ) . xz ;
var mn = math . min ( v0 , math . min ( v1 , v2 ) ) ;
var mx = math . max ( v0 , math . max ( v1 , v2 ) ) ;
nodeBounds [ i ] = new IntRect ( mn . x , mn . y , mx . x , mx . y ) ;
}
if ( nodeCount > 0 ) BuildSubtree ( permutation , nodeBounds , ref nodes , ref tree , 0 , nodeCount , false , 0 ) ;
nodeBounds . Dispose ( ) ;
permutation . Dispose ( ) ;
bbTree = new BBTree {
tree = tree ,
nodePermutation = nodes ,
} ;
}
static int SplitByX ( NativeArray < IntRect > nodesBounds , NativeArray < int > permutation , int from , int to , int divider ) {
int mx = to ;
for ( int i = from ; i < mx ; i + + ) {
var cr = nodesBounds [ permutation [ i ] ] ;
var cx = ( cr . xmin + cr . xmax ) / 2 ;
if ( cx > divider ) {
mx - - ;
// Swap items i and mx
var tmp = permutation [ mx ] ;
permutation [ mx ] = permutation [ i ] ;
permutation [ i ] = tmp ;
i - - ;
}
}
return mx ;
}
static int SplitByZ ( NativeArray < IntRect > nodesBounds , NativeArray < int > permutation , int from , int to , int divider ) {
int mx = to ;
for ( int i = from ; i < mx ; i + + ) {
var cr = nodesBounds [ permutation [ i ] ] ;
var cx = ( cr . ymin + cr . ymax ) / 2 ;
if ( cx > divider ) {
mx - - ;
// Swap items i and mx
var tmp = permutation [ mx ] ;
permutation [ mx ] = permutation [ i ] ;
permutation [ i ] = tmp ;
i - - ;
}
}
return mx ;
}
static int BuildSubtree ( NativeArray < int > permutation , NativeArray < IntRect > nodeBounds , ref UnsafeList < int > nodes , ref UnsafeList < BBTreeBox > tree , int from , int to , bool odd , int depth ) {
var rect = NodeBounds ( permutation , nodeBounds , from , to ) ;
int boxId = tree . Length ;
tree . Add ( new BBTreeBox ( rect ) ) ;
if ( to - from < = MaximumLeafSize ) {
if ( depth > MAX_TREE_HEIGHT ) {
Debug . LogWarning ( $"Maximum tree height of {MAX_TREE_HEIGHT} exceeded (got depth of {depth}). Querying this tree may fail. Is the tree very unbalanced?" ) ;
}
var box = tree [ boxId ] ;
var nodeOffset = box . nodeOffset = nodes . Length ;
tree [ boxId ] = box ;
nodes . Length + = MaximumLeafSize ;
// 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 + + ) {
nodes [ nodeOffset + i ] = i < to - from ? permutation [ from + i ] : - 1 ;
}
return boxId ;
} else {
int splitIndex ;
if ( odd ) {
// X
int divider = ( rect . xmin + rect . xmax ) / 2 ;
splitIndex = SplitByX ( nodeBounds , permutation , from , to , divider ) ;
} else {
// Y/Z
int divider = ( rect . ymin + rect . ymax ) / 2 ;
splitIndex = SplitByZ ( nodeBounds , permutation , from , to , divider ) ;
}
int margin = ( to - from ) / 8 ;
bool veryUneven = splitIndex < = from + margin | | splitIndex > = to - margin ;
if ( veryUneven ) {
// 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 ( nodeBounds , permutation , from , to , divider ) ;
} else {
// Y/Z
int divider = ( rect . ymin + rect . ymax ) / 2 ;
splitIndex = SplitByZ ( nodeBounds , permutation , from , to , divider ) ;
}
veryUneven = splitIndex < = from + margin | | splitIndex > = to - margin ;
if ( veryUneven ) {
// Almost all nodes were on one side of the divider
// Just pick one half
splitIndex = ( from + to ) / 2 ;
}
}
var left = BuildSubtree ( permutation , nodeBounds , ref nodes , ref tree , from , splitIndex , ! odd , depth + 1 ) ;
var right = BuildSubtree ( permutation , nodeBounds , ref nodes , ref tree , splitIndex , to , ! odd , depth + 1 ) ;
var box = tree [ boxId ] ;
box . left = left ;
box . right = right ;
tree [ boxId ] = box ;
return boxId ;
}
}
/// <summary>Calculates the bounding box in XZ space of all nodes between from (inclusive) and to (exclusive)</summary>
static IntRect NodeBounds ( NativeArray < int > permutation , NativeArray < IntRect > nodeBounds , int from , int to ) {
var mn = nodeBounds [ permutation [ from ] ] . Min . ToInt2 ( ) ;
var mx = nodeBounds [ permutation [ from ] ] . Max . ToInt2 ( ) ;
for ( int j = from + 1 ; j < to ; j + + ) {
var otherRect = nodeBounds [ permutation [ j ] ] ;
var rmin = new int2 ( otherRect . xmin , otherRect . ymin ) ;
var rmax = new int2 ( otherRect . xmax , otherRect . ymax ) ;
mn = math . min ( mn , rmin ) ;
mx = math . max ( mx , rmax ) ;
}
return new IntRect ( mn . x , mn . y , mx . x , mx . y ) ;
}
[BurstCompile]
public readonly struct ProjectionParams {
public readonly float2x3 planeProjection ;
public readonly float2 projectedUpNormalized ;
public readonly float3 projectionAxis ;
public readonly float distanceScaleAlongProjectionAxis ;
public readonly DistanceMetric distanceMetric ;
// bools are for some reason not blittable by the burst compiler, so we have to use a byte
readonly byte alignedWithXZPlaneBacking ;
public bool alignedWithXZPlane = > alignedWithXZPlaneBacking ! = 0 ;
/// <summary>
/// Calculates the squared distance from a point to a box when projected to 2D.
///
/// The input rectangle is assumed to be on the XZ plane, and to actually represent an infinitely tall box (along the Y axis).
///
/// The planeProjection matrix projects points from 3D to 2D. The box will also be projected.
/// The upProjNormalized vector is the normalized direction orthogonal to the 2D projection.
/// It is the direction pointing out of the plane from the projection's point of view.
///
/// In the special case that the projection just projects 3D coordinates onto the XZ plane, this is
/// equivalent to the distance from a point to a rectangle in 2D.
/// </summary>
public float SquaredRectPointDistanceOnPlane ( IntRect rect , float3 p ) {
return SquaredRectPointDistanceOnPlane ( in this , ref rect , ref p ) ;
}
[BurstCompile(FloatMode = FloatMode.Fast)] [ IgnoredByDeepProfiler ]
private static float SquaredRectPointDistanceOnPlane ( in ProjectionParams projection , ref IntRect rect , ref float3 p ) {
if ( projection . alignedWithXZPlane ) {
var p1 = new float2 ( rect . xmin , rect . ymin ) * Int3 . PrecisionFactor ;
var p4 = new float2 ( rect . xmax , rect . ymax ) * Int3 . PrecisionFactor ;
var closest = math . clamp ( p . xz , p1 , p4 ) ;
return math . lengthsq ( closest - p . xz ) ;
} else {
var p1 = new float3 ( rect . xmin , 0 , rect . ymin ) * Int3 . PrecisionFactor - p ;
var p4 = new float3 ( rect . xmax , 0 , rect . ymax ) * Int3 . PrecisionFactor - p ;
var p2 = new float3 ( rect . xmin , 0 , rect . ymax ) * Int3 . PrecisionFactor - p ;
var p3 = new float3 ( rect . xmax , 0 , rect . ymin ) * Int3 . PrecisionFactor - p ;
var p1proj = math . mul ( projection . planeProjection , p1 ) ;
var p2proj = math . mul ( projection . planeProjection , p2 ) ;
var p3proj = math . mul ( projection . planeProjection , p3 ) ;
var p4proj = math . mul ( projection . planeProjection , p4 ) ;
var upNormal = new float2 ( projection . projectedUpNormalized . y , - projection . projectedUpNormalized . x ) ;
// Calculate the dot product of pNproj and upNormal for all N, this is the distance between p and pN
// along the direction orthogonal to upProjNormalized.
// The box is infinite along the up direction (since it is only a rect). When projected down to 2D
// this results in an infinite line with a given thickness (a beam).
// This is assuming the projection direction is not parallel to the world up direction, in which case we
// would have entered the other branch of this if statement.
// The minumum value and maximum value in dists gives us the signed distance to this beam
// from the point p.
var dists = math . mul ( math . transpose ( new float2x4 ( p1proj , p2proj , p3proj , p4proj ) ) , upNormal ) ;
// Calculate the shortest distance to the beam (may be 0 if p is inside the beam).
var dist = math . clamp ( 0 , math . cmin ( dists ) , math . cmax ( dists ) ) ;
return dist * dist ;
}
}
public ProjectionParams ( NNConstraint constraint , GraphTransform graphTransform ) {
const float MAX_ERROR_IN_RADIANS = 0.01f ;
// The normal of the plane we are projecting onto (if any).
if ( constraint ! = null & & constraint . distanceMetric . projectionAxis ! = Vector3 . zero ) {
// (inf,inf,inf) is a special value indicating to use the graph's natural up direction
if ( float . IsPositiveInfinity ( constraint . distanceMetric . projectionAxis . x ) ) {
projectionAxis = new float3 ( 0 , 1 , 0 ) ;
} else {
projectionAxis = math . normalizesafe ( graphTransform . InverseTransformVector ( constraint . distanceMetric . projectionAxis ) ) ;
}
if ( projectionAxis . x * projectionAxis . x + projectionAxis . z * projectionAxis . z < MAX_ERROR_IN_RADIANS * MAX_ERROR_IN_RADIANS ) {
// We could let the code below handle this case, but since it is a common case we can optimize it a bit
// by using a fast-path here.
projectedUpNormalized = float2 . zero ;
planeProjection = new float2x3 ( 1 , 0 , 0 , 0 , 0 , 1 ) ; // math.transpose(new float3x2(new float3(1, 0, 0), new float3(0, 0, 1)));
distanceMetric = DistanceMetric . ScaledManhattan ;
alignedWithXZPlaneBacking = ( byte ) 1 ;
distanceScaleAlongProjectionAxis = math . max ( constraint . distanceMetric . distanceScaleAlongProjectionDirection , 0 ) ;
return ;
}
// Find any two vectors which are perpendicular to the normal (and each other)
var planeAxis1 = math . normalizesafe ( math . cross ( new float3 ( 1 , 0 , 1 ) , projectionAxis ) ) ;
if ( math . all ( planeAxis1 = = 0 ) ) planeAxis1 = math . normalizesafe ( math . cross ( new float3 ( - 1 , 0 , 1 ) , projectionAxis ) ) ;
var planeAxis2 = math . normalizesafe ( math . cross ( projectionAxis , planeAxis1 ) ) ;
// Note: The inverse of an orthogonal matrix is its transpose, and the transpose is faster to compute
planeProjection = math . transpose ( new float3x2 ( planeAxis1 , planeAxis2 ) ) ;
// The projection of the (0,1,0) vector onto the plane.
// This is important because the BBTree stores its rectangles in the XZ plane.
// If the projection is close enough to the XZ plane, we snap to that because it allows us to use faster and more precise distance calculations.
projectedUpNormalized = math . lengthsq ( planeProjection . c1 ) < = MAX_ERROR_IN_RADIANS * MAX_ERROR_IN_RADIANS ? float2 . zero : math . normalize ( planeProjection . c1 ) ;
distanceMetric = DistanceMetric . ScaledManhattan ;
alignedWithXZPlaneBacking = math . all ( projectedUpNormalized = = 0 ) ? ( byte ) 1 : ( byte ) 0 ;
// The distance along the projection axis is scaled by a cost factor to make the distance
// along the projection direction more or less important compared to the distance in the plane.
// Usually the projection direction is less important.
// For example, when an agent looks for the closest node, it is typically more interested in finding a point close
// to it which is more or less directly below it, than it is in finding a point which is closer, but requires sideways movement.
// Even if this value is zero we will use the distance along the projection axis to break ties.
// Otherwise, when getting the nearest node in e.g. a tall building, it would not be well defined
// which floor of the building was closest.
distanceScaleAlongProjectionAxis = math . max ( constraint . distanceMetric . distanceScaleAlongProjectionDirection , 0 ) ;
} else {
projectionAxis = float3 . zero ;
planeProjection = default ;
projectedUpNormalized = default ;
distanceMetric = DistanceMetric . Euclidean ;
alignedWithXZPlaneBacking = 1 ;
distanceScaleAlongProjectionAxis = 0 ;
}
}
}
public float DistanceSqrLowerBound ( float3 p , in ProjectionParams projection ) {
if ( tree . Length = = 0 ) return float . PositiveInfinity ;
return projection . SquaredRectPointDistanceOnPlane ( tree [ 0 ] . rect , p ) ;
}
/// <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="distanceSqr">The best squared distance for the previous solution. Will be updated with the best distance
/// after this search. Supply positive infinity to start the search from scratch.</param>
/// <param name="previous">This search will start from the previous NNInfo and improve it if possible. Will be updated with the new result.
/// Even if the search fails on this call, the solution will never be worse than previous.</param>
/// <param name="nodes">The nodes what this BBTree was built from</param>
/// <param name="triangles">The triangles that this BBTree was built from</param>
/// <param name="vertices">The vertices that this BBTree was built from</param>
/// <param name="projection">Projection parameters derived from the constraint</param>
public void QueryClosest ( float3 p , NNConstraint constraint , in ProjectionParams projection , ref float distanceSqr , ref NNInfo previous , GraphNode [ ] nodes , UnsafeSpan < int > triangles , UnsafeSpan < Int3 > vertices ) {
if ( tree . Length = = 0 ) return ;
UnsafeSpan < NearbyNodesIterator . BoxWithDist > stack ;
unsafe {
NearbyNodesIterator . BoxWithDist * stackPtr = stackalloc NearbyNodesIterator . BoxWithDist [ MAX_TREE_HEIGHT ] ;
stack = new UnsafeSpan < NearbyNodesIterator . BoxWithDist > ( stackPtr , MAX_TREE_HEIGHT ) ;
}
stack [ 0 ] = new NearbyNodesIterator . BoxWithDist {
index = 0 ,
distSqr = 0.0f ,
} ;
var it = new NearbyNodesIterator {
stack = stack ,
stackSize = 1 ,
indexInLeaf = 0 ,
point = p ,
projection = projection ,
distanceThresholdSqr = distanceSqr ,
tieBreakingDistanceThreshold = float . PositiveInfinity ,
tree = tree . AsUnsafeSpan ( ) ,
nodes = nodePermutation . AsUnsafeSpan ( ) ,
triangles = triangles ,
vertices = vertices ,
} ;
// We use an iterator which searches through the tree and returns nodes closer than it.distanceThresholdSqr.
// The iterator is compiled using burst for high performance, but when a new candidate node is found we need
// to evaluate it in pure C# due to the NNConstraint being a C# class.
// TODO: If constraint==null (or NNConstraint.None) we could run the whole thing in burst to improve perf even more.
var result = previous ;
while ( it . stackSize > 0 & & it . MoveNext ( ) ) {
var current = it . current ;
if ( constraint = = null | | constraint . Suitable ( nodes [ current . node ] ) ) {
it . distanceThresholdSqr = current . distanceSq ;
it . tieBreakingDistanceThreshold = current . tieBreakingDistance ;
result = new NNInfo ( nodes [ current . node ] , current . closestPointOnNode , current . distanceSq ) ;
}
}
distanceSqr = it . distanceThresholdSqr ;
previous = result ;
}
struct CloseNode {
public int node ;
public float distanceSq ;
public float tieBreakingDistance ;
public float3 closestPointOnNode ;
}
public enum DistanceMetric : byte {
Euclidean ,
ScaledManhattan ,
}
[BurstCompile]
struct NearbyNodesIterator : IEnumerator < CloseNode > {
public UnsafeSpan < BoxWithDist > stack ;
public int stackSize ;
public UnsafeSpan < BBTreeBox > tree ;
public UnsafeSpan < int > nodes ;
public UnsafeSpan < int > triangles ;
public UnsafeSpan < Int3 > vertices ;
public int indexInLeaf ;
public float3 point ;
public ProjectionParams projection ;
public float distanceThresholdSqr ;
public float tieBreakingDistanceThreshold ;
internal CloseNode current ;
public CloseNode Current = > current ;
public struct BoxWithDist {
public int index ;
public float distSqr ;
}
public bool MoveNext ( ) {
return MoveNext ( ref this ) ;
}
void IDisposable . Dispose ( ) { }
void System . Collections . IEnumerator . Reset ( ) = > throw new NotSupportedException ( ) ;
object System . Collections . IEnumerator . Current = > throw new NotSupportedException ( ) ;
// Note: Using FloatMode=Fast here can cause NaNs in rare cases.
// I have not tracked down why, but it is not unreasonable given that FloatMode=Fast assumes that infinities do not happen.
[BurstCompile(FloatMode = FloatMode.Default)]
static bool MoveNext ( ref NearbyNodesIterator it ) {
var distanceThresholdSqr = it . distanceThresholdSqr ;
while ( true ) {
if ( it . stackSize = = 0 ) {
return false ;
}
// Pop the last element from the stack
var boxRef = it . stack [ it . stackSize - 1 ] ;
// If we cannot possibly find anything better than the current best solution in here, skip this box.
// Allow the search when we can find an equally close node, because tie breaking
// may cause this search to find a better node.
if ( boxRef . distSqr > distanceThresholdSqr ) {
it . stackSize - - ;
// Setting this to zero shouldn't be necessary in theory, as a leaf will always (in theory) be searched completely.
// However, in practice the distance to a node may be a tiny bit lower than the distance to the box containing the node, due to floating point errors.
// and so the leaf's search may be terminated early if a point is found on a node exactly on the border of the box.
// In that case it is important that we reset the iterator to the start of the next leaf.
it . indexInLeaf = 0 ;
continue ;
}
BBTreeBox box = it . tree [ boxRef . index ] ;
if ( box . IsLeaf ) {
for ( int i = it . indexInLeaf ; i < MaximumLeafSize ; i + + ) {
var node = it . nodes [ box . nodeOffset + i ] ;
if ( node = = - 1 ) break ;
var ti1 = ( uint ) ( node * 3 + 0 ) ;
var ti2 = ( uint ) ( node * 3 + 1 ) ;
var ti3 = ( uint ) ( node * 3 + 2 ) ;
if ( ti3 > = it . triangles . length ) throw new Exception ( "Invalid node index" ) ;
Unity . Burst . CompilerServices . Hint . Assume ( ti1 < it . triangles . length & & ti2 < it . triangles . length & & ti3 < it . triangles . length ) ;
var vi1 = it . vertices [ it . triangles [ ti1 ] ] ;
var vi2 = it . vertices [ it . triangles [ ti2 ] ] ;
var vi3 = it . vertices [ it . triangles [ ti3 ] ] ;
if ( it . projection . distanceMetric = = DistanceMetric . Euclidean ) {
var v1 = ( float3 ) vi1 ;
var v2 = ( float3 ) vi2 ;
var v3 = ( float3 ) vi3 ;
Polygon . ClosestPointOnTriangleByRef ( in v1 , in v2 , in v3 , in it . point , out var closest ) ;
var sqrDist = math . distancesq ( closest , it . point ) ;
if ( sqrDist < distanceThresholdSqr ) {
it . indexInLeaf = i + 1 ;
it . current = new CloseNode {
node = node ,
distanceSq = sqrDist ,
tieBreakingDistance = 0 ,
closestPointOnNode = closest ,
} ;
return true ;
}
} else {
Polygon . ClosestPointOnTriangleProjected ( ref vi1 , ref vi2 , ref vi3 , ref it . projection , ref it . point , out var closest , out var sqrDist , out var distAlongProjection ) ;
// Check if this point is better than the previously best point.
// Handling ties here is important, in case the navmesh has multiple overlapping regions (e.g. a multi-story building).
if ( sqrDist < distanceThresholdSqr | | ( sqrDist = = distanceThresholdSqr & & distAlongProjection < it . tieBreakingDistanceThreshold ) ) {
it . indexInLeaf = i + 1 ;
it . current = new CloseNode {
node = node ,
distanceSq = sqrDist ,
tieBreakingDistance = distAlongProjection ,
closestPointOnNode = closest ,
} ;
return true ;
}
}
}
it . indexInLeaf = 0 ;
it . stackSize - - ;
} else {
it . stackSize - - ;
int first = box . left , second = box . right ;
var firstDist = it . projection . SquaredRectPointDistanceOnPlane ( it . tree [ first ] . rect , it . point ) ;
var secondDist = it . projection . SquaredRectPointDistanceOnPlane ( it . tree [ second ] . rect , it . point ) ;
if ( secondDist < firstDist ) {
// Swap
Memory . Swap ( ref first , ref second ) ;
Memory . Swap ( ref firstDist , ref secondDist ) ;
}
if ( it . stackSize + 2 > it . stack . Length ) {
throw new InvalidOperationException ( "Tree is too deep. Overflowed the internal stack." ) ;
}
// Push both children on the stack so that we can explore them later (if they are not too far away).
// We push the one with the smallest distance last so that it will be popped first.
if ( secondDist < = distanceThresholdSqr ) it . stack [ it . stackSize + + ] = new BoxWithDist {
index = second ,
distSqr = secondDist ,
} ;
if ( firstDist < = distanceThresholdSqr ) it . stack [ it . stackSize + + ] = new BoxWithDist {
index = first ,
distSqr = firstDist ,
} ;
}
}
}
}
struct BBTreeBox {
public IntRect rect ;
public int nodeOffset ;
public int left , right ;
public bool IsLeaf = > nodeOffset > = 0 ;
public BBTreeBox ( IntRect rect ) {
nodeOffset = - 1 ;
this . rect = rect ;
left = right = - 1 ;
}
}
public void DrawGizmos ( CommandBuilder draw ) {
Gizmos . color = new Color ( 1 , 1 , 1 , 0.5F ) ;
if ( tree . Length = = 0 ) return ;
DrawGizmos ( ref draw , 0 , 0 ) ;
}
void DrawGizmos ( ref CommandBuilder draw , 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 - min ;
size = new Vector3 ( size . x , 1 , size . z ) ;
center . y + = depth * 2 ;
draw . xz . WireRectangle ( center , new float2 ( size . x , size . z ) , AstarMath . IntToColor ( depth , 1f ) ) ;
if ( ! box . IsLeaf ) {
DrawGizmos ( ref draw , box . left , depth + 1 ) ;
DrawGizmos ( ref draw , box . right , depth + 1 ) ;
}
}
}
}