﻿// 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;
        }
    }
}