#pragma once

#include "VrutApi.h"
#include "BinTrees.h"
#include <deque>
#include <vector>
#include "bvolumes.h"

namespace bTree
{
	/////////////////// Frustum intersection
	// Basically copied from VRUT::Camera, but it allows to use it without having Camera (I need just projection matrix)
	class VRUT_CORE_API CullingPlanes
	{
	public:
		///Frustum plane enumerator
		enum FRUSTUM_PLANE
		{
			FRUSTUM_NEAR = 0,  ///<Near plane index
			FRUSTUM_FAR,       ///<Far plane index
			FRUSTUM_LEFT,      ///<Left plane index
			FRUSTUM_RIGHT,     ///<Right plane index
			FRUSTUM_TOP,       ///<Top plane index
			FRUSTUM_BOTTOM,    ///<Bottom plane index
			FRUSTUM_EXTRA1,    ///<Plane used for Mirror culling
			FRUSTUM_EXTRA2     ///<Plane used for Mirror culling
		};

		///Intersection result
		enum VFC_RESULT
		{
			OUTSIDE = 0,    ///<Object is outside VF
			INSIDE,         ///<Object is inside VF
			INTERSECT       ///<Object is partially inside and partially outside
		};

		std::vector<Vector4> planes;

		/// Input is the transpose of view-projection matrix. Creates clip planes of given clip volume
		///
		/// The planes are not normalized. As long as we need only OUTSIDE/INSIDE result, and not plane-point distances, there is no need to normalize them.
		void FromMatrixVP_T(const Matrix &viewproj_t, const VRUT::AABB &clip_volume);
		/// Uses AABB::DefaultClipVolume if clip_volume is nullptr
		void FromMatrixVP_T(const Matrix &viewproj_t, const VRUT::AABB *clip_volume = nullptr)
		{
			FromMatrixVP_T(viewproj_t, clip_volume ? *clip_volume : VRUT::AABB::DefaultClipVolume);
		}

		void FromMatrixVPinv(const Matrix &viewproj_inv);			// (VP)-1, no V*(P-1), no (V-1)*(P-1), ...
		void FromMatrixVinv_P(const Matrix &view_inv, const Matrix &proj)
		{
			Matrix proj_inv = proj;
			proj_inv.InvertThisPrecise();

			Matrix viewproj_inv = proj_inv * view_inv;				// (VP)-1 = P-1 * V-1
			FromMatrixVPinv(viewproj_inv);
		}
		void FromMatrixV_P(const Matrix &view, const Matrix &proj)
		{
			Matrix view_inv = view;
			view_inv.InvertThisPrecise();
			FromMatrixVinv_P(view_inv, proj);
		}
		void FromMatrixVinv_P_centeredCam(const Matrix &view_inv, const Matrix &proj)
		{
			// TODO This method is used nowhere. It was created for rendergl2, but it is not used anymore.
			//		This method is however a bit better than FromMatrixVPinv, because it should work with LHS as well as with RHS.
			//		Its more general version is FromMatrixVP_T.
			Matrix mat(proj.Transpose() * view_inv.InverseOrto().Transpose());
			planes.resize(6);
			planes[FRUSTUM_NEAR]   = Vector4(mat._m41 + mat._m31, mat._m42 + mat._m32, mat._m43 + mat._m33, mat._m44 + mat._m34).PlaneNormalize();
			planes[FRUSTUM_FAR]    = Vector4(mat._m41 - mat._m31, mat._m42 - mat._m32, mat._m43 - mat._m33, mat._m44 - mat._m34).PlaneNormalize();
			planes[FRUSTUM_LEFT]   = Vector4(mat._m41 + mat._m11, mat._m42 + mat._m12, mat._m43 + mat._m13, mat._m44 + mat._m14).PlaneNormalize();
			planes[FRUSTUM_RIGHT]  = Vector4(mat._m41 - mat._m11, mat._m42 - mat._m12, mat._m43 - mat._m13, mat._m44 - mat._m14).PlaneNormalize();
			planes[FRUSTUM_TOP]    = Vector4(mat._m41 - mat._m21, mat._m42 - mat._m22, mat._m43 - mat._m23, mat._m44 - mat._m24).PlaneNormalize();
			planes[FRUSTUM_BOTTOM] = Vector4(mat._m41 + mat._m21, mat._m42 + mat._m22, mat._m43 + mat._m23, mat._m44 + mat._m24).PlaneNormalize();
		}
		//points in order: TopLeftBack, BottomLeftBack, BottomRightBack, /*TopRightBack,*/ TopRightForward, TopLeftForward, BottomRightForward/*, BottomLeftForward*/
		bool FromPoints(const std::vector<Vector3> &points)
		{
			if (points.size()!=6)
				return false;
			planes.resize(6);
			planes[FRUSTUM_NEAR]	= Vector4::PlaneFromPoints(points[0], points[2], points[1]);
			planes[FRUSTUM_FAR]		= Vector4::PlaneFromPoints(points[5], points[3], points[4]);
			planes[FRUSTUM_LEFT]	= Vector4::PlaneFromPoints(points[0], points[1], points[4]);
			planes[FRUSTUM_RIGHT]	= Vector4::PlaneFromPoints(points[2], points[3], points[5]);
			planes[FRUSTUM_TOP]		= Vector4::PlaneFromPoints(points[0], points[4], points[3]);
			planes[FRUSTUM_BOTTOM]	= Vector4::PlaneFromPoints(points[1], points[2], points[5]);
			return true;
		}
		bool FromReflection(const VRUT::AABB &aabb, const Matrix &aabbMat, const Vector3 &cameraPosition, const Vector4 &reflectionPlane, const Vector3 &reflectedCameraPosition, const float farPlane, VRUT::BSphere &bsphere/*,
			std::vector<Vector3> *frustumPoints=NULL*/)
		{
			//build list of point indices (the edge of AABB faces visible from camera position)
			unsigned int quads[]={0, 1, 3, 2,  0, 2, 6, 4,  0, 4, 5, 1,   5, 4, 6, 7,  1, 5, 7, 3,  2, 3, 7, 6};
			float sideDot[6];
			float sideArea[6];
			for (size_t i=0; i<6; ++i)
			{
				//compute angle to camera
				Vector3 p1(aabbMat.TransformCoord(aabb.GetVertex(quads[i*4])));
				Vector3 p2(aabbMat.TransformCoord(aabb.GetVertex(quads[i*4+1])));
				Vector3 p3(aabbMat.TransformCoord(aabb.GetVertex(quads[i*4+3])));
				sideArea[i]=p1.TriangleSurfaceArea(p2, p3);//it should be *2 for rectangle
				if (sideArea[i]==0)
					sideDot[i]=-1;//the AABB face is degenerated so we need not to use it
				else
					sideDot[i]=(p2-p1).Cross(p3-p1).Dot(cameraPosition-p1);
			}
			//use only one AABB side if it is dominant
			if ((sideArea[0]>1000*sideArea[1]) && (sideArea[0]>1000*sideArea[2]))
				sideDot[1]=sideDot[2]=sideDot[4]=sideDot[5]=-1;
			else if ((sideArea[1]>1000*sideArea[0]) && (sideArea[1]>1000*sideArea[2]))
				sideDot[0]=sideDot[2]=sideDot[3]=sideDot[5]=-1;
			else if ((sideArea[2]>1000*sideArea[0]) && (sideArea[2]>1000*sideArea[1]))
				sideDot[0]=sideDot[1]=sideDot[3]=sideDot[4]=-1;
			std::vector<unsigned int> points;
			for (size_t ii=0; ii<3; ++ii)
			{
				//check if angle to camera is less the PI/2, oposit sides of AABB cannot be used together
				size_t i=0;
				if (sideDot[ii]<0)
				{
					if (sideDot[ii+3]<0)
						continue;
					else
						i=ii+3;
				}
				else
				{
					if (sideDot[ii+3]<0)
						i=ii;
					else
						i=(sideDot[ii]>sideDot[ii+3])?ii:ii+3;
				}
				/*if (frustumPoints) //add all active quads
				{
					for (int j = 0; j < 4; ++j)
						frustumPoints->push_back(aabbMat.TransformCoord(aabb.GetVertex(quads[i*4+j])));
				}*/
				if (points.empty())
				{
					//add whole quad
					for (size_t j=0; j<4; ++j)
						points.push_back(quads[i*4+j]);
				}
				else
				{
					//merge quad with current polygon
					std::vector<unsigned int> tmpPoints;
					for (size_t j=0; j<4; ++j)
						tmpPoints.push_back(quads[i*4+j]);
					int lastIsIn=-1;
					for (size_t j=0; j<tmpPoints.size()+1; ++j)
					{
						int isIn=-1;
						for (size_t k=0; k<points.size(); ++k)
						{
							if (tmpPoints[j%tmpPoints.size()]==points[k])
							{
								isIn=k;
								break;
							}
						}
						if ((lastIsIn>=0) && (isIn==-1))
						{
							//insert the two new points on correct place
							points.insert(points.begin()+lastIsIn+1, tmpPoints[j%tmpPoints.size()]);
							points.insert(points.begin()+lastIsIn+2, tmpPoints[(j+1)%tmpPoints.size()]);
							break;
						}
						lastIsIn=isIn;
					}
					//filter out "returning" lines
					for (size_t j=0; j<points.size(); ++j)
					{
						if (points[j]==points[(j+2)%points.size()])
						{
							points.erase(points.begin()+j);
							points.erase(points.begin()+(j%points.size()));
						}
					}
				}
			}
			//build super quad projected on reflection plane
			if (points.size()<4)
				return false;
			std::vector<Vector3> pointsTr;//transformed points projected on reflection plane
			for (size_t i=0; i<points.size(); ++i)
			{
				Vector3 lpoint(aabbMat.TransformCoord(aabb.GetVertex(points[i])));
				Vector3 lnorm(lpoint-cameraPosition);
				Vector3 point;
				reflectionPlane.PlaneLineIntersection(lnorm, lpoint, point);
				pointsTr.push_back(point);
			}
			//filter out identical points
			for (size_t i=1; i<points.size();)
			{
				if (pointsTr[i-1]==pointsTr[i])
				{
					points.erase(points.begin()+i);
					pointsTr.erase(pointsTr.begin()+i);
				}
				else
					++i;
			}
			std::vector<Vector3> points3D;
			if (points.size()==4)
				points3D.assign(pointsTr.begin(), pointsTr.end());
			else
			{
//#ifdef MIRROR_PLANES_WITH_MINIMAL_QUAD
				//now we must find minimal super quad
				std::vector<Vector3> pointsTrRep;//replacement points (one point replaces two, ie. pointsTrRep[i] replaces pointsTr[i] and pointsTr[i+1])
				std::vector<float> pointsTrRepArea;//areas added by replacement points
				for (size_t i=0; i<pointsTr.size(); ++i)
				{
					//replacement point lies on intersections of "outer" planes
					Vector4 p1(Vector4::PlaneFromPoints(pointsTr[(i-1)%pointsTr.size()], pointsTr[i], reflectedCameraPosition));
					Vector4 p2(Vector4::PlaneFromPoints(pointsTr[(i+1)%pointsTr.size()], pointsTr[(i+2)%pointsTr.size()], reflectedCameraPosition));
					Vector3 lnorm, lpoint;
					if (!p1.PlanePlaneIntersection(p2, lnorm, lpoint))
					{
						pointsTrRep.push_back(Vector3(FLT_MAX, FLT_MAX, FLT_MAX));
						pointsTrRepArea.push_back(FLT_MAX);
						continue;
					}
					//we will use point that is on reflection plane
					Vector3 point;
					if (fabs(reflectionPlane.PlaneLineIntersection(lnorm.Normalize(), lpoint, point))<1e-6)
						point=lpoint;
					pointsTrRep.push_back(point);
					std::vector<float> coefs;
					coefs.push_back((pointsTr[i]-point).Length());
					coefs.push_back((pointsTr[(i+1)%pointsTr.size()]-pointsTr[i]).Length());
					coefs.push_back((point-pointsTr[(i+1)%pointsTr.size()]).Length());
					std::sort(coefs.begin(), coefs.end());
					pointsTrRepArea.push_back(point.TriangleSurfaceArea(pointsTr[i], pointsTr[(i+1)%pointsTr.size()])+((coefs[0]!=0)?coefs[2]:0));
				}
				//now we will replace points and try to find combination with minimal quad surface area
				if (points.size()==5)
				{
					size_t minInd=0;
					float minSurfaceArea=FLT_MAX;
					for (size_t i = 0; i < pointsTrRepArea.size(); ++i)
					{
						if (pointsTrRepArea[i]<minSurfaceArea)
						{
							minSurfaceArea=pointsTrRepArea[i];
							minInd=i;
						}
					}
					std::vector<Vector3> curQuad;
					for (size_t k=0; k<points.size(); ++k)
					{
						if (k==minInd)
							curQuad.push_back(pointsTrRep[k]);
						else if (k!=(minInd+1)%points.size())
							curQuad.push_back(pointsTr[k]);
					}
					points3D.clear();
					for (size_t k=0; k<curQuad.size(); ++k)
						points3D.push_back(curQuad[curQuad.size()-k-1]);
				}
				else //if (points.size()==6)
				{
					float minSurfaceArea=FLT_MAX;
					for (size_t i=0; i<points.size()-2; ++i)
					{
						if (pointsTrRep[i].x==FLT_MAX)
							continue;
						for (size_t j=i+2; j<points.size(); ++j)
						{
							if (pointsTrRep[j].x==FLT_MAX)
								continue;
							//now we should replace unitPoly i and i+1 by unitPointsRep[i] and unitPoly j and j+1 by unitPointsRep[j] and compute quad surface area
							float surfaceArea=FLT_MAX;
							std::vector<Vector3> curQuad;
							for (size_t k=0; k<points.size(); ++k)
							{
								if ((k==i) || (k==j))
									curQuad.push_back(pointsTrRep[k]);
								else if ((k!=(i+1)%points.size()) && (k!=(j+1)%points.size()))
									curQuad.push_back(pointsTr[k]);
							}
							//now compute surface area, we have precomputed additional area caused by point replacement, it is sufficient to use it
							surfaceArea=pointsTrRepArea[i]+pointsTrRepArea[j];
							if (surfaceArea<minSurfaceArea)
							{
								minSurfaceArea=surfaceArea;
								points3D.clear();
								for (size_t k=0; k<curQuad.size(); ++k)
									points3D.push_back(curQuad[curQuad.size()-k-1]);
							}
						}
					}
				}
				/*if (frustumPoints) //add transformed aabb
				{
					frustumPoints->push_back(reflectedCameraPosition);
					for (int i = 0; i < pointsTr.size(); i++)
					{
						frustumPoints->push_back(aabbMat.TransformCoord(aabb.GetVertex(points[i])));
						//frustumPoints->push_back(pointsTr[i]);
						//frustumPoints->push_back(pointsTrRep[i]);
					}
				}*/
			}
			if (points3D.size()<4)
				return false;
			//now we will check order (CW, CCW)
			if ((points3D[2]-points3D[1]).Cross(points3D[3]-points3D[1]).Dot(cameraPosition-points3D[1])>0)
			{
				//we need oposit order of points
				Vector3 tmp=points3D[0];
				points3D[0]=points3D[3];
				points3D[3]=tmp;
				tmp=points3D[1];
				points3D[1]=points3D[2];
				points3D[2]=tmp;
			}
			//we also need to generate BSphere
			VRUT::AABB camAABB;
			for (size_t i=0; i<points3D.size(); ++i)
				camAABB.ExpandWithPoint(points3D[i]);
			/*if (frustumPoints) //add eight frustum points
			{
				frustumPoints->assign(points3D.begin(), points3D.end());
				for (int i = 0; i < 4; i++)
					frustumPoints->push_back(reflectedCameraPosition+(frustumPoints->at(i)-reflectedCameraPosition).Normalize()*farPlane);
			}*/
			//make the final points for plane generation
			//TODO: Now we should to do intersection with base camera frustum (to do narrower frustum in case that the mirror geometry is partially outside screen)
			//compute forward projections as extending line from camera through back points with given distance from camera
			Vector3 TopRightForward		= reflectedCameraPosition+(points3D[3]-reflectedCameraPosition).Normalize()*farPlane;
			points3D.pop_back();
			points3D.push_back(TopRightForward);
			points3D.push_back(reflectedCameraPosition+(points3D[0]-reflectedCameraPosition).Normalize()*farPlane);
			points3D.push_back(reflectedCameraPosition+(points3D[2]-reflectedCameraPosition).Normalize()*farPlane);
			//continue with BSphere
			for (size_t i=3; i<points3D.size(); ++i)
				camAABB.ExpandWithPoint(points3D[i]);
			camAABB.ExpandWithPoint(reflectedCameraPosition+(points3D[1]-reflectedCameraPosition).Normalize()*farPlane);
			bsphere.Center=camAABB.Center();
			bsphere.Radius=camAABB.Radius();
			/*if (frustumPoints) //add all points used for planes
				frustumPoints->insert(frustumPoints->end(), points3D.begin(), points3D.end());*/
			return FromPoints(points3D);
		}

		// *last_plane is a pointer to index of the plane that last culled the aabb
		// *plane_mask a binary mask of planes which need not to be tested (e.g. because aabb's parent is inside the plane)
		// If (last_plane == nullptr) then it behaves as if *last_plane = 0 and no value is passsed out
		// If (plane_mask == nullptr) then it behaves as if *plane_mask = 0 and no value is passsed out
		static VFC_RESULT InFrustum(const CullingPlanes &planes, const VRUT::AABB &aabb, unsigned char *last_plane, unsigned char *plane_mask);

		// Simpler version of InFrustum that does not handle 'last_plane' and 'plane_mask'.
		static VFC_RESULT InFrustumSimpler(const CullingPlanes &planes, const VRUT::AABB &aabb);

		// Simplest version of InFrustum, returns false if outside, true if inside or intersecting.
		// plane_transf is used to transform the planes before the test if done
		static bool InFrustumSimplest(const CullingPlanes &planes, const VRUT::AABB &aabb);
		static bool InFrustumSimplest(const CullingPlanes &planes, const Matrix &plane_transf, const VRUT::AABB &aabb);

		// Returns the volume of AABB as six clip planes
		static void AABBToClipPlanes(std::vector<Vector4> &clip_planes, const VRUT::AABB &clip_volume);
		static std::vector<Vector4> AABBToClipPlanes(const VRUT::AABB &clip_volume);
	};
	
	/*
	* Cull - BVH Culling
	*
	* Basic version of cull - void Cull(TreeNodeType *root, const CullingPlanes &planes, NodeSplitter &node_splitter) may be called on any version of binary tree that have AABB.
	* NodeSplitter must have two methods:
	*		void NodeSplitter::InsertSubTree(TreeNodeType *node);		- called when the whole subtree of 'node' lies in frustum
	*		void NodeSplitter::InsertLeaf(TreeNodeType *node);			- called when leaf node 'node' lies in frustum
	*
	* There are several extensions:
	*   - void Cull(TreeNodeType *root, const CullingPlanes &planes, const VRUT::BSphere &cam_bsphere, NodeSplitter &node_splitter)
	*		- there is a bounding sphere test before frustum test. TreeNodeType must have the following method: const VRUT::BSphere &GetBSphere() const
	*   - void Cull(TreeNodeType *root, const CullingPlanes &planes, int camera_index, NodeSplitter &node_splitter)
	*		- cull saves the index of the last plane that culled the object and starts the next culling with that plane
	*		- TreeNodeType must have vector PerCameraLastCullingPlaneIndex containing per camera an index of the last plane
	*		- camera_index is an index of camera that performs the culling, useful when there are several cameras (main camera, environment probe, ...)
	*   - void Cull(TreeNodeType *root, const CullingPlanes &planes, int camera_index, const VRUT::BSphere &cam_bsphere, NodeSplitter &node_splitter)
	*		- combination of both above
	*
	*	TODO - find a way to unify four Cull functions without loosing the possibility to call it on BVH without sphere etc.
	*/
	template <class TreeNodeType, class NodeSplitter> void Cull(TreeNodeType *root, const CullingPlanes &planes, NodeSplitter &node_splitter)
	{
		if (!root)		return;

		// Plane mask says which planes needs not to be tested, because one of node's parents where tested
		// by the plane and the result was "INSIDE"
		typedef std::pair<TreeNodeType *, unsigned char>		BVHNodePlaneMaskPair;

		std::deque<BVHNodePlaneMaskPair> queue;
		queue.push_back(BVHNodePlaneMaskPair(root, 0));

		while (!queue.empty())
		{
			BVHNodePlaneMaskPair q_entry = queue.front();
			queue.pop_front();

			///AABB - frustum
			CullingPlanes::VFC_RESULT result = CullingPlanes::InFrustum(planes, q_entry.first->GetAABB(), nullptr, &q_entry.second);

			switch (result)
			{
			case CullingPlanes::OUTSIDE:
				break;
			case CullingPlanes::INSIDE:
				{
					node_splitter.InsertSubTree(q_entry.first);
					break;
				}
			case CullingPlanes::INTERSECT:
				{
					if (q_entry.first->IsLeaf())
						node_splitter.InsertLeaf(q_entry.first);
					else
					{
						queue.push_back(BVHNodePlaneMaskPair(q_entry.first->GetLChild(), q_entry.second));
						queue.push_back(BVHNodePlaneMaskPair(q_entry.first->GetRChild(), q_entry.second));
					}
					break;
				}
			}
		}
	}
	template <class TreeNodeType, class NodeSplitter> void Cull(TreeNodeType *root, const CullingPlanes &planes, const VRUT::BSphere &cam_bsphere, NodeSplitter &node_splitter)
	{
		if (!root)		return;

		// Plane mask says which planes needs not to be tested, because one of node's parents where tested
		// by the plane and the result was "INSIDE"
		typedef std::pair<TreeNodeType *, unsigned char>		BVHNodePlaneMaskPair;

		std::deque<BVHNodePlaneMaskPair> queue;
		queue.push_back(BVHNodePlaneMaskPair(root, 0));

		while (!queue.empty())
		{
			BVHNodePlaneMaskPair q_entry = queue.front();
			queue.pop_front();

			CullingPlanes::VFC_RESULT result;
			
			// bSphere - bSphere
			if (!(q_entry.first->GetBSphere().Intersects(&cam_bsphere)))
			{
				result = CullingPlanes::OUTSIDE;
			}
			else
			{
				///AABB - frustum
				result = CullingPlanes::InFrustum(planes, q_entry.first->GetAABB(), nullptr, &q_entry.second);
			}

			switch (result)
			{
			case CullingPlanes::OUTSIDE:
				break;
			case CullingPlanes::INSIDE:
				{
					node_splitter.InsertSubTree(q_entry.first);
					break;
				}
			case CullingPlanes::INTERSECT:
				{
					if (q_entry.first->IsLeaf())
						node_splitter.InsertLeaf(q_entry.first);
					else
					{
						queue.push_back(BVHNodePlaneMaskPair(q_entry.first->GetLChild(), q_entry.second));
						queue.push_back(BVHNodePlaneMaskPair(q_entry.first->GetRChild(), q_entry.second));
					}
					break;
				}
			}
		}
	}
	template <class TreeNodeType, class NodeSplitter> void Cull(TreeNodeType *root, const CullingPlanes &planes, int camera_index, NodeSplitter &node_splitter)
	{
		assert(camera_index >= 0);
		if (!root)		return;

		// Plane mask says which planes needs not to be tested, because one of node's parents where tested
		// by the plane and the result was "INSIDE"
		typedef std::pair<TreeNodeType *, unsigned char>		BVHNodePlaneMaskPair;

		std::deque<BVHNodePlaneMaskPair> queue;
		queue.push_back(BVHNodePlaneMaskPair(root, 0));

		while (!queue.empty())
		{
			BVHNodePlaneMaskPair q_entry = queue.front();
			queue.pop_front();

			///AABB - frustum
			assert(int(q_entry.first->PerCameraLastCullingPlaneIndex.size()) > camera_index);
			CullingPlanes::VFC_RESULT result = CullingPlanes::InFrustum(planes, q_entry.first->GetAABB(), &q_entry.first->PerCameraLastCullingPlaneIndex[camera_index], &q_entry.second);

			switch (result)
			{
			case CullingPlanes::OUTSIDE:
				break;
			case CullingPlanes::INSIDE:
				{
					node_splitter.InsertSubTree(q_entry.first);
					break;
				}
			case CullingPlanes::INTERSECT:
				{
					if (q_entry.first->IsLeaf())
						node_splitter.InsertLeaf(q_entry.first);
					else
					{
						queue.push_back(BVHNodePlaneMaskPair(q_entry.first->GetLChild(), q_entry.second));
						queue.push_back(BVHNodePlaneMaskPair(q_entry.first->GetRChild(), q_entry.second));
					}
					break;
				}
			}
		}
	}
	template <class TreeNodeType, class NodeSplitter> void Cull(TreeNodeType *root, const CullingPlanes &planes, int camera_index, const VRUT::BSphere &cam_bsphere, NodeSplitter &node_splitter)
	{
		assert(camera_index >= 0);
		if (!root)		return;

		// Plane mask says which planes needs not to be tested, because one of node's parents where tested
		// by the plane and the result was "INSIDE"
		typedef std::pair<TreeNodeType *, unsigned char>		BVHNodePlaneMaskPair;

		std::deque<BVHNodePlaneMaskPair> queue;
		queue.push_back(BVHNodePlaneMaskPair(root, 0));

		while (!queue.empty())
		{
			BVHNodePlaneMaskPair q_entry = queue.front();
			queue.pop_front();

			CullingPlanes::VFC_RESULT result;
			
			// bSphere - bSphere
			if (!(q_entry.first->GetBSphere().Intersects(&cam_bsphere)))
				result = CullingPlanes::OUTSIDE;
			else
			{
				///AABB - frustum
				assert(int(q_entry.first->PerCameraLastCullingPlaneIndex.size()) > camera_index);
				result = CullingPlanes::InFrustum(planes, q_entry.first->GetAABB(), &q_entry.first->PerCameraLastCullingPlaneIndex[camera_index], &q_entry.second);
			}

			switch (result)
			{
			case CullingPlanes::OUTSIDE:
				break;
			case CullingPlanes::INSIDE:
				{
					node_splitter.InsertSubTree(q_entry.first);
					break;
				}
			case CullingPlanes::INTERSECT:
				{
					if (q_entry.first->IsLeaf())
						node_splitter.InsertLeaf(q_entry.first);
					else
					{
						queue.push_back(BVHNodePlaneMaskPair(q_entry.first->GetLChild(), q_entry.second));
						queue.push_back(BVHNodePlaneMaskPair(q_entry.first->GetRChild(), q_entry.second));
					}
					break;
				}
			}
		}
	}
}
