|
// src*: https://gist.github.com/andrew-raphael-lukasik/778b8891974d2e18fcfa89854e4f3509 |
|
using Unity.Collections; |
|
using Unity.Mathematics; |
|
using Unity.Jobs; |
|
|
|
using Debug = UnityEngine.Debug; |
|
|
|
[System.Serializable] |
|
public struct QuadTree |
|
{ |
|
|
|
public EType type; |
|
public float2 min, max; |
|
public int pointIndex; |
|
|
|
/// <remarks> Constructs an empty leaf node. </remarks> |
|
public QuadTree ( float2 min , float2 max ) |
|
{ |
|
this.min = min; |
|
this.max = max; |
|
this.pointIndex = -1; |
|
this.type = EType.Leaf; |
|
} |
|
|
|
public enum EType : byte |
|
{ |
|
UNDEFINED = 0 , |
|
Leaf , |
|
Trunk |
|
}; |
|
|
|
public override string ToString () => UnityEngine.JsonUtility.ToJson(this,false); |
|
|
|
public static void Insert ( float2 point , NativeList<QuadTree> quadTreeBuffer , NativeList<float2> pointsBuffer ) |
|
{ |
|
if( quadTreeBuffer.Length!=0 ) |
|
Insert( 0 , point , quadTreeBuffer , pointsBuffer ); |
|
else |
|
Debug.LogError($"NativeList<{nameof(QuadTree)}> is expected to contain at least a single entry already where initial bounds are defined."); |
|
} |
|
/// (mid.x,max.y) |
|
/// (min.x,max.y) +-------+-------+ max |
|
/// | | | |
|
/// | TL | TR | |
|
/// | | | |
|
/// (min.x,mid.y) +------mid------+ (max.x,mid.y) |
|
/// | | | |
|
/// y | BL | BR | |
|
/// | | | | |
|
/// +--x min +-------+-------+ (max.x,min.y) |
|
/// (mid.x,min.y) |
|
static void Insert ( int nodeIndex , float2 point , NativeList<QuadTree> quadTree , NativeList<float2> points ) |
|
{ |
|
if( nodeIndex>=quadTree.Length ) throw new System.Exception($"Given {nameof(nodeIndex)} ({nodeIndex}) must be < than {nameof(quadTree)}.Length ({quadTree.Length})"); |
|
|
|
insert_again: |
|
var node = quadTree[nodeIndex]; |
|
if( node.type==EType.Leaf ) |
|
{ |
|
if( node.pointIndex==-1 )// no point content |
|
{ |
|
// insert point into an empty leaf |
|
int pointIndex = points.Length; |
|
points.Add( point ); |
|
|
|
node.pointIndex = pointIndex; |
|
quadTree[nodeIndex] = node; |
|
} |
|
else// point content |
|
{ |
|
float2 existingPoint = points[node.pointIndex]; |
|
|
|
if( math.all(point==existingPoint) ) |
|
{ |
|
// duplicate detected, ignore it |
|
return; |
|
} |
|
|
|
_Subdivide( nodeIndex , quadTree , points ); |
|
goto insert_again; |
|
} |
|
} |
|
else if( node.type==EType.Trunk ) |
|
{ |
|
int childIndex = ChildIndex( nodeIndex ); |
|
|
|
float2 min = node.min; |
|
float2 max = node.max; |
|
float2 mid = min + ( max - min )/2; |
|
|
|
// we need to go deeper |
|
bool2 greater = point > mid; |
|
if( math.all(greater) ) |
|
{ |
|
// top right |
|
nodeIndex = childIndex + 2; |
|
goto insert_again; |
|
} |
|
else if( greater.x ) |
|
{ |
|
// bottom right |
|
nodeIndex = childIndex + 3; |
|
goto insert_again; |
|
} |
|
else if( greater.y ) |
|
{ |
|
// top left |
|
nodeIndex = childIndex + 1; |
|
goto insert_again; |
|
} |
|
else |
|
{ |
|
// bottom left |
|
nodeIndex = childIndex + 0; |
|
goto insert_again; |
|
} |
|
} |
|
else// undefined node |
|
{ |
|
// this should never happen |
|
// definitely an error somewhere |
|
throw new System.Exception($"ERROR SOMEWHERE; READING AN UNDEFINED NODE:{node}"); |
|
} |
|
} |
|
|
|
static void _Subdivide ( int nodeIndex , NativeList<QuadTree> quadTree , NativeList<float2> points ) |
|
{ |
|
var node = quadTree[nodeIndex]; |
|
|
|
int childIndex = ChildIndex( nodeIndex ); |
|
|
|
// upsize |
|
int childDepth = IndexDepth( nodeIndex:childIndex ); |
|
{ |
|
int requiredLength = SumPowersOf4( childDepth ); |
|
if( quadTree.Length<requiredLength ) |
|
{ |
|
quadTree.Length = requiredLength; |
|
} |
|
} |
|
|
|
float2 min = node.min; |
|
float2 max = node.max; |
|
float2 mid = min + ( max - min )/2; |
|
|
|
QuadTree BL = new QuadTree( min , mid ); |
|
QuadTree TL = new QuadTree( new float2(min.x,mid.y) , new float2(mid.x,max.y) ); |
|
QuadTree TR = new QuadTree( mid , max ); |
|
QuadTree BR = new QuadTree( new float2(mid.x,min.y) , new float2(max.x,mid.y) ); |
|
|
|
// re-insert existing point |
|
int pointIndex = node.pointIndex; |
|
if( pointIndex!=-1 ) |
|
{ |
|
float2 point = points[pointIndex]; |
|
bool2 greater = point > mid; |
|
if( math.all(greater) ) |
|
{ |
|
// top right |
|
TR.pointIndex = pointIndex; |
|
} |
|
else if( greater.x ) |
|
{ |
|
// bottom right |
|
BR.pointIndex = pointIndex; |
|
} |
|
else if( greater.y ) |
|
{ |
|
// top left |
|
TL.pointIndex = pointIndex; |
|
} |
|
else |
|
{ |
|
// bottom left |
|
BL.pointIndex = pointIndex; |
|
} |
|
} |
|
|
|
quadTree[childIndex+0] = BL; |
|
quadTree[childIndex+1] = TL; |
|
quadTree[childIndex+2] = TR; |
|
quadTree[childIndex+3] = BR; |
|
|
|
node.type = EType.Trunk; |
|
node.pointIndex = -1; |
|
quadTree[nodeIndex] = node; |
|
} |
|
|
|
public static int ChildIndex ( int parentIndex ) |
|
{ |
|
return 4 * parentIndex + 1; |
|
} |
|
static int ParentIndex ( in int nodeIndex ) |
|
{ |
|
int nodeDepth = IndexDepth( nodeIndex:nodeIndex ); |
|
return ParentIndex( nodeIndex , nodeDepth ); |
|
} |
|
static int ParentIndex ( in int nodeIndex , int nodeDepth ) |
|
{ |
|
return (int)( SumPowersOf4(nodeDepth-2) -1 + math.ceil( ( (nodeIndex - SumPowersOf4(nodeDepth-1) +1 )) / 4.0 ) ); |
|
} |
|
public static int IndexDepth ( in int nodeIndex ) |
|
{ |
|
int depth = 0; |
|
int power = 0; |
|
int sum = 0; |
|
while( nodeIndex>sum ) |
|
{ |
|
depth++; |
|
power++; |
|
sum += (int)math.pow( 4 , power ); |
|
} |
|
return depth; |
|
} |
|
static int SumPowersOf4 ( in int power ) |
|
{ |
|
int accu = 0; |
|
for( int i=0 ; i<=power ; i++ ) |
|
accu += (int)math.pow( 4.0 , i ); |
|
return accu; |
|
} |
|
public static int NumNodes ( int depth ) |
|
{ |
|
return (int)( ( math.pow(4.0,depth) -1 ) / 3 ); |
|
} |
|
|
|
[Unity.Burst.BurstCompile( CompileSynchronously=true )] |
|
public partial struct InsertJob : IJob |
|
{ |
|
float2 _point; |
|
NativeList<QuadTree> _quadTreeBuffer; |
|
NativeList<float2> _pointsBuffer; |
|
public InsertJob ( float2 point , NativeList<QuadTree> quadTreeBuffer , NativeList<float2> pointsBuffer ) |
|
{ |
|
this._point = point; |
|
this._quadTreeBuffer = quadTreeBuffer; |
|
this._pointsBuffer = pointsBuffer; |
|
} |
|
void IJob.Execute() |
|
{ |
|
QuadTree.Insert( point:_point , quadTreeBuffer:_quadTreeBuffer , pointsBuffer:_pointsBuffer ); |
|
} |
|
} |
|
|
|
} |