Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Point nearest-neighbour search class

Discussion in 'Scripting' started by andeeeee, Sep 7, 2009.

  1. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    A common task in game code is finding which of a stored set of points is nearest to another arbitrary point. For example, you might want to find which waypoint or monster generator is nearest to the player's current position. However, the search is time-consuming when there are a lot of stored points and the search is just looking through all of them to find the nearest.

    I've had a go at implementing a kd-tree, a data structure that speeds up the nearest-neighbour search dramatically (see the attached script file). It is currently a very basic implementation, but I'd be happy to hear suggestions for improvements of functionality or efficiency.
     

    Attached Files:

  2. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    Hey Andeeee,

    Thanks for posting this code. I'd like to use it to learn more about KDTrees as well as Unity.

    Could you give me any basic instructions how to implement your code? I tried dropping the script on a empty GO but I just get the error message - Can't add script behaviour KDTree. The script needs to derive from MonoBehaviour!

    Any advice?

    Thanks.

    Mitch
     
  3. ibyte

    ibyte

    Joined:
    Aug 14, 2009
    Posts:
    1,047
    Hi Andeeee,

    I was wondering if your script could be used to find the nearest player to the current position of the ball as it moves around the field? I thought it might be used it to auto select the closest player?

    iByte
     
  4. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    I made some progress - I don't know c# at all - I barely know unityscript, but I did manage to convert the KDTRee from c# to unityscript. It runs without errors and does seem to compute a KDTree from an array of points.

    But when I perform a FindNearest call on a point, it always returns an index of -1. It never finds the nearest point.

    I'm sure I screwed up the code somewhat when I took a sledgehammer to it and converted to unityscript. Here's the code - if anyone has any advice for me I'd appreciate it.

    I put both of these scripts on and empty GO:

    KDTree.js

    Code (csharp):
    1. var lr : KDTree[];
    2. var pivot : Vector3;
    3. var pivotIndex : int;
    4. var axis : int;
    5.    
    6. //  Change this value to 2 if you only need two-dimensional X,Y points. The search will
    7. //  be quicker in two dimensions.
    8. var numDims : int = 3;
    9.  
    10. function KDTree()
    11. {
    12.     lr = new KDTree[2];
    13. }
    14.  
    15. //  Make a new tree from a list of points.
    16. function MakeFromPoints(points : Vector3[]) : KDTree
    17. {
    18.     var indices : int[] = Iota(points.length);
    19.     return MakeFromPointsInner(0, 0, points.length - 1, points, indices);
    20. }
    21.  
    22. //  Recursively build a tree by separating points at plane boundaries.
    23. function MakeFromPointsInner(depth : int, stIndex : int, enIndex : int, points : Vector3[], inds : int[]) : KDTree
    24. {
    25.     var root : KDTree = this;
    26.     root.KDTree();
    27.     root.axis = depth % numDims;
    28.     var splitPoint : int = FindPivotIndex(points, inds, stIndex, enIndex, root.axis);
    29.  
    30.     root.pivotIndex = inds[splitPoint];
    31.     root.pivot = points[root.pivotIndex];
    32.        
    33.     var leftEndIndex : int = splitPoint - 1;
    34.            
    35.     if (leftEndIndex >= stIndex)
    36.     {
    37.         root.lr[0] = MakeFromPointsInner(depth + 1, stIndex, leftEndIndex, points, inds);
    38.     }
    39.        
    40.     var rightStartIndex : int = splitPoint + 1;
    41.        
    42.     if (rightStartIndex <= enIndex)
    43.     {
    44.         root.lr[1] = MakeFromPointsInner(depth + 1, rightStartIndex, enIndex, points, inds);
    45.     }
    46.        
    47.     return root;
    48. }
    49.  
    50. //  Find a new pivot index from the range by splitting the points that fall either side
    51. //  of its plane.
    52. function FindPivotIndex(points : Vector3[], inds : int[], stIndex : int, enIndex : int, axis : int) : int
    53. {
    54.     var splitPoint : int = FindSplitPoint(points, inds, stIndex, enIndex, axis);
    55.     // int splitPoint = Random.Range(stIndex, enIndex);
    56.  
    57.     var pivot : Vector3 = points[inds[splitPoint]];
    58.     SwapElements(inds, stIndex, splitPoint);
    59.  
    60.     var currPt : int = stIndex + 1;
    61.     var endPt : int = enIndex;
    62.        
    63.     while (currPt <= endPt)
    64.     {
    65.         var curr : Vector3 = points[inds[currPt]];
    66.            
    67.         if ((curr[axis] > pivot[axis]))
    68.         {
    69.             SwapElements(inds, currPt, endPt);
    70.             endPt--;
    71.         }
    72.         else
    73.         {
    74.             SwapElements(inds, currPt - 1, currPt);
    75.             currPt++;
    76.         }
    77.     }
    78.        
    79.     return currPt - 1;
    80. }
    81.  
    82. function SwapElements(arr : int[], a : int, b : int) : void
    83. {
    84.     var temp : int = arr[a];
    85.     arr[a] = arr[b];
    86.     arr[b] = temp;
    87. }
    88.    
    89.  
    90. //  Simple "median of three" heuristic to find a reasonable splitting plane.
    91. function FindSplitPoint(points : Vector3[], inds : int[], stIndex : int, enIndex : int, axis : int) : int
    92. {
    93.     var a : float = points[inds[stIndex]][axis];
    94.     var b : float = points[inds[enIndex]][axis];
    95.     var midIndex : int = (stIndex + enIndex) / 2;
    96.     var m : float = points[inds[midIndex]][axis];
    97.        
    98.     if (a > b)
    99.     {
    100.         if (m > a)
    101.         {
    102.             return stIndex;
    103.         }
    104.            
    105.         if (b > m)
    106.         {
    107.             return enIndex;
    108.         }
    109.            
    110.         return midIndex;
    111.     }
    112.     else
    113.     {
    114.         if (a > m)
    115.         {
    116.             return stIndex;
    117.         }
    118.            
    119.         if (m > b)
    120.         {
    121.             return enIndex;
    122.         }
    123.            
    124.         return midIndex;
    125.     }
    126. }
    127.  
    128. function Iota(num : int) : int[]
    129. {
    130.     var result : int[] = new int[num];
    131.        
    132.     for (var i : int = 0; i < num; i++)
    133.     {
    134.         result[i] = i;
    135.     }
    136.        
    137.     return result;
    138. }
    139.  
    140. //  Find the nearest point in the set to the supplied point.
    141. function FindNearest(pt : Vector3) : int
    142. {
    143.     var bestSqDist : float = Mathf.Infinity;
    144.     var bestIndex : int = -1;
    145.        
    146.     Search(pt, bestSqDist, bestIndex);
    147.        
    148.     return bestIndex;
    149. }
    150.    
    151.  
    152. //  Recursively search the tree.
    153. function Search(pt : Vector3, bestSqSoFar : float, bestIndex : int) : void
    154. {
    155.     var mySqDist : float = (pivot - pt).sqrMagnitude;
    156.        
    157.     if (mySqDist < bestSqSoFar)
    158.     {
    159.         bestSqSoFar = mySqDist;
    160.         bestIndex = pivotIndex;
    161.     }
    162.  
    163.     var planeDist : float = pt[axis] - pivot[axis]; //DistFromSplitPlane(pt, pivot, axis);
    164.        
    165.     var selector : int = planeDist <= 0 ? 0 : 1;
    166.        
    167.     if (lr[selector] != null)
    168.     {
    169.         lr[selector].Search(pt, bestSqSoFar, bestIndex);
    170.     }
    171.        
    172.     selector = (selector + 1) % 2;
    173.        
    174.     var sqPlaneDist : float = planeDist * planeDist;
    175.  
    176.     if ((lr[selector] != null)  (bestSqSoFar > sqPlaneDist))
    177.     {
    178.         lr[selector].Search(pt, bestSqSoFar, bestIndex);
    179.     }
    180. }
    181.    
    182.  
    183. //  Get a point's distance from an axis-aligned plane.
    184. function DistFromSplitPlane(pt : Vector3, planePt : Vector3, axis : int) : float
    185. {
    186.     return pt[axis] - planePt[axis];
    187. }
    and WayPoints.js

    Code (csharp):
    1. var index = 10;
    2. var scale = 50.0;
    3. var wayPoints : Vector3[];
    4.  
    5. var kd : KDTree;
    6.  
    7. function Start ()
    8. {  
    9.     wayPoints = new Vector3[index];
    10.    
    11.     for (var wayPoint : Vector3 in wayPoints)
    12.     {  
    13.         wayPoint.x = Random.Range(-scale, scale);
    14.         wayPoint.y = Random.Range(-scale, scale);
    15.         wayPoint.z = Random.Range(-scale, scale);
    16.     }
    17.    
    18.     kd.MakeFromPoints(wayPoints);
    19.     Debug.Log(kd.FindNearest(Vector3.zero));
    20. }
    21.  
    22. function OnDrawGizmos()
    23. {
    24.     Gizmos.color = Color.white;
    25.     for (var wayPoint : Vector3 in wayPoints)
    26.     {
    27.         Gizmos.DrawSphere(wayPoint, .5);
    28.     }
    29. }
    In the Inspector I connect the KDTree Component to the kd variable in Waypoints.js.

    Thanks in advance for any bits of wisdom.

    Mitch
     
  5. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    iByte,

    Not to long ago I was playing around with players chasing a soccer ball. You probably already know this but the closest player to the ball is not always the best player to go get the ball.

    It's the player that can intercept the ball in the fastest time. And that's a tricky task to compute. Not only do you have to factor in the velocity of the ball and the players, you also need to take into account player acceleration.

    And then there's the issue of the ball's trajectory when it takes the ball over the player's heads.

    I had quite a fun time playing with all that last year. I had some moderate success but quite a few times the wrong player would go get the ball so my math was not quite right.

    Mitch
     
  6. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    Sorry it's taken me a bit of time to get onto this...

    @iByte: The kd-tree isn't suitable for finding the nearest player to the ball in a sports game. The reason is that there is an overhead in preprocessing the data to build the tree at first - this takes longer than a simple search through the points to check which is closest. The kd-tree is only suitable when the set of points is fixed.

    @MitchStan: You don't actually need to reimplement the code in JS unless you really want to. You can just use the class from JS - place the kdtree.cs file in the project's standard assets folder and it will be accessible to a JS script.

    You can't attach the class to an object directly because, as noted, it doesn't derive from MonoBehaviour. You should declare a variable of type KDTree in your code and initialise it with the MakeFromPoints static function:-
    Code (csharp):
    1. var tree: KDTree;
    2. var pointsArray: Vector3[];
    3.  
    4. function Start() {
    5.     tree = KDTree.MakeFromPoints(pointsArray);
    6. }
    Having done that, you can search for the point nearest any arbitrary target point (a player's position, say) using the FindNearest function:-
    Code (csharp):
    1. var nearest: int = tree.FindNearest(targetPoint);
    This function returns an integer, which is an index into the original points array.
     
  7. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    Thanks Andeeee. Works like a charm.

    I was having fun trying to convert it to JS but I couldn't get it to work. I think I'll try for a few more days - just to learn.

    Again, thanks!

    Mitch
     
  8. spinaljack

    spinaljack

    Joined:
    Mar 18, 2010
    Posts:
    992
    Hi Andeee,

    When I type in the script you posted it comes up with the error: The name KDTree does not denote a valid type.

    I've placed the kdtree.cs in the assets folder so I dunno what's wrong.
     
  9. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    Try placing the KDTree script file in the Standard Assets folder. You will need to do this if you are accessing the class from JavaScript.
     
  10. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Hi, andeeeee, very impressive work. By the way, how it would be easiest way to add or delete individual elements in this tree? I am interested to apply this algorithm for my RTS game. However, as game is dynamic (some warriors die, some are created new), vector3 array size would be always changing and I was interested if it would work correctly?
     
  11. sanchoflat

    sanchoflat

    Joined:
    Jun 9, 2013
    Posts:
    14
    Hi all. A lot of time i'm searching a way to find dynamic mooving nearest object. I try to represent my map with zones. But this idea doesn't like me, because i use planes with triggers.
    I have a lot of objects. They move every time in different directions. And every frame they should find the nearest object to them. My algorithm work good, but it has some bugs. I learned little about kd-trees, but how does the work with dynamic objects? Is it fast? Thx and sorry for my english.
     
  12. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    I managed to apply this algorithm for now, but I am facing some difficulties with finding not the first nearest neighbour, but k-th nearest neighbour. I modified the code like that:
    Code (CSharp):
    1.     public void FindNearestR(int suitIndex, Vector3 pt, ref float sqrRmin) {
    2.         float bestSqDist = 1000000000f;
    3.         int bestIndex = -1;
    4.        
    5.        
    6.        
    7.         SearchR(pt, ref bestSqDist, ref bestIndex, ref sqrRmin);
    8.        
    9.     //    int i = bestIndex;
    10.     //    int ii = i-1;
    11.        
    12.        
    13.         sqrRmin = bestSqDist;
    14.         suitIndex = bestIndex-1;
    15.     }
    16.    
    17.  
    18. //    Recursively search the tree.
    19.     void SearchR(Vector3 pt, ref float bestSqSoFar, ref int bestIndex, ref float sqrRmin) {
    20.         float mySqDist = (pivot - pt).sqrMagnitude;
    21.        
    22.         if(mySqDist > sqrRmin){
    23.        
    24.         if (mySqDist < bestSqSoFar) {
    25.            
    26.                 bestSqSoFar = mySqDist;
    27.                 bestIndex = pivotIndex;
    28.            
    29.         //    else{
    30.         //        Debug.Log(mySqDist);
    31.         //    }
    32.            
    33.            
    34.         }
    35.        
    36.         float planeDist = pt[axis] - pivot[axis]; //DistFromSplitPlane(pt, pivot, axis);
    37.        
    38.         int selector = planeDist <= 0 ? 0 : 1;
    39.        
    40.         if (lr[selector] != null) {
    41.             lr[selector].SearchR(pt, ref bestSqSoFar, ref bestIndex, ref sqrRmin);
    42.         }
    43.        
    44.         selector = (selector + 1) % 2;
    45.        
    46.         float sqPlaneDist = planeDist * planeDist;
    47.  
    48.         if ((lr[selector] != null) && (bestSqSoFar > sqPlaneDist)) {
    49.             lr[selector].SearchR(pt, ref bestSqSoFar, ref bestIndex, ref sqrRmin);
    50.         }
    51.         }
    52.     }
    53.  
    Here I used sqrRmin as minimum distance to allow NN to be used. For first NN sqrRmin=0, for 2nd NN it is becoming equal first NN sqrRmin and when function is called it should avoid counting first NN due to this condition. Unfortunately something wrong is going on with mySqDist > sqrRmin and I am always ending up with infinite loop. Does anyone see how it would be possible to fix this?
     
  13. MFen

    MFen

    Joined:
    Sep 25, 2013
    Posts:
    13
    Great resource thank you. I am using it to place a collider on nearest tree as I have anywhere from 200k trees and up, however I am running into this issue when I put all of the trees into one array: maxVertices < 65536 && maxIndices < 65536*3

    I checked to ensure my array could handle the amount of entities I was feeding it and that didn't break until I put in about 10Mil Vector3's, all I need is about 200-500k Vector3's. If you can respond to the reasoning for this error and whether or not there may be a fix for it or if thats just the max it can do that would be great thank you.
     
  14. hot-knife-digital

    hot-knife-digital

    Joined:
    Oct 22, 2013
    Posts:
    12
    Just want to point out that the code snippet quoted above is Java and not C Sharp, for any nube (like me) that come by this thread. This bit below.
    Code (JavaScript):
    1. var tree: KDTree;
    2. var pointsArray: Vector3[];
    3. function Start() {
    4.     tree = KDTree.MakeFromPoints(pointsArray);
    5. }
     
  15. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Here I updated this class with additional functions, which allows to search for k-th nearest neighbour(s) or return distances to them instead of indices.

    P.S. I checked upon Unity 5 and it allows to build very large trees (I tried up to 1.5 million Vector3's and it's working fine).
     

    Attached Files:

    fxcarl likes this.
  16. Wahooney

    Wahooney

    Joined:
    Mar 8, 2010
    Posts:
    281
    Hi,
    Is it possible to find all points in a radius around the test point?
     
  17. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    You can keep calling:
    Code (CSharp):
    1. FindNearestK(Vector3 pt, int k)
    function with larger k orders until the distance to the found point becomes larger than your critical radius. This should run with O(m*n log n) performance, where m would be number of points inside your radius. It's going towards O(N*N) if your cut-off radius is getting close to the point where all dataset points are being added (so keep your cut-off radius reasonable).

    I should add soon that function into the class when I will be looking there next time.
     
  18. TH_Unity

    TH_Unity

    Joined:
    Jul 9, 2013
    Posts:
    11
    This is exactly a functionality I need at the moment. I guess the common usecase here is to find neighbors to certain points and establish a connection to each point in a radius.
    A proper method for that would be great, but right now i am going to do it the way you described
     
  19. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    I think it depends what exactly you are doing. If you need to find just the nearest one (i.e. find the closest tree for worker to be chopped in the forest to collect logs) or to get the environment properties (i.e. find how big is the sphere for 6 neighbours, calculate densities, etc.). It is good tip to use fixed number of points, as the loop will take always the same time to calculate rather than the fixed radius (i.e. in radius = 5 you can find sometimes 3 neighbours, while other time you can find 3000 neighbours, and if you get very large number of neighbours, the loops will become very heavy and your game could freeze).
     
  20. TH_Unity

    TH_Unity

    Joined:
    Jul 9, 2013
    Posts:
    11
    I just tested it for a grid that is a 20x20 units area with one point at each point (x, y). so (0,0),(1,0),(2,0),...,(0,1),(0,2),...,(i,j),...(19,19).

    When I now try to find the neighbors within a 1 unit radius i get this as a result. Am i doing something wrong here? I use node.position (e.g. (14,6)) as an input position for the nearest k search



    UPDATE: I think i found the reason for this to happen. Basically the nearest k-th algorithm is not safe for an environment where two points have the exact same distance to a certain position.
    Code (CSharp):
    1.         if ( mySqDist < bestSqSoFar )
    2.         {
    3.             if ( mySqDist > minSqDist )
    4.             {
    5.                 bestSqSoFar = mySqDist;
    6.                 bestIndex = pivotIndex;
    7.             }
    8.         }
    This part ignores the second point with the same distance to the point as "minSqDist" and therefore will not be noted. This means that you might miss proper points here and the result of the k search will be false, since there is one (or more) point(s) that are nearer to the given location than the returned point

    UPDATE 2:
    Fixed the issue with the following changes:
    Code (CSharp):
    1.     void SearchK( Vector3 pt, HashSet<int> p_pivotIndexSet, ref float bestSqSoFar, ref float minSqDist, ref int bestIndex )
    2.     {
    3.         float mySqDist = ( pivot - pt ).sqrMagnitude;
    4.  
    5.         if ( mySqDist < bestSqSoFar )
    6.         {
    7.             if ( mySqDist >= minSqDist && !p_pivotIndexSet.Contains( pivotIndex ) )
    8.             {
    9.                 bestSqSoFar = mySqDist;
    10.                 bestIndex = pivotIndex;
    11.             }
    12.         }
    Code (CSharp):
    1.     public int FindNearestK( Vector3 pt, int k )
    2.     {
    3.         // Find and returns    k-th nearest neighbour
    4.         float bestSqDist = 1000000000f;
    5.         float minSqDist = -1.0f;
    6.         int bestIndex = -1;
    7.         HashSet<int> _pivotIndexSet = new HashSet<int>();
    8.  
    9.         for ( int i = 0; i < k - 1; i++ )
    10.         {
    11.             SearchK( pt, _pivotIndexSet, ref bestSqDist, ref minSqDist, ref bestIndex );
    12.             _pivotIndexSet.Add( bestIndex );
    13.  
    14.             minSqDist = bestSqDist;
    15.             bestSqDist = 1000000000f;
    16.             bestIndex = -1;
    17.         }
    18.  
    19.         SearchK( pt, _pivotIndexSet, ref bestSqDist, ref minSqDist, ref bestIndex );
    20.  
    21.         return bestIndex;
    22.     }
     
    Last edited: May 23, 2016
  21. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Hmm, I wouldn't use KDTree for the grid if points are exactly on the grid with fixed cell size. KDTree is probably best if you have completely randomly distributed points in space. If you have them in the grid, you can use x and y indices to find nearest point. For example if you have cell (8,7) the nearest points would be (7,7), (9,7), (8,6) and (8,8). You can use internal loop between (x-1,y-1) and (x+1,y+1) as for a grid to get neighbours. It might be faster and better defined that way.
     
  22. TH_Unity

    TH_Unity

    Joined:
    Jul 9, 2013
    Posts:
    11
    You're absolutly right about a pure grid. I should have added that I use the grid just as a starting point. The final cluster is nothing that can be put into a pattern since you can drag points around, add new ones and pretty much organize them in an unpredictable pattern. However, the KD Tree is sufficient for this as well.

    You should be aware though that the current implemention of the k search is flawed. So as mentioned, for two points with the exact same distance to a point only one point will be returned. Using the hashset fixes that and does not produce a noticable overhead
     
    Last edited: May 24, 2016
  23. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    I think you was using FindNearestK function, which returns exact k-th neighbour. For example if you call ind = FindNearest(pt, 6), you will always get 6-th nearest neighbour (there won't be 5th, 4th, 7th and other neighbours as this returns only a single integer).

    You can try using FindNearestsK, which is just a bit below in the class (lines 228 - 249 in my last version). This one should return an array of all neighbours up to k-th, i.e. in previous example it would return all 6 neighbours. You can check if in the array points are added correctly when using it with grid distributed points.

    P.S. Did you tried to check how much performance is drained by using hashtags vs not using them? I.e. you can check by looping over several thousand points to find their neighbours while switching between one version and another. I suspect there could be significant FPS drop on Hashtags version.
     
    Last edited: May 24, 2016
  24. TH_Unity

    TH_Unity

    Joined:
    Jul 9, 2013
    Posts:
    11
    I did use the method with return value int[] (FindNearests). The thing is that you WILL NOT get the k-th nearest neighbor if there are two points with the same distance to a certain point. This may be a question of definition, but take a look at this example:
    You have 3 points: (1, 0), (0, 1) and (1, 1). You now want to find the second nearest neighbor to the point (0, 0). So you would do "FindNearest( Vector3.zero, 2);". This will return the point (1, 1), but not (0, 1).
    If you use "FindNearests" this becomes even worse, since you will get an indices array of the points [ (1, 0), (1, 1)]. But this are clearly NOT the two nearest points to (0, 0), but (1, 0) and (0, 1) are. So you will have a false result when you query the index of the second nearest point, since this is the index of (1, 1) instead of (0, 1).

    I have another question: Is there a way to do a "Nearest Point to ray" query? So that you shoot a ray and will the point with the closest distance to the ray will be returned? I can only imagine a numerical approach where you check the distance of the closest point for a fixed step distance and a maximum distance and return the point with the lowest distance to the ray. Any other ideas?
     
  25. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Oh, I see your point. It's probably that FindNearests are finding always the same point (first or last) if distances are exactly the same. Masking already found points, like you did with hashsets helps of course, but I am worried a bit about performance - did you checked in a way I described in previous post "P.S." ? I am interested in "Contains" function (this one acts as a loop), as it is in the deepest SearchK function, which itself does not have any loops. That could make search much slower. If you don't have performance checking class, let me know, I can post mine.

    Distance to the ray seems to be a different approach, where KDTree doesn't suppose to find it. If you have just a single ray, you can try something like this:
    http://answers.unity3d.com/questions/62644/distance-between-a-ray-and-a-point.html

    If you have many hundreds of thousands of rays, it may need some tweaking how to select best possible candidates list to avoid N^2 calculations. There are also different functions for line segments, planes and so on, i.e. check this page:
    http://wiki.unity3d.com/index.php/3d_Math_functions

    [Update] I just noticed that these HashSets has O(1) performance on checking containment, while Lists has O(N) performance, so they might be not making big impact. However, it might be worthwhile to keep in mind that if there is some noticeable performance issues, it could be good to keep a new function as a separate one, as KDTrees are mostly beneficial when there are no exactly equal distances.
     
    Last edited: May 24, 2016
  26. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I was recently playing with the new job system in Unity 2018.1.0b8 and managed to jobify this kdtree. I placed the project with examples on GitHub:
    https://github.com/chanfort/kdtree-jobified-unity

    In order to jobify, I converted kdtree class into struct and also set 4 arrays to store kdtree data instead of recursive tree of objects. The content includes the old kdtree class, the new kdtree struct and the example in KDtest.cs .

    The example test is set to compare performance between the old kdtree and the new jobified one. The test runs with 5000 points kdtree build at startup. 5000 other query points are searching for their nearest neighbours in the kdtree on every update.

    The options changes by pressing A (build in arrays and original kdtree class), B (new kdtree struct and native arrays) and C (jobified (multithreaded) kdtree with IJobParallelFor usage over neighbour search) keys in the keyboard.

    The performance is as follows:

    Editor: A - 28 FPS, B - 12 FPS, C - 23 FPS
    When build: A - 64 FPS, B - 58 FPS, C - 125 FPS

    So at the moment, improvements are only visible when running the build project (not running in Editor).

    I will update these numbers when Unity will release Burst compiler (likely 2018.2). So stay tuned - looks very promising.
     
    Last edited: Feb 22, 2018
    chrismarch and hungrybelome like this.
  27. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Is B) and C) done with the IL2CPP using the preview build which contains optimizations for NativeArray?

    I'll be very surprised if builtin arrays are faster than native arrays in il2cpp with the preview build.
    If not I'd be really curious to see what framerate is like with the preview il2cpp build.

    (https://forum.unity.com/threads/job-system-not-as-fast-as-mine-why-not.518374/#post-3397587)

    As a general advice. I really recommend measuring not FPS but the actual time spent on the job in ms. It gives a much more accurate picture of speedup / slow downs.
     
  28. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So just installed 2018.1.0b9 and tested for three cases:

    Editor: A - 34 ms, B - 80 ms, C - 51 ms
    Mono build: A - 15 ms, B - 17 ms, C - 7.1 ms
    IL2CPP build: A - 6.8 ms, B - 6.1 ms, C - 3.7 ms

    This time I used the mean deltaTime in ms. So, indeed IL2CPP speeds up native arrays. Now all points make sense :) .
     
  29. ditzel

    ditzel

    Joined:
    Aug 6, 2016
    Posts:
    3
  30. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Nice. Would be interesting to see how your kdtree compares with andeee made kd-tree in terms of performance, i.e. measuring stress test FPS or ms with thousands of points every update.
     
  31. bartofzo

    bartofzo

    Joined:
    Mar 16, 2017
    Posts:
    150
    Thanks for this, really useful!

    I did find a mistake in there though. When using RemoveAll or RemoveAt, it was possible that the Next reference was still pointing to the removed item, which caused weird problems ofcourse. Fixed it with this:

    Code (CSharp):
    1. /// <summary>
    2.     /// Remove at position i (position in list or loop)
    3.     /// </summary>
    4.     public void RemoveAt(int i)
    5.     {
    6.         var list = new List<KdNode>(_getNodes());
    7.         list.RemoveAt(i);
    8.         Clear();
    9.         foreach (var node in list)
    10.         {
    11.             node.next = null;
    12.             _add(node);
    13.         }
    14.     }
    15.  
    16.     /// <summary>
    17.     /// remove all objects that matches the given predicate
    18.     /// </summary>
    19.     /// <param name="match">lamda expression</param>
    20.     public void RemoveAll(Predicate<T> match)
    21.     {
    22.         var list = new List<KdNode>(_getNodes());
    23.         list.RemoveAll(n => match(n.component));
    24.         Clear();
    25.         foreach (var node in list)
    26.         {
    27.             node.next = null;
    28.             _add(node);
    29.         }
    30.     }