#region Using directives

using System;
using System.Collections;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Math;
using OpenTK.Input;
using OpenTK.Platform;

#endregion


namespace DataProcessing
{
    public class Octree
    {
        #region variable definitions

        private OctreeNode parentNode;  //rodicovsky uzel
        private ArrayList nodes;        //kolekce uzlu
        private int depth;              //hloubka grafu
        private bool merge;             //maji se slucovat uzly v predposledni hloubce?
        private int minPoints = 1;      //kolik nejmene muze list obsahovat bodu

        public OctreeNode ParentNode
        {
            get { return parentNode; }
            set { parentNode = value; }
        }

        public ArrayList Nodes
        {
            get { return nodes; }
            set { nodes = value; }
        }
        
        public bool Merge
        {
            get { return merge; }
            set { merge = value; }
        }

        public int MinPoints
        {
            get { return minPoints; }
            set { minPoints = value; }
        }

        #endregion define variables

        //konstruktor
        public Octree(float X, float Y, float Z, float edge, int newDepth)
        {
            nodes = new ArrayList();
            parentNode = new OctreeNode(X, Y, Z, edge);
            depth = newDepth;            
        }

        //rozklad uzlu na 8 potomku
        public void Subdivide(OctreeNode node, int depth, int currentDepth)
        {
            if (currentDepth > depth)
                return;

            float X, Y, Z, newEdge, diff;

            X = node.CentreX;
            Y = node.CentreY;
            Z = node.CentreZ;

            newEdge = node.EdgeLenght / 2;
            diff = node.EdgeLenght / 4;

            // vytvorime oktaly
            node.Octals[0] = new OctreeNode(X - diff, Y - diff, Z - diff, newEdge, node);
            node.Octals[1] = new OctreeNode(X - diff, Y - diff, Z + diff, newEdge, node);
            node.Octals[2] = new OctreeNode(X - diff, Y + diff, Z - diff, newEdge, node);
            node.Octals[3] = new OctreeNode(X - diff, Y + diff, Z + diff, newEdge, node);
            node.Octals[4] = new OctreeNode(X + diff, Y - diff, Z - diff, newEdge, node);
            node.Octals[5] = new OctreeNode(X + diff, Y - diff, Z + diff, newEdge, node);
            node.Octals[6] = new OctreeNode(X + diff, Y + diff, Z - diff, newEdge, node);
            node.Octals[7] = new OctreeNode(X + diff, Y + diff, Z + diff, newEdge, node);

            //a kazdy si vezme svoje body
            node.Octals[0].GetVertices();
            node.Octals[1].GetVertices();
            node.Octals[2].GetVertices();
            node.Octals[3].GetVertices();
            node.Octals[4].GetVertices();
            node.Octals[5].GetVertices();
            node.Octals[6].GetVertices();
            node.Octals[7].GetVertices();

            //pokud je dosazena hloubka vybere uzly, ktere obsahuji vic bodu nez bylo zadano a pridame do kolekce uzlu ze, kterych budou nasledne segmenty
            if (currentDepth == depth)
            {
                int count = 0;
                for (int i = 0; i < 8; i++)
                {
                    if (node.Octals[i].Vertices.Count >= minPoints)
                        count++;
                }

                //merge osm oktalu do rodicovskeho uzlu
                if (count == 8 && merge)
                {
                    nodes.Add(node);
                }
                else
                {
                    for (int i = 0; i < 8; i++)
                    {
                        if (node.Octals[i] != null && node.Octals[i].Vertices.Count >= minPoints)
                            nodes.Add(node.Octals[i]);
                    }
                }
                
                return;
            }

            //rekurze.. dalsi deleni do hloubky
            for (int i = 0; i < 8; i++)
            {
                if (node.Octals[i].Vertices.Count > 0)
                    Subdivide(node.Octals[i], depth, currentDepth + 1);
            }
        }        
    }

    //trida octree uzlu 
    public class OctreeNode
    {
        #region variable definitions

        private float minX, maxX, minY, maxY, minZ, maxZ;
        private ArrayList vertices;     //kolekce bodu, ktere uzel obsahuje
        private OctreeNode parent;      // rodic
        private OctreeNode[] octals;    //potomci
        private float centreX;
        private float centreY;
        private float centreZ;
        private float edgeLenght;

        public ArrayList Vertices
        {
            get { return vertices; }
            set { vertices = value; }
        }

        public OctreeNode[] Octals
        {
            get { return octals; }
            set { octals = value; }
        }

        public float CentreX
        {
            get { return centreX; }
            set { centreX = value; }
        }

        public float CentreY
        {
            get { return centreY; }
            set { centreY = value; }
        }

        public float CentreZ
        {
            get { return centreZ; }
            set { centreZ = value; }
        }

        public float EdgeLenght
        {
            get { return edgeLenght; }
            set { edgeLenght = value; }
        }

        #endregion define variables

        //konstruktor
        public OctreeNode(float X, float Y, float Z, float edge)
        {
            centreX = X;
            centreY = Y;
            centreZ = Z;
            edgeLenght = edge;

            octals = new OctreeNode[8];
            vertices = new ArrayList();
        }

        //pretizeny konstruktor
        public OctreeNode(float X, float Y, float Z, float edge, OctreeNode newParent)
        {
            centreX = X;
            centreY = Y;
            centreZ = Z;
            edgeLenght = edge;

            minX = X - edge / 2;
            maxX = X + edge / 2;
            minY = Y - edge / 2;
            maxY = Y + edge / 2;
            minZ = Z - edge / 2;
            maxZ = Z + edge / 2;

            octals = new OctreeNode[8];
            vertices = new ArrayList();
            parent = newParent;
        }

        //fce ktera prebere patricne body od rodice
        public void GetVertices()
        {
            for (int i = parent.vertices.Count - 1; i != -1; i--)
            {

                Vertex vertex = (Vertex)parent.vertices[i];
                if (Contains(vertex))
                {
                    vertices.Add(vertex);
                    parent.vertices.RemoveAt(i);
                }
            }
        }

        //fce zjistuje zda dany bod rodice patri do meho objemu
        private bool Contains(Vertex vertex)
        {
            if (vertex.X < minX || vertex.X > maxX)
                return false;

            if (vertex.Y < minY || vertex.Y > maxY)
                return false;

            if (vertex.Z < minZ || vertex.Z > maxZ)
                return false;

            return true;
        }        
    }
}
