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

#include "stdafx.h"
#include "hammer.h"
#include "hammer_mathlib.h"
#include "TorusDlg.h"

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


static LPCTSTR pszSection = "Torus";

void MakeArcCenterRadius(float xCenter, float yCenter, float xrad, float yrad, int npoints, float start_ang, float fArc, float points[][2]);
void MakeArc(float x1, float y1, float x2, float y2, int npoints, float start_ang, float fArc, float points[][2]);

CTorusDlg::CTorusDlg(Vector& boxmins, Vector& boxmaxs, CWnd* pParent /*=NULL*/)
	: CDialog(CTorusDlg::IDD, pParent)
{
	bmins = boxmins;
	bmaxs = boxmaxs;

	//{{AFX_DATA_INIT(CTorusDlg)
	m_iSides = 0;
	m_iWallWidth = 0;
	m_iAddHeight = 0;
	m_fArc = 0.0f;
	m_fAngle = 0.0f;
	m_fRotationArc = 0.0f;
	m_fRotationAngle = 0.0f;
	m_iRotationSides = 0;
	m_fCrossSectionRadius = 0;
	//}}AFX_DATA_INIT

	// load up old defaults
	CString str;
	m_iWallWidth = AfxGetApp()->GetProfileInt(pszSection, "Wall Width", 32);
	str = AfxGetApp()->GetProfileString(pszSection, "Arc_", "360");
	m_fArc = atof(str);
	m_iSides = AfxGetApp()->GetProfileInt(pszSection, "Sides", 16);
	str = AfxGetApp()->GetProfileString(pszSection, "Start Angle_", "0");
	m_fAngle = atof(str);

	str = AfxGetApp()->GetProfileString(pszSection, "Rotation Arc_", "360");
	m_fRotationArc = atof(str);
	m_iRotationSides = AfxGetApp()->GetProfileInt(pszSection, "Rotation Sides", 16);
	str = AfxGetApp()->GetProfileString(pszSection, "Rotation Start Angle_", "0");
	m_fRotationAngle = atof(str);
	
	m_iAddHeight = AfxGetApp()->GetProfileInt(pszSection, "Add Height", 0);

	Vector vecSize;
	VectorSubtract( bmaxs, bmins, vecSize );
	if ( m_iAddHeight > vecSize.z )
	{
		m_iAddHeight = vecSize.z;
	}

	// This is the maximum cross-section radius
	m_fCrossSectionRadius = MaxTorusCrossSectionRadius();
}

void CTorusDlg::SaveValues()
{
	CString str;
	AfxGetApp()->WriteProfileInt(pszSection, "Wall Width", m_iWallWidth);
	str.Format("%f", m_fArc);
	AfxGetApp()->WriteProfileString(pszSection, "Arc_", str);
	AfxGetApp()->WriteProfileInt(pszSection, "Sides", m_iSides);
	str.Format("%f", m_fAngle);
	AfxGetApp()->WriteProfileString(pszSection, "Start Angle_", str);

	str.Format("%f", m_fRotationArc);
	AfxGetApp()->WriteProfileString(pszSection, "Rotation Arc_", str);
	str.Format("%f", m_fRotationAngle);
	AfxGetApp()->WriteProfileString(pszSection, "Rotation Start Angle_", str);
	AfxGetApp()->WriteProfileInt(pszSection, "Rotation Sides", m_iRotationSides);
	AfxGetApp()->WriteProfileInt(pszSection, "Add Height", m_iAddHeight);
}

void CTorusDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CTorusDlg)
	DDX_Control(pDX, IDC_ANGLESPIN, m_cStartAngleSpin);
	DDX_Control(pDX, IDC_WALLWIDTHSPIN, m_cWallWidthSpin);
	DDX_Control(pDX, IDC_WALLWIDTH, m_cWallWidth);
	DDX_Control(pDX, IDC_SIDESSPIN, m_cSidesSpin);
	DDX_Control(pDX, IDC_SIDES, m_cSides);
	DDX_Control(pDX, IDC_ARCSPIN, m_cArcSpin);
	DDX_Control(pDX, IDC_ARC, m_cArc);
	DDX_Control(pDX, IDC_PREVIEW, m_cPreview);
	DDX_Control(pDX, IDC_PREVIEW_TOP_VIEW, m_cTopViewPreview);
	DDX_Text(pDX, IDC_SIDES, m_iSides);
	DDV_MinMaxInt(pDX, m_iSides, 3, 2048);
	DDX_Text(pDX, IDC_WALLWIDTH, m_iWallWidth);
	DDX_Text(pDX, IDC_ADDHEIGHT, m_iAddHeight);
	DDX_Text(pDX, IDC_ARC, m_fArc);
	DDV_MinMaxFloat(pDX, m_fArc, 8.f, 360.f);
	DDX_Text(pDX, IDC_ANGLE, m_fAngle);
	DDV_MinMaxFloat(pDX, m_fAngle, -360.0f, 360.f);
	DDX_Text(pDX, IDC_ROTATION_ARC, m_fRotationArc);
	DDV_MinMaxFloat(pDX, m_fRotationArc, 0.f, 3600.f);
	DDX_Text(pDX, IDC_ROTATION_ANGLE, m_fRotationAngle);
	DDV_MinMaxFloat(pDX, m_fRotationAngle, -360.0f, 360.f);
	DDX_Text(pDX, IDC_ROTATION_SIDES, m_iRotationSides);
	DDV_MinMaxInt(pDX, m_iRotationSides, 3, 2048);
	DDX_Text(pDX, IDC_CROSS_SECTION_RADIUS, m_fCrossSectionRadius);
	DDV_MinMaxFloat(pDX, m_fCrossSectionRadius, 0.f, 5000.f);
	//}}AFX_DATA_MAP

	if ( pDX->m_bSaveAndValidate )
	{
		Vector vecSize;
		VectorSubtract( bmaxs, bmins, vecSize );
		if ( m_iAddHeight > vecSize.z )
		{
			m_iAddHeight = vecSize.z;
		}
	}
}


BEGIN_MESSAGE_MAP(CTorusDlg, CDialog)
	//{{AFX_MSG_MAP(CTorusDlg)
	ON_EN_CHANGE(IDC_ARC, OnChangeArc)
	ON_EN_CHANGE(IDC_ROTATION_ARC, OnChangeTorusArc)
	ON_BN_CLICKED(IDC_CIRCLE, OnCircle)
	ON_BN_CLICKED(IDC_TORUS_COMPUTE_RADIUS, OnComputeRadius)
	ON_EN_UPDATE(IDC_SIDES, OnUpdateSides)
	ON_EN_UPDATE(IDC_WALLWIDTH, OnUpdateWallwidth)
	ON_WM_PAINT()
	ON_BN_CLICKED(IDC_TORUS_PREVIEW, OnTorusPreview)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTorusDlg message handlers

void CTorusDlg::OnChangeArc() 
{
}

void CTorusDlg::OnChangeTorusArc() 
{
}

void CTorusDlg::OnCircle() 
{
	m_cArcSpin.SetPos(360);
	OnTorusPreview();
}

void CTorusDlg::OnComputeRadius() 
{
	UpdateData( TRUE );

	// This is the maximum cross-section radius
	m_fCrossSectionRadius = MaxTorusCrossSectionRadius();
	UpdateData( FALSE );
	OnTorusPreview();
}

void CTorusDlg::OnUpdateSides() 
{
}

void CTorusDlg::OnUpdateWallwidth() 
{
}

BOOL CTorusDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	m_cArcSpin.SetRange(8, 360);
	m_cSidesSpin.SetRange(3, 100);
	m_cWallWidthSpin.SetRange(2, m_iMaxWallWidth);
	m_cStartAngleSpin.SetRange(0, 360);

	m_cPreview.ShowWindow(SW_HIDE);
	m_cTopViewPreview.ShowWindow(SW_HIDE);

	bInitialized = TRUE;
	return TRUE;
}

void CTorusDlg::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	CBrush black(RGB(0,0,0));
	CBrush grey(RGB(128,128,128));

	// Do not call CDialog::OnPaint() for painting messages
	CRect rcPreview;
	m_cPreview.GetWindowRect(&rcPreview);
	ScreenToClient(&rcPreview);
	dc.FillRect(rcPreview, &black);

	DrawTorusCrossSection( &dc );

	rcPreview.InflateRect(1,1);
	dc.FrameRect(rcPreview, &grey);
	ValidateRect(rcPreview);

	m_cTopViewPreview.GetWindowRect(&rcPreview);
	ScreenToClient(&rcPreview);
	dc.FillRect(rcPreview, &black);

	DrawTorusTopView( &dc );

	rcPreview.InflateRect(1,1);
	dc.FrameRect(rcPreview, &grey);
	ValidateRect(rcPreview);

	bInitialized = TRUE;
}

void CTorusDlg::OnTorusPreview() 
{
	// 
	// Build preview.
	//
	bInitialized = TRUE;
	InvalidateRect(NULL);
	UpdateWindow();
}

CTorusDlg::~CTorusDlg()
{
}


//-----------------------------------------------------------------------------
// Gets the inner radius of the torus cross-section
//-----------------------------------------------------------------------------
float CTorusDlg::MaxTorusCrossSectionRadius() const
{
	Vector vecSize;
	VectorSubtract( bmaxs, bmins, vecSize );

	float flTorusRadius = (vecSize.z - m_iAddHeight);

	// Check for multiple revolutions...
	if ( ( m_fRotationArc > 360 ) || (( m_fRotationArc == 360 ) && ( m_iAddHeight != 0 )) )
	{
		// Height per revolution
		float flHeightPerRev = 360.0f * m_iAddHeight / m_fRotationArc;
		if ( flHeightPerRev < flTorusRadius )
		{
			flTorusRadius = flHeightPerRev;
		}
	}

	// Also constrain it based on x & y too...
	if ( (vecSize.x * 0.5f) < flTorusRadius )
	{
		flTorusRadius = vecSize.x * 0.5f;
	}

	if ( (vecSize.y * 0.5f) < flTorusRadius )
	{
		flTorusRadius = vecSize.y * 0.5f;
	}

	flTorusRadius *= 0.5f;
	if ( flTorusRadius < m_iWallWidth )
	{
		flTorusRadius = m_iWallWidth;
	}

	return flTorusRadius;
}


//-----------------------------------------------------------------------------
// Gets the inner radius of the torus cross-section
//-----------------------------------------------------------------------------
float CTorusDlg::GetTorusCrossSectionRadius() const
{
	Vector vecSize;
	VectorSubtract( bmaxs, bmins, vecSize );

	float flTorusRadius = m_fCrossSectionRadius;

	// Also constrain it based on x & y too...
	if ( (vecSize.x * 0.25f) < flTorusRadius )
	{
		flTorusRadius = vecSize.x * 0.25f;
	}

	if ( (vecSize.y * 0.25f) < flTorusRadius )
	{
		flTorusRadius = vecSize.y * 0.25f;
	}

	return flTorusRadius;
}


//-----------------------------------------------------------------------------
// Draws the torus cross-section
//-----------------------------------------------------------------------------
void CTorusDlg::DrawTorusCrossSection(CDC* pDC )
{
	int iSides, iWallWidth;
	float fArc, fStartAngle;

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

	float fScaleX;
	float fScaleY;

	UpdateData(TRUE);

	CPen m_hPen, *pOldPen;

	m_hPen.CreatePen(PS_SOLID, 1, RGB(255,255,255));

	pOldPen = pDC->SelectObject(&m_hPen);

	CRect rcItem;
	m_cPreview.GetWindowRect(&rcItem);
	ScreenToClient(&rcItem);

	CPoint pt;
	pt.x = rcItem.left + rcItem.Width()  / 2;
	pt.y = rcItem.top  + rcItem.Height() / 2;

	float flMaxRadius = GetTorusCrossSectionRadius();
	iWallWidth = m_iWallWidth;
	if ( iWallWidth > flMaxRadius )
		iWallWidth = flMaxRadius;
	float flTorusRadius = flMaxRadius - iWallWidth;

	float flDeltaZ = bmaxs[2] - bmins[2];
	if (flDeltaZ)
	{
		if ( flDeltaZ < flMaxRadius * 2.0f )
		{
			flDeltaZ = flMaxRadius * 2.0f;
		}
		fScaleX = rcItem.Width()/flDeltaZ;
		fScaleY = rcItem.Height()/flDeltaZ;
	}
	else
	{
		fScaleX = fScaleY = 1.0f;
//		fScaleX = rcItem.Width() / (2.0f * flMaxRadius);
//		fScaleY = rcItem.Height() / (2.0f * flMaxRadius);
	}

	fArc = m_fArc;
	fStartAngle = m_fAngle;
	iSides = m_iSides;

	MakeArcCenterRadius(0, 0,
		flTorusRadius + iWallWidth, flTorusRadius + iWallWidth, 
		iSides, fStartAngle, fArc, fOuterPoints);

	MakeArcCenterRadius(0, 0, 
		flTorusRadius, flTorusRadius, 
		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
		
	Vector points[4];	
	for (i = 0; i < iSides; i++)
	{
		int iNextPoint = i+1;
		if (iNextPoint >= iSides + 1)
		{
			iNextPoint = 0;
		}

		points[0][0] = fOuterPoints[i][0];
		points[0][1] = fOuterPoints[i][1];

		points[1][0] = fOuterPoints[iNextPoint][0];
		points[1][1] = fOuterPoints[iNextPoint][1];

		points[2][0] = fInnerPoints[iNextPoint][0];
		points[2][1] = fInnerPoints[iNextPoint][1];

		points[3][0] = fInnerPoints[i][0];
		points[3][1] = fInnerPoints[i][1];

		for (int j = 0; j < 4; j++)
		{
			points[j][0] = fScaleX * points[j][0];
			points[j][1] = fScaleY * points[j][1];
		}

		pDC->MoveTo(pt.x + (int)points[1][0], pt.y - (int)points[1][1]);
		pDC->LineTo(pt.x + (int)points[0][0], pt.y - (int)points[0][1]);
		pDC->LineTo(pt.x + (int)points[3][0], pt.y - (int)points[3][1]);
		pDC->LineTo(pt.x + (int)points[2][0], pt.y - (int)points[2][1]);
	}

	// Close the cross-section off...
	if ( fArc != 360.0f )
	{
		pDC->LineTo(pt.x + (int)points[1][0], pt.y - (int)points[1][1]);
	}

	pDC->SelectObject(pOldPen);
}


void CTorusDlg::DrawTorusTopView( CDC* pDC )
{
	int i;
	float fOuterPoints[ARC_MAX_POINTS][2];
	float fInnerPoints[ARC_MAX_POINTS][2];

	float fScaleX;
	float fScaleY;

	UpdateData(TRUE);

	CPen m_hPen, *pOldPen;

	m_hPen.CreatePen(PS_SOLID, 1, RGB(255,255,255));

	pOldPen = pDC->SelectObject(&m_hPen);

	CRect rcItem;
	m_cTopViewPreview.GetWindowRect(&rcItem);
	ScreenToClient(&rcItem);

	CPoint pt;
	pt.x = rcItem.left + rcItem.Width()  / 2;
	pt.y = rcItem.top  + rcItem.Height() / 2;

	if (bmaxs[0] - bmins[0])
	{
		fScaleX = rcItem.Width() /(bmaxs[0] - bmins[0]);
	}
	else
	{
		fScaleX = 1.0f;
	}
	
	if (bmaxs[1] - bmins[1])
	{
		fScaleY = rcItem.Height() /(bmaxs[1] - bmins[1]);
	}
	else
	{
		fScaleY = 1.0f;
	}

	int iSides, iWallWidth;
	float fArc, fStartAngle;

	fArc = m_fRotationArc;
	fStartAngle = m_fRotationAngle;
	iSides = m_iRotationSides;
	iWallWidth = GetTorusCrossSectionRadius() * 2.0f;

	float xCenter = (bmaxs[0] + bmins[0]) * 0.5f;
	float yCenter = (bmaxs[1] + bmins[1]) * 0.5f;
	float xRad = (bmaxs[0] - xCenter - iWallWidth);
	float yRad = (bmaxs[1] - yCenter - iWallWidth);
	if (xRad < 0.0f )
	{
		xRad = 0.0f;
	}
	if (yRad < 0.0f )
	{
		yRad = 0.0f;
	}

	MakeArcCenterRadius(xCenter, yCenter, xRad + iWallWidth, yRad + iWallWidth, 
		iSides,	fStartAngle, fArc, fOuterPoints);

	MakeArcCenterRadius(xCenter, yCenter, xRad, yRad,
		iSides, fStartAngle, fArc, fInnerPoints);
	
	Vector vecCenter;
	VectorLerp( bmins, bmaxs, 0.5f, vecCenter );
	
	Vector points[4];	
	for (i = 0; i < iSides; i++)
	{
		int iNextPoint = i+1;
		if (iNextPoint >= iSides + 1)
		{
			iNextPoint = 0;
		}

		points[0][0] = fOuterPoints[i][0];
		points[0][1] = fOuterPoints[i][1];

		points[1][0] = fOuterPoints[iNextPoint][0];
		points[1][1] = fOuterPoints[iNextPoint][1];

		points[2][0] = fInnerPoints[iNextPoint][0];
		points[2][1] = fInnerPoints[iNextPoint][1];

		points[3][0] = fInnerPoints[i][0];
		points[3][1] = fInnerPoints[i][1];

		for (int j = 0; j < 4; j++)
		{
			points[j][0] = fScaleX * (points[j][0] - vecCenter[0]);
			points[j][1] = fScaleY * (points[j][1] - vecCenter[1]);
		}

		pDC->MoveTo(pt.x + (int)points[1][0], pt.y - (int)points[1][1]);
		pDC->LineTo(pt.x + (int)points[0][0], pt.y - (int)points[0][1]);
		pDC->LineTo(pt.x + (int)points[3][0], pt.y - (int)points[3][1]);
		pDC->LineTo(pt.x + (int)points[2][0], pt.y - (int)points[2][1]);
	}

	// Close the cross-section off...
	if ( fArc != 360.0f )
	{
		pDC->LineTo(pt.x + (int)points[1][0], pt.y - (int)points[1][1]);
	}

	pDC->SelectObject(pOldPen);
}