Files
pgs/Assets/ProCamera2D/Code/Core/KDTree.cs
2026-02-21 16:58:22 -08:00

246 lines
7.0 KiB
C#

// KDTree.cs - A Stark, September 2009.
// This class implements a data structure that stores a list of points in space.
// A common task in game programming is to take a supplied point and discover which
// of a stored set of points is nearest to it. For example, in path-plotting, it is often
// useful to know which waypoint is nearest to the player's current
// position. The kd-tree allows this "nearest neighbour" search to be carried out quickly,
// or at least much more quickly than a simple linear search through the list.
// At present, the class only allows for construction (using the MakeFromPoints static method)
// and nearest-neighbour searching (using FindNearest). More exotic kd-trees are possible, and
// this class may be extended in the future if there seems to be a need.
// The nearest-neighbour search returns an integer index - it is assumed that the original
// array of points is available for the lifetime of the tree, and the index refers to that
// array.
using UnityEngine;
using System.Collections;
namespace Com.LuisPedroFonseca.ProCamera2D
{
public class KDTree
{
public KDTree[] lr;
public Vector3 pivot;
public int pivotIndex;
public int axis;
// Change this value to 2 if you only need two-dimensional X,Y points. The search will
// be quicker in two dimensions.
const int numDims = 3;
public KDTree()
{
lr = new KDTree[2];
}
// Make a new tree from a list of points.
public static KDTree MakeFromPoints(params Vector3[] points)
{
int[] indices = Iota(points.Length);
return MakeFromPointsInner(0, 0, points.Length - 1, points, indices);
}
// Recursively build a tree by separating points at plane boundaries.
static KDTree MakeFromPointsInner(
int depth,
int stIndex, int enIndex,
Vector3[] points,
int[] inds
)
{
KDTree root = new KDTree();
root.axis = depth % numDims;
int splitPoint = FindPivotIndex(points, inds, stIndex, enIndex, root.axis);
root.pivotIndex = inds[splitPoint];
root.pivot = points[root.pivotIndex];
int leftEndIndex = splitPoint - 1;
if (leftEndIndex >= stIndex)
{
root.lr[0] = MakeFromPointsInner(depth + 1, stIndex, leftEndIndex, points, inds);
}
int rightStartIndex = splitPoint + 1;
if (rightStartIndex <= enIndex)
{
root.lr[1] = MakeFromPointsInner(depth + 1, rightStartIndex, enIndex, points, inds);
}
return root;
}
static void SwapElements(int[] arr, int a, int b)
{
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// Simple "median of three" heuristic to find a reasonable splitting plane.
static int FindSplitPoint(Vector3[] points, int[] inds, int stIndex, int enIndex, int axis)
{
float a = points[inds[stIndex]][axis];
float b = points[inds[enIndex]][axis];
int midIndex = (stIndex + enIndex) / 2;
float m = points[inds[midIndex]][axis];
if (a > b)
{
if (m > a)
{
return stIndex;
}
if (b > m)
{
return enIndex;
}
return midIndex;
}
else
{
if (a > m)
{
return stIndex;
}
if (m > b)
{
return enIndex;
}
return midIndex;
}
}
// Find a new pivot index from the range by splitting the points that fall either side
// of its plane.
public static int FindPivotIndex(Vector3[] points, int[] inds, int stIndex, int enIndex, int axis)
{
int splitPoint = FindSplitPoint(points, inds, stIndex, enIndex, axis);
// int splitPoint = Random.Range(stIndex, enIndex);
Vector3 pivot = points[inds[splitPoint]];
SwapElements(inds, stIndex, splitPoint);
int currPt = stIndex + 1;
int endPt = enIndex;
while (currPt <= endPt)
{
Vector3 curr = points[inds[currPt]];
if ((curr[axis] > pivot[axis]))
{
SwapElements(inds, currPt, endPt);
endPt--;
}
else
{
SwapElements(inds, currPt - 1, currPt);
currPt++;
}
}
return currPt - 1;
}
public static int[] Iota(int num)
{
int[] result = new int[num];
for (int i = 0; i < num; i++)
{
result[i] = i;
}
return result;
}
// Find the nearest point in the set to the supplied point.
public int FindNearest(Vector3 pt)
{
float bestSqDist = 1000000000f;
int bestIndex = -1;
Search(pt, ref bestSqDist, ref bestIndex);
return bestIndex;
}
// Recursively search the tree.
void Search(Vector3 pt, ref float bestSqSoFar, ref int bestIndex)
{
float mySqDist = (pivot - pt).sqrMagnitude;
if (mySqDist < bestSqSoFar)
{
bestSqSoFar = mySqDist;
bestIndex = pivotIndex;
}
float planeDist = pt[axis] - pivot[axis]; //DistFromSplitPlane(pt, pivot, axis);
int selector = planeDist <= 0 ? 0 : 1;
if (lr[selector] != null)
{
lr[selector].Search(pt, ref bestSqSoFar, ref bestIndex);
}
selector = (selector + 1) % 2;
float sqPlaneDist = planeDist * planeDist;
if ((lr[selector] != null) && (bestSqSoFar > sqPlaneDist))
{
lr[selector].Search(pt, ref bestSqSoFar, ref bestIndex);
}
}
// Get a point's distance from an axis-aligned plane.
float DistFromSplitPlane(Vector3 pt, Vector3 planePt, int axis)
{
return pt[axis] - planePt[axis];
}
// Simple output of tree structure - mainly useful for getting a rough
// idea of how deep the tree is (and therefore how well the splitting
// heuristic is performing).
public string Dump(int level)
{
string result = pivotIndex.ToString().PadLeft(level) + "\n";
if (lr[0] != null)
{
result += lr[0].Dump(level + 2);
}
if (lr[1] != null)
{
result += lr[1].Dump(level + 2);
}
return result;
}
}
}