//
//	Copyright © 2005 Nemetschek North America 
//	All Rights Reserved.
//
// Created by Vladislav Stanev

#include "StdAfx.h"
//#include "VWPolygon3DOperation.h"

// engine from http://www.xs4all.nl/~kholwerd/download/download.html
#include "kbool/booleng.h"

using namespace VWFC::Math;

static void ArmBoolEng_3D(Bool_Engine* pBoolEngine);
static void SetPolygonA_3D(Bool_Engine* pBoolEngine, const VWPolygon3D& poly);
static void SetPolygonB_3D(Bool_Engine* pBoolEngine, const VWPolygon2D& poly);
static void GetResult_3D(Bool_Engine* pBoolEngine, const VWPolygon3D& refPoly, TSTLPolygons3DArray& out);
static void GetResult_3D(Bool_Engine* pBoolEngine, const VWPolygon3D& refPoly, VWPolygon3D& out);

VWPolygon3DOperation::VWPolygon3DOperation(const VWPolygon2D& clipPoly)
: VWPolygonOperation( clipPoly )
{
	m_pSTLCutResultArray	= NULL;
}

VWPolygon3DOperation::~VWPolygon3DOperation()
{
}

bool VWPolygon3DOperation::BooleanOR(const VWPolygon3D& polygon, TSTLPolygons3DArray&/*out*/ resultPolygons)
{
	if ( polygon.IsEmpty() || m_clipPoly.IsEmpty() )
		return false;

	bool	ok		= true;

	try
	{
		((Bool_Engine*) m_pBooleanEngine)->Activate();
		::SetPolygonA_3D( (Bool_Engine*) m_pBooleanEngine, polygon );
		::SetPolygonB_3D( (Bool_Engine*) m_pBooleanEngine, m_clipPoly );
		((Bool_Engine*) m_pBooleanEngine)->Do_Operation( BOOL_OR );
		::GetResult_3D( (Bool_Engine*) m_pBooleanEngine, polygon, resultPolygons );
	}
	catch(Bool_Engine_Error& /*error*/)
	{
		ok = false;
	}

	return ok;
}

bool VWPolygon3DOperation::BooleanAND(const VWPolygon3D& polygon, TSTLPolygons3DArray&/*out*/ resultPolygons)
{
	if ( polygon.IsEmpty() || m_clipPoly.IsEmpty() )
		return false;

	bool	ok		= true;

	try
	{
		if ( VWPolygon3DOperation::AreOverlapping( m_clipPoly, polygon ) ) {
			((Bool_Engine*) m_pBooleanEngine)->Activate();
			::SetPolygonA_3D( (Bool_Engine*) m_pBooleanEngine, polygon );
			::SetPolygonB_3D( (Bool_Engine*) m_pBooleanEngine, m_clipPoly );
			((Bool_Engine*) m_pBooleanEngine)->Do_Operation( BOOL_AND );
			::GetResult_3D( (Bool_Engine*) m_pBooleanEngine, polygon, resultPolygons );
		}
		else {
			size_t		vertCnt	= polygon.GetVertexCount();
			if ( vertCnt > 0 ) {
				const VWPoint3D& v1	= polygon.GetVertexAt( 0, false );

				if ( m_clipPoly.IsPtInPoly2D( v1.x, v1.y, VWPoint2D::sEpsilon ) ) {
					resultPolygons.push_back( polygon );
				}
			}
		}
	}
	catch(Bool_Engine_Error& /*error*/)
	{
		ok = false;
	}

	return ok;
}

bool VWPolygon3DOperation::BooleanEXOR(const VWPolygon3D& polygon, TSTLPolygons3DArray&/*out*/ resultPolygons)
{
	if ( polygon.IsEmpty() || m_clipPoly.IsEmpty() )
		return false;

	bool	ok		= true;

	try
	{
		((Bool_Engine*) m_pBooleanEngine)->Activate();
		::SetPolygonA_3D( (Bool_Engine*) m_pBooleanEngine, polygon );
		::SetPolygonB_3D( (Bool_Engine*) m_pBooleanEngine, m_clipPoly );
		((Bool_Engine*) m_pBooleanEngine)->Do_Operation( BOOL_EXOR );
		::GetResult_3D( (Bool_Engine*) m_pBooleanEngine, polygon, resultPolygons );
	}
	catch(Bool_Engine_Error& /*error*/)
	{
		ok = false;
	}

	return ok;
}

bool VWPolygon3DOperation::BooleanPoly_SUB_Clip(const VWPolygon3D& polygon, TSTLPolygons3DArray&/*out*/ resultPolygons)
{
	if ( polygon.IsEmpty() || m_clipPoly.IsEmpty() )
		return false;

	bool	ok		= true;

	try
	{
		((Bool_Engine*) m_pBooleanEngine)->Activate();
		::SetPolygonA_3D( (Bool_Engine*) m_pBooleanEngine, polygon );
		::SetPolygonB_3D( (Bool_Engine*) m_pBooleanEngine, m_clipPoly );
		((Bool_Engine*) m_pBooleanEngine)->Do_Operation( BOOL_A_SUB_B );
		::GetResult_3D( (Bool_Engine*) m_pBooleanEngine, polygon, resultPolygons );
	}
	catch(Bool_Engine_Error& /*error*/)
	{
		ok = false;
	}

	return ok;
}

bool VWPolygon3DOperation::BooleanClip_SUB_Poly(const VWPolygon3D& polygon, TSTLPolygons3DArray&/*out*/ resultPolygons)
{
	if ( polygon.IsEmpty() || m_clipPoly.IsEmpty() )
		return false;

	bool	ok		= true;

	try
	{
		((Bool_Engine*) m_pBooleanEngine)->Activate();
		::SetPolygonA_3D( (Bool_Engine*) m_pBooleanEngine, polygon );
		::SetPolygonB_3D( (Bool_Engine*) m_pBooleanEngine, m_clipPoly );
		((Bool_Engine*) m_pBooleanEngine)->Do_Operation( BOOL_B_SUB_A );
		::GetResult_3D( (Bool_Engine*) m_pBooleanEngine, polygon, resultPolygons );
	}
	catch(Bool_Engine_Error& /*error*/)
	{
		ok = false;
	}

	return ok;
}

// clip the 'poly' using 'clipper' polygon as clipping object
bool VWPolygon3DOperation::ClipToPoly(TSTLPolygons3DArray&/*out*/ resultPolygons, const VWPolygon3D& inPoly, const VWPolygon2D& inClipper)
{
	if ( inPoly.IsEmpty() || inClipper.IsEmpty() )
		return false;

	bool	ok		= true;

	try
	{
		Bool_Engine		engine;
		::ArmBoolEng_3D( & engine );
		::SetPolygonA_3D( &engine, inPoly );
		::SetPolygonB_3D( &engine, inClipper );

		// clip
		engine.Do_Operation( BOOL_AND );

		::GetResult_3D( & engine, inPoly, resultPolygons );
	}
	catch(Bool_Engine_Error& /*error*/)
	{
		ok = false;
	}

	return ok;
}

bool VWPolygon3DOperation::CombinePolygons(VWPolygon3D&/*out*/ resultPolygon, const VWPolygon3D& inPoly1, const VWPolygon2D& inPoly2)
{
	if ( inPoly1.IsEmpty() || inPoly2.IsEmpty() )
		return false;

	bool	ok		= true;

	try
	{
		Bool_Engine		engine;
		::ArmBoolEng_3D( & engine );
		::SetPolygonA_3D( &engine, inPoly1 );
		::SetPolygonB_3D( &engine, inPoly2 );

		// combine
		engine.Do_Operation( BOOL_OR );

		::GetResult_3D( & engine, inPoly1, resultPolygon );
	}
	catch(Bool_Engine_Error& /*error*/)
	{
		ok = false;
	}

	return ok;
}

bool VWPolygon3DOperation::SubtractPolygons(TSTLPolygons3DArray&/*out*/ resultPolygons, const VWPolygon3D& inPoly1, const VWPolygon2D& inPoly2)
{
	if ( inPoly1.IsEmpty() || inPoly2.IsEmpty() )
		return false;

	bool	ok		= true;

	try
	{
		Bool_Engine		engine;
		::ArmBoolEng_3D( & engine );
		::SetPolygonA_3D( &engine, inPoly1 );
		::SetPolygonB_3D( &engine, inPoly2 );

		// combine
		engine.Do_Operation( BOOL_A_SUB_B );

		::GetResult_3D( & engine, inPoly1, resultPolygons );
	}
	catch(Bool_Engine_Error& /*error*/)
	{
		ok = false;
	}

	return ok;
}

// cut the specified polygon by cutter
// the result are those parts of the polygon that lie inside the cutter
bool VWPolygon3DOperation::CutPolygon(TSTLPolygons3DArray&/*out*/ resultPolygons, const VWPolygon3D& inPoly, const VWPolygon2D& inCutter)
{
	VWPolygon3DOperation	operation( inCutter );
	return operation.CutPolygon( inPoly, resultPolygons );
}

// cut the specified polygon by cutter
// the result are those parts of the polygon that lie inside the cutter
bool VWPolygon3DOperation::CutPolygon(const VWPolygon3D& inPoly, TSTLPolygons3DArray&/*out*/ resultPolygons)
{
	size_t				len			= inPoly.GetVertexCount();
	if ( len == 0 )
		return false;

	SVertex*			pFirstVert	= NULL;
	SVertex*			pLastVert	= NULL;
	for(size_t i=0; i<len; i++) {
		SVertex*			pVert	= new SVertex();
		const VWPoint3D&	pt		= inPoly.GetVertexAt( i );
		pVert->index				= i;
		pVert->pt					= pt;
		pVert->z					= pt.z;

		if ( pFirstVert == NULL )
			pFirstVert	= pVert;
		else
			pLastVert->pNextVertex	= pVert;

		pLastVert		= pVert;
	}

	if ( inPoly.IsClosed() ) {
		pLastVert->pNextVertex	= pFirstVert;
	}

	VWFC_ASSERT( pFirstVert );

	size_t		vertsCnt		= len;
	this->AddIntersectionVerticesForCut( pFirstVert, vertsCnt, m_clipPoly );

	m_pSTLCutResultArray			= & resultPolygons;
	m_cutResutPoly.ClearVertices();
	this->WalkValidVertexsForCut( pFirstVert, vertsCnt );

	SVertex*	pThisVert	= pFirstVert;
	do {
		SVertex*	pNextVert	= pThisVert->pNextVertex;
		if ( pNextVert == pFirstVert )
			pNextVert	= NULL;

		delete pThisVert;
		pThisVert		= pNextVert;
	} while ( pThisVert );
	pFirstVert			= NULL;
	pLastVert			= NULL;

	return resultPolygons.size() > 0;
}

void VWPolygon3DOperation::AddValidVertex(SVertex* pVertex)
{
	m_cutResutPoly.AddVertex( pVertex->pt.x, pVertex->pt.y, pVertex->z );
}

void VWPolygon3DOperation::FinishValidVerticesForPoly(bool bClosed)
{
	if ( m_pSTLCutResultArray ) {
		if ( m_cutResutPoly.GetVertexCount() >= 2 )  {
			m_cutResutPoly.SetClosed( bClosed );
			m_pSTLCutResultArray->push_back( m_cutResutPoly );
		}
	}
	m_cutResutPoly.ClearVertices();
}

/*static*/ bool VWPolygon3DOperation::AreOverlapping(const VWPolygon2D& a, const VWPolygon3D& b)
{
	const double kEpsilon	= 0.0001;

	bool		bClip		= false;

	size_t		vertCnt2	= b.GetVertexCount();

	size_t		vertCnt1	= a.GetVertexCount();
	for(size_t i=0; i<vertCnt1; i++) {
		const VWPoint2D& v1	= a.GetVertexAt( i, false );
		const VWPoint2D& v2	= a.GetVertexAt( i + 1, true );

		for(size_t j=0; j<vertCnt2; j++) {
			const VWPoint3D& p1	= b.GetVertexAt( j, false );
			const VWPoint3D& p2	= b.GetVertexAt( j + 1, true );

			if ( ! bClip ) {
				double	intersX, intersY;
				bool	bParal	= false;
				bool	bOnSeg1	= false;
				bool	bOnSeg2	= false;
				VWLine2D::IntersectLines( p1.x, p1.y, p2.x, p2.y, v1.x, v1.y, v2.x, v2.y, intersX, intersY, bParal, bOnSeg1, bOnSeg2, kEpsilon );

				bClip			= ! bParal && bOnSeg1 && bOnSeg2;
				if ( bClip ) {
					break;
				}
			}
		}

		if ( bClip ) {
			break;
		}
	}

	return bClip;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Bool engine code
static void ArmBoolEng_3D(Bool_Engine* pBoolEngine)
{
    // set some global vals to arm the boolean engine
    double DGRID = 0.00000001;		// round coordinate X or Y value in calculations to this
    double MARGE = 0.0000001;		// snap with in this range points to lines in the intersection routines
									// should always be > DGRID  a  MARGE >= 10*DGRID is oke
									// this is also used to remove small segments and to decide when
									// two segments are in line.
    double CORRECTIONFACTOR = 500.0;  // correct the polygons by this number
    double CORRECTIONABER   = 1.0;    // the accuracy for the rounded shapes used in correction
    double ROUNDFACTOR      = 1.5;    // when will we round the correction shape to a circle
    double SMOOTHABER       = 10.0;   // accuracy when smoothing a polygon
    double MAXLINEMERGE     = 1000.0; // leave as is, segments of this length in smoothen
   
    if (MARGE <= DGRID)
    {
		VWFC_ASSERT( false );
       // cout << "MARGE smaller than GRID" << endl;
        MARGE = DGRID*10;
    }

    // DGRID is only meant to make fractional parts of input data which 
    // are doubles, part of the integers used in vertexes within the boolean algorithm.
    // And therefore bigger than 1 is not usefull.
    // Within the algorithm all input data is multiplied with DGRID
    if ( DGRID < 1 )
        DGRID = 1/DGRID;
    else
        DGRID = 1;
    
    // space for extra intersection inside the boolean algorithms
    // only change this if there are problems
    int GRID =100;

    pBoolEngine->SetMarge( MARGE );
    pBoolEngine->SetGrid( GRID );
    pBoolEngine->SetDGrid( DGRID );
    pBoolEngine->SetCorrectionFactor( CORRECTIONFACTOR );
    pBoolEngine->SetCorrectionAber( CORRECTIONABER );
    pBoolEngine->SetSmoothAber( SMOOTHABER );
    pBoolEngine->SetMaxlinemerge( MAXLINEMERGE );
    pBoolEngine->SetRoundfactor( ROUNDFACTOR );

}

static void SetPolygonA_3D(Bool_Engine* pBoolEngine, const VWPolygon3D& poly)
{
    if ( pBoolEngine->StartPolygonAdd( GROUP_A ) )
    {
		size_t		len	= poly.GetVertexCount();
		for(size_t i=0; i<len; i++) {
			const VWPoint3D& pt	= poly.GetVertexAt( i );
			pBoolEngine->AddPoint( pt.x, pt.y );
		}
    }
    pBoolEngine->EndPolygonAdd(); 
}

static void SetPolygonB_3D(Bool_Engine* pBoolEngine, const VWPolygon2D& poly)
{
    if ( pBoolEngine->StartPolygonAdd( GROUP_B ) )
    {
		size_t		len	= poly.GetVertexCount();
		for(size_t i=0; i<len; i++) {
			const VWPoint2D& pt	= poly.GetVertexAt( i );
			pBoolEngine->AddPoint( pt.x, pt.y );
		}
    }
    pBoolEngine->EndPolygonAdd(); 
}

static void GetResult_3D(Bool_Engine* pBoolEngine, const VWPolygon3D& refPoly, TSTLPolygons3DArray& out)
{
	out.clear();
    // foreach resultant polygon in the booleng ...
    while ( pBoolEngine->StartPolygonGet() )
    {
		VWPolygon3D	resultPoly;
        // foreach point in the polygon
        while ( pBoolEngine->PolygonHasMorePoints() )
        {
			VWPoint3D		pt;
			pt.x					= pBoolEngine->GetPolygonXPoint();
			pt.y					= pBoolEngine->GetPolygonYPoint();
			pt.z					= refPoly.GetVerticalDistance( pt );
			resultPoly.AddVertex( pt );
        }
        pBoolEngine->EndPolygonGet();
		out.push_back( resultPoly );
    }
}

static void GetResult_3D(Bool_Engine* pBoolEngine, const VWPolygon3D& refPoly, VWPolygon3D& out)
{
	out.ClearVertices();
    // foreach resultant polygon in the booleng ...
    if ( pBoolEngine->StartPolygonGet() )
    {
        // foreach point in the polygon
        while ( pBoolEngine->PolygonHasMorePoints() )
        {
			VWPoint3D		pt;
			pt.x					= pBoolEngine->GetPolygonXPoint();
			pt.y					= pBoolEngine->GetPolygonYPoint();
			pt.z					= refPoly.GetVerticalDistance( pt );
			out.AddVertex( pt );
        }
        pBoolEngine->EndPolygonGet();
    }
}


