//////////////////////////////////////////////////////////////////////
//
//	Crytek CryENGINE Source code
//	
//	File:AABBSV.h
//	Description: shadow volume AABB functionality for overlap testings
//
//	History:
//	-Feb 15,2004:Created by Michael Glueck, code provided by Ivo Herzeg
//
//////////////////////////////////////////////////////////////////////

#ifndef AABBSV_H
#define AABBSV_H

#if _MSC_VER > 1000
# pragma once
#endif

#include "Cry_Geo.h"

struct Shadowvolume 
{
	uint32 sideamount;
	uint32 nplanes;

	Plane oplanes[10];
};

namespace NAABB_SV
{
	//***************************************************************************************
	//***************************************************************************************
	//***             Calculate a ShadowVolume using an AABB and a point-light            ***
	//***************************************************************************************
	//***  The planes of the AABB facing away from the point-light are the far-planes     ***
	//***  of the ShadowVolume. There can be 3-6 far-planes.                              ***
	//***************************************************************************************
	void AABB_ReceiverShadowVolume(const Vec3& PointLight, const AABB& Occluder, Shadowvolume &sv);

	//***************************************************************************************
	//***************************************************************************************
	//***             Calculate a ShadowVolume using an AABB and a point-light            ***
	//***************************************************************************************
	//***  The planes of the AABB facing the point-light are the near-planes of the       ***
	//***  the ShadowVolume. There can be 1-3 near-planes.                                ***
	//***  The far-plane is defined by lightrange.                                        ***
	//***************************************************************************************
	void AABB_ShadowVolume(const Vec3& PointLight, const AABB& Occluder, Shadowvolume &sv, f32 lightrange);	

	//***************************************************************************************
	//***   this is the "fast" version to check if an AABB is overlapping a shadowvolume  ***
	//***************************************************************************************
	bool Is_AABB_In_ShadowVolume( const Shadowvolume &sv,  const AABB& Receiver );
	
	//***************************************************************************************
	//***   this is the "hierarchical" check                                              ***
	//***************************************************************************************
	char Is_AABB_In_ShadowVolume_hierarchical( const Shadowvolume &sv,  const AABB& Receiver ); 
}





inline void NAABB_SV::AABB_ReceiverShadowVolume(const Vec3& PointLight, const AABB& Occluder, Shadowvolume &sv)
{
	sv.sideamount=0;
	sv.nplanes=0;

	//------------------------------------------------------------------------------
	//-- check if PointLight is in front of any occluder plane or inside occluder --
	//------------------------------------------------------------------------------
	uint32 front=0;
	if (PointLight.x<Occluder.min.x)  front|=0x01;
	if (PointLight.x>Occluder.max.x)  front|=0x02;
	if (PointLight.y<Occluder.min.y)  front|=0x04;
	if (PointLight.y>Occluder.max.y)  front|=0x08;
	if (PointLight.z<Occluder.min.z)  front|=0x10;
	if (PointLight.z>Occluder.max.z)  front|=0x20;

	sv.sideamount = BoxSides[(front<<3)+7];

	uint32 back = front^0x3f;	
	if (back&0x01) { sv.oplanes[sv.nplanes].SetPlane(Vec3(-1,+0,+0),Occluder.min); sv.nplanes++; } 
	if (back&0x02) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+1,+0,+0),Occluder.max); sv.nplanes++; }
	if (back&0x04) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+0,-1,+0),Occluder.min); sv.nplanes++; } 
	if (back&0x08) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+0,+1,+0),Occluder.max); sv.nplanes++; }
	if (back&0x10) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+0,+0,-1),Occluder.min); sv.nplanes++; } 
	if (back&0x20) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+0,+0,+1),Occluder.max); sv.nplanes++; }

	if (front==0) return;		//light is inside occluder

	//all 8 vertices of a AABB
	Vec3 o[8] = 
	{ 
		Vec3(Occluder.min.x,Occluder.min.y,Occluder.min.z),
			Vec3(Occluder.max.x,Occluder.min.y,Occluder.min.z),
			Vec3(Occluder.min.x,Occluder.max.y,Occluder.min.z),
			Vec3(Occluder.max.x,Occluder.max.y,Occluder.min.z),
			Vec3(Occluder.min.x,Occluder.min.y,Occluder.max.z),
			Vec3(Occluder.max.x,Occluder.min.y,Occluder.max.z),
			Vec3(Occluder.min.x,Occluder.max.y,Occluder.max.z),
			Vec3(Occluder.max.x,Occluder.max.y,Occluder.max.z)
	};

	//---------------------------------------------------------------------
	//---      find the silhouette-vertices of the occluder-AABB        ---
	//---------------------------------------------------------------------
	uint32 p0	=	BoxSides[(front<<3)+0];
	uint32 p1	=	BoxSides[(front<<3)+1];
	uint32 p2	=	BoxSides[(front<<3)+2];
	uint32 p3	=	BoxSides[(front<<3)+3];
	uint32 p4	=	BoxSides[(front<<3)+4];
	uint32 p5	=	BoxSides[(front<<3)+5];

	float a;
	if(sv.sideamount==4) 
	{
		//sv.oplanes[sv.nplanes+0]	=	Plane::CreatePlane( o[p0],o[p1], PointLight );
		//sv.oplanes[sv.nplanes+1]	=	Plane::CreatePlane( o[p1],o[p2], PointLight );
		//sv.oplanes[sv.nplanes+2]	=	Plane::CreatePlane( o[p2],o[p3], PointLight );
		//sv.oplanes[sv.nplanes+3]	=	Plane::CreatePlane( o[p3],o[p0], PointLight );
		sv.sideamount=0;
		a=(o[p1]-o[p0])|(o[p0]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p0],o[p1], PointLight ); sv.sideamount++; }
		a=(o[p2]-o[p1])|(o[p1]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p1],o[p2], PointLight ); sv.sideamount++; }
		a=(o[p3]-o[p2])|(o[p2]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p2],o[p3], PointLight ); sv.sideamount++; }
		a=(o[p0]-o[p3])|(o[p3]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p3],o[p0], PointLight ); sv.sideamount++; }
	}

	if(sv.sideamount==6) 
	{
		//sv.oplanes[sv.nplanes+0]	=	Plane::CreatePlane( o[p0],o[p1], PointLight );
		//sv.oplanes[sv.nplanes+1]	=	Plane::CreatePlane( o[p1],o[p2], PointLight );
		//sv.oplanes[sv.nplanes+2]	=	Plane::CreatePlane( o[p2],o[p3], PointLight );
		//sv.oplanes[sv.nplanes+3]	=	Plane::CreatePlane( o[p3],o[p4], PointLight );
		//sv.oplanes[sv.nplanes+4]	=	Plane::CreatePlane( o[p4],o[p5], PointLight );
		//sv.oplanes[sv.nplanes+5]	=	Plane::CreatePlane( o[p5],o[p0], PointLight );

		sv.sideamount=0;
		a=(o[p1]-o[p0])|(o[p0]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p0],o[p1], PointLight ); sv.sideamount++; }
		a=(o[p2]-o[p1])|(o[p1]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p1],o[p2], PointLight ); sv.sideamount++; }
		a=(o[p3]-o[p2])|(o[p2]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p2],o[p3], PointLight ); sv.sideamount++; }
		a=(o[p4]-o[p3])|(o[p3]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p3],o[p4], PointLight ); sv.sideamount++; }
		a=(o[p5]-o[p4])|(o[p4]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p4],o[p5], PointLight ); sv.sideamount++; }
		a=(o[p0]-o[p5])|(o[p5]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p5],o[p0], PointLight ); sv.sideamount++; }

	}
}

inline void NAABB_SV::AABB_ShadowVolume(const Vec3& PointLight, const AABB& Occluder, Shadowvolume &sv, f32 lightrange) 
{
	sv.sideamount=0;
	sv.nplanes=0;

	//------------------------------------------------------------------------------
	//-- check if PointLight is in front of any occluder plane or inside occluder --
	//------------------------------------------------------------------------------
	uint32 front=0;
	if (PointLight.x<Occluder.min.x)  front|=0x01;
	if (PointLight.x>Occluder.max.x)  front|=0x02;
	if (PointLight.y<Occluder.min.y)  front|=0x04;
	if (PointLight.y>Occluder.max.y)  front|=0x08;
	if (PointLight.z<Occluder.min.z)  front|=0x10;
	if (PointLight.z>Occluder.max.z)  front|=0x20;
	if (front==0) return; //light is inside occluder
	sv.sideamount = BoxSides[(front<<3)+7];

	if (front&0x01) { sv.oplanes[sv.nplanes].SetPlane(Vec3(-1,+0,+0),Occluder.min); sv.nplanes++; } 
	if (front&0x02) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+1,+0,+0),Occluder.max); sv.nplanes++; }
	if (front&0x04) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+0,-1,+0),Occluder.min); sv.nplanes++; } 
	if (front&0x08) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+0,+1,+0),Occluder.max); sv.nplanes++; }
	if (front&0x10) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+0,+0,-1),Occluder.min); sv.nplanes++; } 
	if (front&0x20) { sv.oplanes[sv.nplanes].SetPlane(Vec3(+0,+0,+1),Occluder.max); sv.nplanes++; }

	//all 8 vertices of a AABB
	Vec3 o[8] = 
	{ 
		Vec3(Occluder.min.x,Occluder.min.y,Occluder.min.z),
			Vec3(Occluder.max.x,Occluder.min.y,Occluder.min.z),
			Vec3(Occluder.min.x,Occluder.max.y,Occluder.min.z),
			Vec3(Occluder.max.x,Occluder.max.y,Occluder.min.z),
			Vec3(Occluder.min.x,Occluder.min.y,Occluder.max.z),
			Vec3(Occluder.max.x,Occluder.min.y,Occluder.max.z),
			Vec3(Occluder.min.x,Occluder.max.y,Occluder.max.z),
			Vec3(Occluder.max.x,Occluder.max.y,Occluder.max.z)
	};

	//---------------------------------------------------------------------
	//---      find the silhouette-vertices of the occluder-AABB        ---
	//---------------------------------------------------------------------
	uint32 p0	=	BoxSides[(front<<3)+0];
	uint32 p1	=	BoxSides[(front<<3)+1];
	uint32 p2	=	BoxSides[(front<<3)+2];
	uint32 p3	=	BoxSides[(front<<3)+3];
	uint32 p4	=	BoxSides[(front<<3)+4];
	uint32 p5	=	BoxSides[(front<<3)+5];

	//the new center-position in world-space
	Vec3 MiddleOfOccluder	=	(Occluder.max+Occluder.min)*0.5f;
	sv.oplanes[sv.nplanes]	=	Plane::CreatePlane( (MiddleOfOccluder-PointLight).GetNormalized() , (MiddleOfOccluder-PointLight).GetNormalized()*lightrange+PointLight );
	sv.nplanes++;

	float a;
	if(sv.sideamount==4) 
	{
		sv.sideamount=0;
		a=(o[p1]-o[p0])|(o[p0]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p0],o[p1], PointLight ); sv.sideamount++; }
		a=(o[p2]-o[p1])|(o[p1]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p1],o[p2], PointLight ); sv.sideamount++; }
		a=(o[p3]-o[p2])|(o[p2]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p2],o[p3], PointLight ); sv.sideamount++; }
		a=(o[p0]-o[p3])|(o[p3]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p3],o[p0], PointLight ); sv.sideamount++; }
	}

	if(sv.sideamount==6) 
	{
		sv.sideamount=0;
		a=(o[p1]-o[p0])|(o[p0]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p0],o[p1], PointLight ); sv.sideamount++; }
		a=(o[p2]-o[p1])|(o[p1]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p1],o[p2], PointLight ); sv.sideamount++; }
		a=(o[p3]-o[p2])|(o[p2]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p2],o[p3], PointLight ); sv.sideamount++; }
		a=(o[p4]-o[p3])|(o[p3]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p3],o[p4], PointLight ); sv.sideamount++; }
		a=(o[p5]-o[p4])|(o[p4]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p4],o[p5], PointLight ); sv.sideamount++; }
		a=(o[p0]-o[p5])|(o[p5]-PointLight); 
		if (a) { sv.oplanes[sv.nplanes+sv.sideamount]	=	Plane::CreatePlane( o[p5],o[p0], PointLight ); sv.sideamount++; }
	}
}

inline bool NAABB_SV::Is_AABB_In_ShadowVolume( const Shadowvolume &sv,  const AABB& Receiver ) 
{
	uint32 pa=sv.sideamount+sv.nplanes;

	f32 d;
	const Vec3* pAABB=&Receiver.min;

	//------------------------------------------------------------------------------
	//----    check if receiver-AABB is in front of any of these planes       ------
	//------------------------------------------------------------------------------
	for (uint32 x=0; x<pa; x++) 
	{ 
		d=sv.oplanes[x].d;		
		d += sv.oplanes[x].n.x * pAABB[(*((uint32*)&sv.oplanes[x].n.x)>>31)].x;
		d += sv.oplanes[x].n.y * pAABB[(*((uint32*)&sv.oplanes[x].n.y)>>31)].y;
		d += sv.oplanes[x].n.z * pAABB[(*((uint32*)&sv.oplanes[x].n.z)>>31)].z;
		if (d>0) return CULL_EXCLUSION;		
	}
	return CULL_OVERLAP;
}

inline char NAABB_SV::Is_AABB_In_ShadowVolume_hierarchical( const Shadowvolume &sv,  const AABB& Receiver ) 
{
	uint32 pa=sv.sideamount+sv.nplanes;
	const Vec3* pAABB=&Receiver.min;

	f32 dot1,dot2;
	uint32 notOverlap = 0x80000000; // will be reset to 0 if there's at least one overlapping

	//------------------------------------------------------------------------------
	//----    check if receiver-AABB is in front of any of these planes       ------
	//------------------------------------------------------------------------------
	for (uint32 x=0; x<pa; x++) 
	{
		dot1=dot2=sv.oplanes[x].d;		
		dot1 += sv.oplanes[x].n.x * pAABB[0+(*((uint32*)&sv.oplanes[x].n.x)>>31)].x;
		dot2 += sv.oplanes[x].n.x * pAABB[1-(*((uint32*)&sv.oplanes[x].n.x)>>31)].x;
		dot1 += sv.oplanes[x].n.y * pAABB[0+(*((uint32*)&sv.oplanes[x].n.y)>>31)].y;
		dot2 += sv.oplanes[x].n.y * pAABB[1-(*((uint32*)&sv.oplanes[x].n.y)>>31)].y;
		dot1 += sv.oplanes[x].n.z * pAABB[0+(*((uint32*)&sv.oplanes[x].n.z)>>31)].z;
		dot2 += sv.oplanes[x].n.z * pAABB[1-(*((uint32*)&sv.oplanes[x].n.z)>>31)].z;
		if ( !(((int32&)dot1)&0x80000000) ) return CULL_EXCLUSION;		
		notOverlap &= (int32&)dot2;
	}
	if (notOverlap)	return CULL_INCLUSION; 
	return CULL_OVERLAP;
}

#endif

