//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//


#include "stdafx.h"
#include "OPTGeneral.h"
#include "Options.h"
#include "hammer_mathlib.h"
#include "MapFace.h"
#include "MapGroup.h"
#include "MapSolid.h"
#include "hammer.h"

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>

//-----------------------------------------------------------------------------
// Purpose: Create a segment using two polygons and a start and end position in
//			those polygons.
// Input  : fZMin - 
//			fZMax - 
//			fOuterPoints - 
//			fInnerPoints - 
//			iStart - 
//			iEnd - 
//			bCreateSouthFace - 
// Output : 
//-----------------------------------------------------------------------------
static CMapSolid *CreateSegment(float fZMin, float fZMax, float fOuterPoints[][2], float fInnerPoints[][2], int iStart, int iEnd, BOOL bCreateSouthFace)
{
	CMapFace Face;
	Vector points[4];	// all sides have four vertices

	CMapSolid *pSolid = new CMapSolid;

	int iNorthSouthPoints = 3 + (bCreateSouthFace ? 1 : 0);

	// create top face
	points[0][0] = fOuterPoints[iStart][0];
	points[0][1] = fOuterPoints[iStart][1];
	points[0][2] = fZMin;

	points[1][0] = fOuterPoints[iEnd][0];
	points[1][1] = fOuterPoints[iEnd][1];
	points[1][2] = fZMin;

	points[2][0] = fInnerPoints[iEnd][0];
	points[2][1] = fInnerPoints[iEnd][1];
	points[2][2] = fZMin;

	points[3][0] = fInnerPoints[iStart][0];
	points[3][1] = fInnerPoints[iStart][1];
	points[3][2] = fZMin;

	Face.CreateFace(points, iNorthSouthPoints);
	pSolid->AddFace(&Face);

	// bottom face - set other z value and reverse order
	for (int i = 0; i < 4; i++)
	{
		points[i][2] = fZMax;
	}

	Face.CreateFace(points, -iNorthSouthPoints);
	pSolid->AddFace(&Face);

	// left side
	points[0][0] = fOuterPoints[iStart][0];
	points[0][1] = fOuterPoints[iStart][1];
	points[0][2] = fZMax;

	points[1][0] = fOuterPoints[iStart][0];
	points[1][1] = fOuterPoints[iStart][1];
	points[1][2] = fZMin;

	points[2][0] = fInnerPoints[iStart][0];
	points[2][1] = fInnerPoints[iStart][1];
	points[2][2] = fZMin;

	points[3][0] = fInnerPoints[iStart][0];
	points[3][1] = fInnerPoints[iStart][1];
	points[3][2] = fZMax;

	Face.CreateFace(points, 4);
	pSolid->AddFace(&Face);

	// right side
	points[0][0] = fOuterPoints[iEnd][0];
	points[0][1] = fOuterPoints[iEnd][1];
	points[0][2] = fZMin;

	points[1][0] = fOuterPoints[iEnd][0];
	points[1][1] = fOuterPoints[iEnd][1];
	points[1][2] = fZMax;

	points[2][0] = fInnerPoints[iEnd][0];
	points[2][1] = fInnerPoints[iEnd][1];
	points[2][2] = fZMax;

	points[3][0] = fInnerPoints[iEnd][0];
	points[3][1] = fInnerPoints[iEnd][1];
	points[3][2] = fZMin;

	Face.CreateFace(points, 4);
	pSolid->AddFace(&Face);

	// north face
	points[0][0] = fOuterPoints[iEnd][0];
	points[0][1] = fOuterPoints[iEnd][1];
	points[0][2] = fZMin;

	points[1][0] = fOuterPoints[iStart][0];
	points[1][1] = fOuterPoints[iStart][1];
	points[1][2] = fZMin;

	points[2][0] = fOuterPoints[iStart][0];
	points[2][1] = fOuterPoints[iStart][1];
	points[2][2] = fZMax;

	points[3][0] = fOuterPoints[iEnd][0];
	points[3][1] = fOuterPoints[iEnd][1];
	points[3][2] = fZMax;

	Face.CreateFace(points, 4);
	pSolid->AddFace(&Face);

	// south face
	if (bCreateSouthFace)
	{
		points[0][0] = fInnerPoints[iStart][0];
		points[0][1] = fInnerPoints[iStart][1];
		points[0][2] = fZMin;

		points[1][0] = fInnerPoints[iEnd][0];
		points[1][1] = fInnerPoints[iEnd][1];
		points[1][2] = fZMin;

		points[2][0] = fInnerPoints[iEnd][0];
		points[2][1] = fInnerPoints[iEnd][1];
		points[2][2] = fZMax;

		points[3][0] = fInnerPoints[iStart][0];
		points[3][1] = fInnerPoints[iStart][1];
		points[3][2] = fZMax;

		Face.CreateFace(points, 4);
		pSolid->AddFace(&Face);
	}

	pSolid->InitializeTextureAxes(Options.GetTextureAlignment(), INIT_TEXTURE_ALL | INIT_TEXTURE_FORCE);

	return(pSolid);
}


//-----------------------------------------------------------------------------
// Purpose: Create a segment using two polygons and a start and end position in
//			those polygons.
// Input  : fZMin - 
//			fZMax - 
//			fOuterPoints - 
//			fInnerPoints - 
//			iStart - 
//			iEnd - 
//			bCreateSouthFace - 
// Output : 
//-----------------------------------------------------------------------------
static CMapSolid *CreateSegment(float fStartOuterPoints[][3], float fStartInnerPoints[][3],
								float fEndOuterPoints[][3], float fEndInnerPoints[][3],
								int iStart, int iEnd, BOOL bCreateSouthFace)
{
	CMapFace Face;
	Vector points[4];	// all sides have four vertices

	CMapSolid *pSolid = new CMapSolid;

	// create top face
	points[0][0] = fStartOuterPoints[iStart][0];
	points[0][1] = fStartOuterPoints[iStart][1];
	points[0][2] = fStartOuterPoints[iStart][2];

	points[1][0] = fStartOuterPoints[iEnd][0];
	points[1][1] = fStartOuterPoints[iEnd][1];
	points[1][2] = fStartOuterPoints[iEnd][2];

	points[2][0] = fStartInnerPoints[iEnd][0];
	points[2][1] = fStartInnerPoints[iEnd][1];
	points[2][2] = fStartInnerPoints[iEnd][2];

	points[3][0] = fStartInnerPoints[iStart][0];
	points[3][1] = fStartInnerPoints[iStart][1];
	points[3][2] = fStartInnerPoints[iStart][2];

	Face.CreateFace(points, -4);
	pSolid->AddFace(&Face);

	// bottom face - set other z value and reverse order
	points[0][0] = fEndOuterPoints[iStart][0];
	points[0][1] = fEndOuterPoints[iStart][1];
	points[0][2] = fEndOuterPoints[iStart][2];

	points[1][0] = fEndOuterPoints[iEnd][0];
	points[1][1] = fEndOuterPoints[iEnd][1];
	points[1][2] = fEndOuterPoints[iEnd][2];

	points[2][0] = fEndInnerPoints[iEnd][0];
	points[2][1] = fEndInnerPoints[iEnd][1];
	points[2][2] = fEndInnerPoints[iEnd][2];

	points[3][0] = fEndInnerPoints[iStart][0];
	points[3][1] = fEndInnerPoints[iStart][1];
	points[3][2] = fEndInnerPoints[iStart][2];

	Face.CreateFace(points, 4);
	pSolid->AddFace(&Face);

	// left side
	points[0][0] = fEndOuterPoints[iStart][0];
	points[0][1] = fEndOuterPoints[iStart][1];
	points[0][2] = fEndOuterPoints[iStart][2];

	points[1][0] = fStartOuterPoints[iStart][0];
	points[1][1] = fStartOuterPoints[iStart][1];
	points[1][2] = fStartOuterPoints[iStart][2];

	points[2][0] = fStartInnerPoints[iStart][0];
	points[2][1] = fStartInnerPoints[iStart][1];
	points[2][2] = fStartInnerPoints[iStart][2];

	points[3][0] = fEndInnerPoints[iStart][0];
	points[3][1] = fEndInnerPoints[iStart][1];
	points[3][2] = fEndInnerPoints[iStart][2];

	Face.CreateFace(points, -4);
	pSolid->AddFace(&Face);

	// right side
	points[0][0] = fStartOuterPoints[iEnd][0];
	points[0][1] = fStartOuterPoints[iEnd][1];
	points[0][2] = fStartOuterPoints[iEnd][2];

	points[1][0] = fEndOuterPoints[iEnd][0];
	points[1][1] = fEndOuterPoints[iEnd][1];
	points[1][2] = fEndOuterPoints[iEnd][2];

	points[2][0] = fEndInnerPoints[iEnd][0];
	points[2][1] = fEndInnerPoints[iEnd][1];
	points[2][2] = fEndInnerPoints[iEnd][2];

	points[3][0] = fStartInnerPoints[iEnd][0];
	points[3][1] = fStartInnerPoints[iEnd][1];
	points[3][2] = fStartInnerPoints[iEnd][2];

	Face.CreateFace(points, -4);
	pSolid->AddFace(&Face);

	// north face
	points[0][0] = fStartOuterPoints[iEnd][0];
	points[0][1] = fStartOuterPoints[iEnd][1];
	points[0][2] = fStartOuterPoints[iEnd][2];

	points[1][0] = fStartOuterPoints[iStart][0];
	points[1][1] = fStartOuterPoints[iStart][1];
	points[1][2] = fStartOuterPoints[iStart][2];

	points[2][0] = fEndOuterPoints[iStart][0];
	points[2][1] = fEndOuterPoints[iStart][1];
	points[2][2] = fEndOuterPoints[iStart][2];

	points[3][0] = fEndOuterPoints[iEnd][0];
	points[3][1] = fEndOuterPoints[iEnd][1];
	points[3][2] = fEndOuterPoints[iEnd][2];

	Face.CreateFace(points, -4);
	pSolid->AddFace(&Face);

	// south face
	if (bCreateSouthFace)
	{
		points[0][0] = fStartInnerPoints[iStart][0];
		points[0][1] = fStartInnerPoints[iStart][1];
		points[0][2] = fStartInnerPoints[iStart][2];

		points[1][0] = fStartInnerPoints[iEnd][0];
		points[1][1] = fStartInnerPoints[iEnd][1];
		points[1][2] = fStartInnerPoints[iEnd][2];

		points[2][0] = fEndInnerPoints[iEnd][0];
		points[2][1] = fEndInnerPoints[iEnd][1];
		points[2][2] = fEndInnerPoints[iEnd][2];

		points[3][0] = fEndInnerPoints[iStart][0];
		points[3][1] = fEndInnerPoints[iStart][1];
		points[3][2] = fEndInnerPoints[iStart][2];

		Face.CreateFace(points, -4);
		pSolid->AddFace(&Face);
	}

	pSolid->InitializeTextureAxes(Options.GetTextureAlignment(), INIT_TEXTURE_ALL | INIT_TEXTURE_FORCE);

	return(pSolid);
}


//-----------------------------------------------------------------------------
// Make a 2d arc
//-----------------------------------------------------------------------------
void MakeArcCenterRadius(float xCenter, float yCenter, float xrad, float yrad, int npoints, float start_ang, float fArc, float points[][2])
{
    int point;
    float angle = start_ang;
	float angle_delta;

	angle_delta = fArc / (float)npoints;

	// Add an additional points if we are not doing a full circle
	if (fArc != 360.0)
	{
		++npoints;
	}
	
    for( point = 0; point < npoints; point++ )
    {
        if ( angle > 360 )
		{
           angle -= 360;
		}

        points[point][0] = V_rint(xCenter + (float)cos(DEG2RAD(angle)) * xrad);
        points[point][1] = V_rint(yCenter + (float)sin(DEG2RAD(angle)) * yrad);

		angle += angle_delta;
    }

	// Full circle, recopy the first point as the closing point.
	if (fArc == 360.0)
	{
	    points[point][0] = points[0][0];
		points[point][1] = points[0][1];
	}
}

void MakeArc(float x1, float y1, float x2, float y2, int npoints, float start_ang, float fArc, float points[][2])
{
    float xrad = (x2 - x1) / 2.0f;
	float yrad = (y2 - y1) / 2.0f;

	// make centerpoint for polygon:
    float xCenter = x1 + xrad;
    float yCenter = y1 + yrad;

	MakeArcCenterRadius( xCenter, yCenter, xrad, yrad, npoints, start_ang, fArc, points );
}

#define ARC_MAX_POINTS 4096


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pBox - 
//			fStartAngle - 
//			iSides - 
//			fArc - 
//			iWallWidth - 
//			iAddHeight - 
//			bPreview - 
// Output : Returns a group containing the arch solids.
//-----------------------------------------------------------------------------
CMapClass *CreateArch(BoundBox *pBox, float fStartAngle, int iSides, float fArc, int iWallWidth, int iAddHeight, BOOL bPreview)
{
	float fOuterPoints[ARC_MAX_POINTS][2];
	float fInnerPoints[ARC_MAX_POINTS][2];

	//
	// create outer points
	//
	MakeArc(pBox->bmins[AXIS_X], pBox->bmins[AXIS_Y],
		pBox->bmaxs[AXIS_X], pBox->bmaxs[AXIS_Y], iSides,
		fStartAngle, fArc, fOuterPoints);

	//
	// create inner points
	//
	MakeArc(pBox->bmins[AXIS_X] + iWallWidth, 
		pBox->bmins[AXIS_Y] + iWallWidth, 
		pBox->bmaxs[AXIS_X] - iWallWidth, 
		pBox->bmaxs[AXIS_Y] - iWallWidth, iSides, 
		fStartAngle, fArc, fInnerPoints);


	//
	// check wall width - if it's half or more of the total,
	//  set the inner poinst to the center point of the box
	//  and turn off the CreateSouthFace flag
	//	
	BOOL bCreateSouthFace = TRUE;
	Vector Center;
	pBox->GetBoundsCenter(Center);
	if((iWallWidth*2+8) >= (pBox->bmaxs[AXIS_X] - pBox->bmins[AXIS_X]) ||
		(iWallWidth*2+8) >= (pBox->bmaxs[AXIS_Y] - pBox->bmins[AXIS_Y]))
	{
		for(int i = 0; i < ARC_MAX_POINTS; i++)
		{
			fInnerPoints[i][AXIS_X] = Center[AXIS_X];
			fInnerPoints[i][AXIS_Y] = Center[AXIS_Y];
		}
		bCreateSouthFace = FALSE;
	}

	// create group for segments
	CMapGroup *pGroup = new CMapGroup;

	Vector MoveAccum( 0.f, 0.f, 0.f );

	float fMinZ, fMaxZ;

	fMinZ = pBox->bmins[2];
	fMaxZ = pBox->bmaxs[2];

	if ((fMaxZ - fMinZ) < 1.0f)
		fMaxZ = fMinZ + 1.0f;

	for (int i = 0; i < iSides; i++)
	{
		int iNextPoint = i+1;
		if (iNextPoint >= iSides + 1)
			iNextPoint = 0;

		CMapSolid *pSolid = CreateSegment(
			fMinZ, fMaxZ,
			fOuterPoints, fInnerPoints,
			i, iNextPoint, bCreateSouthFace);

		pGroup->AddChild(pSolid);

		if (iAddHeight && i)	// don't move first segment
		{
			MoveAccum[2] += iAddHeight;
			pSolid->TransMove(MoveAccum);
		}
	}

	pGroup->CalcBounds(TRUE);
	if (Options.general.bStretchArches)
	{
		// make sure size of group's bounds are size of original bounds -
		//  if not, scale up. this can happen when we use rotation.
		Vector objsize, boundsize;
		pBox->GetBoundsSize(boundsize);
		pGroup->GetBoundsSize(objsize);

		if (boundsize[AXIS_X] > objsize[AXIS_X] || 
			boundsize[AXIS_Y] > objsize[AXIS_Y])
		{
			Vector scale;
			scale[AXIS_X] = boundsize[AXIS_X] / objsize[AXIS_X];
			scale[AXIS_Y] = boundsize[AXIS_Y] / objsize[AXIS_Y];
			scale[AXIS_Z] = 1.0f;  // xxxYWB scaling by 0 causes veneers, so I changed to 1.0
			Vector center;
			pBox->GetBoundsCenter(center);
			pGroup->TransScale(center, scale);
		}
	}

	return pGroup;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pBox - 
//			fStartAngle - 
//			iSides - 
//			fArc - 
//			iWallWidth - 
//			iAddHeight - 
//			bPreview - 
// Output : Returns a group containing the arch solids.
//-----------------------------------------------------------------------------
typedef float TorusPointList_t[ARC_MAX_POINTS][3];

CMapClass *CreateTorus(BoundBox *pBox, float fStartAngle, int iSides, float fArc, int iWallWidth, float flCrossSectionalRadius,
	float fRotationStartAngle, int iRotationSides, float fRotationArc, int iAddHeight, BOOL bPreview)
{
	float xCenter = (pBox->bmaxs[AXIS_X] + pBox->bmins[AXIS_X]) * 0.5f;
	float yCenter = (pBox->bmaxs[AXIS_Y] + pBox->bmins[AXIS_Y]) * 0.5f;
	float xRad = (pBox->bmaxs[AXIS_X] - xCenter);
	float yRad = (pBox->bmaxs[AXIS_Y] - yCenter);
	if (xRad < 0.0f )
	{
		xRad = 0.0f;
	}

	if (yRad < 0.0f )
	{
		yRad = 0.0f;
	}

	if ( flCrossSectionalRadius > (xRad * 0.5f) )
	{
		flCrossSectionalRadius = (xRad * 0.5f);
	}
	if ( flCrossSectionalRadius > (yRad * 0.5f) )
	{
		flCrossSectionalRadius = (yRad * 0.5f);
	}

	if ( iWallWidth < flCrossSectionalRadius )
	{
		flCrossSectionalRadius -= iWallWidth;
	}
	else
	{
		iWallWidth = flCrossSectionalRadius;
		flCrossSectionalRadius = 0.0f;
	}

	float flCrossSectionHalfWidth = flCrossSectionalRadius + iWallWidth;
	xRad -= flCrossSectionHalfWidth;
	yRad -= flCrossSectionHalfWidth;

	float fOuterPoints[ARC_MAX_POINTS][2];
	float fInnerPoints[ARC_MAX_POINTS][2];

	// create outer points (unrotated)
	MakeArcCenterRadius(0.0f, 0.0f,
		flCrossSectionalRadius + iWallWidth, flCrossSectionalRadius + iWallWidth, 
		iSides, fStartAngle, fArc, fOuterPoints);

	BOOL bCreateSouthFace = TRUE;
	if ( flCrossSectionalRadius != 0.0f )
	{
		// create inner points (unrotated)
		MakeArcCenterRadius(0.0f, 0.0f, flCrossSectionalRadius, flCrossSectionalRadius, 
			iSides, fStartAngle, fArc, fInnerPoints);
	}
	else
	{
		for( int i = 0; i < iSides; i++)
		{
			fInnerPoints[i][0] = fInnerPoints[i][1] = 0.0f;
		}
		bCreateSouthFace = FALSE;
	}

	// create group for segments
	CMapGroup *pGroup = new CMapGroup;

	TorusPointList_t innerPoints[2];
	TorusPointList_t outerPoints[2];
	TorusPointList_t *pStartInnerPoints;
	TorusPointList_t *pStartOuterPoints;
	TorusPointList_t *pEndInnerPoints = &innerPoints[1];
	TorusPointList_t *pEndOuterPoints = &outerPoints[1];
	int nCurrIndex = 0;

	float flCurrentZ = pBox->bmins[AXIS_Z] + iWallWidth + flCrossSectionalRadius;
	float flDeltaZ = (float)iAddHeight / (float)(iRotationSides);

	float flRotationAngle = fRotationStartAngle;
	float flRotationDeltaAngle = fRotationArc / iRotationSides;

	bool bIsCircle = ( iAddHeight == 0.0f ) && ( fRotationArc == 360.0f );
	++iRotationSides;
	for ( int i = 0; i != iRotationSides; ++i )
	{
		// This eliminates a seam in circular toruses
		if ( bIsCircle && (i == iRotationSides - 1) )
		{
			flRotationAngle = fRotationStartAngle;
		}

		float xCurrCenter, yCurrCenter;

		float flCosAngle = cos( DEG2RAD(flRotationAngle) );
		float flSinAngle = sin( DEG2RAD(flRotationAngle) );
		xCurrCenter = xCenter + xRad * flCosAngle;
		yCurrCenter = yCenter + yRad * flSinAngle;

		// Update buffers
		pStartInnerPoints = pEndInnerPoints;
		pStartOuterPoints = pEndOuterPoints;
		pEndInnerPoints = &innerPoints[nCurrIndex];
		pEndOuterPoints = &outerPoints[nCurrIndex];
		nCurrIndex = 1 - nCurrIndex;

		// Transform points into actual space.
		int jPrevPoint = -1;
		int j = 0;
		do
		{
			// x original is transformed into x/y based on rotation
			// y original is transformed into z
			(*pEndInnerPoints)[j][0] = xCurrCenter + fInnerPoints[j][0] * flCosAngle;
			(*pEndInnerPoints)[j][1] = yCurrCenter + fInnerPoints[j][0] * flSinAngle;
			(*pEndInnerPoints)[j][2] = flCurrentZ + fInnerPoints[j][1];

			(*pEndOuterPoints)[j][0] = xCurrCenter + fOuterPoints[j][0] * flCosAngle;
			(*pEndOuterPoints)[j][1] = yCurrCenter + fOuterPoints[j][0] * flSinAngle;
			(*pEndOuterPoints)[j][2] = flCurrentZ + fOuterPoints[j][1];

			// We'll use the j == 0 data when iNextPoint = iSides - 1
			if (( i != 0 ) && ( jPrevPoint != -1 ))
			{
				CMapSolid *pSolid = CreateSegment(
					*pStartOuterPoints, *pStartInnerPoints,
					*pEndOuterPoints, *pEndInnerPoints,
					jPrevPoint, j, bCreateSouthFace);

				pGroup->AddChild(pSolid);
			}

			jPrevPoint = j;
			++j;
		} while( jPrevPoint != iSides );

		flRotationAngle += flRotationDeltaAngle;
		flCurrentZ += flDeltaZ;

		if ( flRotationAngle >= 360.0f )
		{
			flRotationAngle -= 360.0f;
		}
	}

	pGroup->CalcBounds(TRUE);

	if (Options.general.bStretchArches)
	{
		// make sure size of group's bounds are size of original bounds -
		//  if not, scale up. this can happen when we use rotation.
		Vector objsize, boundsize;
		pBox->GetBoundsSize(boundsize);
		pGroup->GetBoundsSize(objsize);

		if (boundsize[AXIS_X] > objsize[AXIS_X] || 
			boundsize[AXIS_Y] > objsize[AXIS_Y])
		{
			Vector scale;
			scale[AXIS_X] = boundsize[AXIS_X] / objsize[AXIS_X];
			scale[AXIS_Y] = boundsize[AXIS_Y] / objsize[AXIS_Y];
			scale[AXIS_Z] = 1.0f;  // xxxYWB scaling by 0 causes veneers, so I changed to 1.0
			Vector center;
			pBox->GetBoundsCenter(center);
			pGroup->TransScale(center, scale);
		}
	}

	return pGroup;
}