// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "std_afx.h"
#include "object_viewer.h"
#include "basis_edit.h"
#include "nel/misc/matrix.h"
#include "nel/misc/vector.h"

/////////////////////////////////////////////////////////////////////////////
// CBasisEdit dialog


CBasisEdit::CBasisEdit(CWnd* pParent /*=NULL*/)
{
	//{{AFX_DATA_INIT(CBasisEdit)
	m_Psi = 0;
	m_Theta = 0;
	m_Phi = 0;
	//}}AFX_DATA_INIT
}


void CBasisEdit::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CBasisEdit)
	DDX_Control(pDX, IDC_PHI_SCROLLBAR, m_PhiCtrl);
	DDX_Control(pDX, IDC_PSI_SCROLLBAR, m_PsiCtrl);
	DDX_Control(pDX, IDC_THETA_SCROLLBAR, m_ThetaCtrl);
	DDX_Text(pDX, IDC_PSI_VALUE, m_Psi);
	DDX_Text(pDX, IDC_THETA_VALUE, m_Theta);
	DDX_Text(pDX, IDC_PHI_VALUE, m_Phi);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CBasisEdit, CDialog)
	//{{AFX_MSG_MAP(CBasisEdit)
	ON_WM_PAINT()
	ON_WM_HSCROLL()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CBasisEdit message handlers





void CBasisEdit::init(uint32 x, uint32 y, CWnd *pParent)
{
	nlassert(_Wrapper); // _Wrapper should have been set before init !!
	Create(IDD_BASIS_EDIT, pParent);
	RECT r;
	GetClientRect(&r);

	m_PsiCtrl.SetScrollRange(0, 359);
	m_ThetaCtrl.SetScrollRange(0, 359);
	m_PhiCtrl.SetScrollRange(0, 359);
	
	MoveWindow(x, y, r.right, r.bottom);	
	updateAnglesFromReader();
	ShowWindow(SW_SHOW);	
	UpdateData(FALSE);
}





// build an euler matrix
NLMISC::CMatrix  BuildEulerMatrix(float psi, float theta, float phi)
{
	float ca = cosf(psi), sa = sinf(psi)
		  , cb = cosf(theta), sb = sinf(theta)
		  , cc = cosf(phi), sc = sinf(phi);
	NLMISC::CMatrix m;
	m.identity();
	m.setRot(NLMISC::CVector(ca * cb * cc - sa * sc, -cc * sa - ca * cb *sc, ca * sb)
			 ,NLMISC::CVector(cb * cc * sa + ca * sc, ca * cc - cb * sa * sc, sa *sb)
			 ,NLMISC::CVector(-cc * sb, sb * sc, cb)
			);
	return m;
}

// get back the euler angles from a matrix
NLMISC::CVector GetEulerAngles(const NLMISC::CMatrix &mat)
{
	float m[3][3];
	// we got cos theta = m33
	NLMISC::CVector v[3];
	mat.getRot(v[0], v[1], v[2]);
	for (uint l = 0; l < 3; ++l)
	{
		m[0][l] = v[l].x; m[1][l] = v[l].y; m[2][l] = v[l].z; 
	}
	
	// there are eight triplet that may satisfy the equation
	// we compute them all, and test them against the matrix

	float b0, b1, a0, a1, a2, a3, c0, c1, c2, c3;
	b0 = acosf(m[2][2]);
	b1 = (float) NLMISC::Pi - b0;
	float sb0 = sinf(b0), sb1 = sinf(b1);
	if (fabsf(sb0) > 10E-6)
	{
		a0 = m[2][0] / sb0;
		c0 = m[1][2] / sb0;
	}
	else
	{
		a0 = c0 = 1.f;
	}
	if (fabs(sb1) > 10E-6)
	{
		a1 = m[2][0] / sb1;
		c1 = m[1][2] / sb1;
	}
	else
	{
		a1 = c1 = 1.f;
	}


	a2 = (float) NLMISC::Pi - a0;
	a3 = (float) NLMISC::Pi - a1;
	
	
	c2 = (float) NLMISC::Pi - c0;
	c3 = (float) NLMISC::Pi - c1;

	NLMISC::CVector sol[] =
	{
		NLMISC::CVector(b0, a0, c0)
		,NLMISC::CVector(b0, a2, c0)
		,NLMISC::CVector(b0, a0, c2)
		,NLMISC::CVector(b0, a2, c2)
		,NLMISC::CVector(b1, a1, c1)
		,NLMISC::CVector(b1, a3, c1)
		,NLMISC::CVector(b1, a1, c3)
		,NLMISC::CVector(b1, a3, c3)
	};

	// now we take the triplet that fit best the 6 other equations

	float bestGap = 0.f;
	uint bestIndex;

	for (uint k = 0; k < 8; ++k)
	{
		float ca = cosf(sol[k].x), sa = sinf(sol[k].x)
		  , cb = cosf(sol[k].y), sb = sinf(sol[k].y)
		  , cc = cosf(sol[k].z), sc = sinf(sol[k].z);

		float gap = fabsf(m[0][0] - ca * cb * cc + sa * sc);
		gap += fabsf(m[1][0] + cc * sa + ca * cb *sc);
		gap += fabsf(m[0][1] - cb * cc * sa - ca * sc);
		gap += fabsf(m[0][1] - cb * cc * sa - ca * sc);
		gap += fabsf(m[1][1] - ca * cc + cb * sa * sc);
		gap += fabsf(m[2][1] - sb *ca);
		gap += fabsf(m[0][2] + cc * sb);

		if (k == 0 || gap < bestGap)
		{
			bestGap = gap;
			bestIndex  = k; 

		}
	}
	return sol[bestIndex];
}



void CBasisEdit::updateAnglesFromReader()
{
	nlassert(_Wrapper); // _Wrapper should have been set before init !!
	
	// read plane basis
	NL3D::CPlaneBasis pb = _Wrapper->get();
	NLMISC::CMatrix mat;
	mat.setRot(pb.X, pb.Y, pb.X ^ pb.Y);
	NLMISC::CVector angles = GetEulerAngles(mat);
	m_PsiCtrl.SetScrollPos((uint) (360.f * angles.x / (2.f * (float) NLMISC::Pi)));
	m_ThetaCtrl.SetScrollPos((uint) (360.f * angles.y / (2.f * (float) NLMISC::Pi)));
	m_PhiCtrl.SetScrollPos((uint) (360.f * angles.z / (2.f * (float) NLMISC::Pi)));
	UpdateData(FALSE);
}





// just project a vector with orthogonal proj
static CPoint BasisVectXForm(NLMISC::CVector &v, float size)
{
	const float sq = sqrtf(2.f) / 2.f;
	return CPoint((int) (size * (sq * (v.x + v.y) )), (int) (size * (-v.z + sq * (v.x - v.y))));
}

// draw a basis using the given colors, and hte given dc

void DrawBasisInDC(const CPoint &center, float size, const NLMISC::CMatrix &m, CDC &dc, NLMISC::CRGBA col[3])
{
	// draw the basis
	CPoint px  = center + BasisVectXForm(m.getI(), size);
	CPoint py  = center + BasisVectXForm(m.getJ(), size);
	CPoint pz  = center + BasisVectXForm(m.getK(), size);


	CPen p[3];
	p[0].CreatePen(PS_SOLID, 1, col[0].R + (col[0].G << 8) + (col[0].B << 16) );
	p[1].CreatePen(PS_SOLID, 1, col[1].R + (col[1].G << 8) + (col[1].B << 16) );
	p[2].CreatePen(PS_SOLID, 1, col[2].R + (col[2].G << 8) + (col[2].B << 16) );


	

	

	// draw letters indicating each axis
	// X
	CPen *old = dc.SelectObject(&p[0]);
	dc.MoveTo(center);
	dc.LineTo(px);
	dc.MoveTo(px + CPoint(2, 2));
	dc.LineTo(px + CPoint(5, 5));
	dc.MoveTo(px + CPoint(4, 2));
	dc.LineTo(px + CPoint(3, 5));

	 
	// Y
	dc.SelectObject(&p[1]);	
	dc.MoveTo(center);
	dc.LineTo(py);
	
	dc.MoveTo(py + CPoint(2, 2));
	dc.LineTo(py + CPoint(4, 4));
	dc.MoveTo(py + CPoint(4, 2));
	dc.LineTo(py + CPoint(1, 5));

	// Z
	dc.SelectObject(&p[2]);
	dc.MoveTo(center);
	dc.LineTo(pz);

	dc.MoveTo(pz + CPoint(2, 2));
	dc.LineTo(pz + CPoint(5, 2));
	dc.MoveTo(pz + CPoint(4, 2));
	dc.LineTo(pz + CPoint(1, 5));
	dc.MoveTo(pz + CPoint(2, 4));
	dc.LineTo(pz + CPoint(5, 4));

	dc.SelectObject(old);

}

void CBasisEdit::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	NLMISC::CRGBA c1[] ={ NLMISC::CRGBA::White, NLMISC::CRGBA::White, NLMISC::CRGBA::White };
	NLMISC::CRGBA c2[] ={ NLMISC::CRGBA::Green, NLMISC::CRGBA::Green, NLMISC::CRGBA::Red };

	if (_Wrapper)
	{
		
		// read plane basis
		NL3D::CPlaneBasis pb = _Wrapper->get();

		CPoint center(20, 20);
		// draw a white box on the left				
		dc.FillSolidRect(0, 0, 39, 39, 0x777777);

	
		NLMISC::CMatrix m;
		m.identity();
		DrawBasisInDC(center, 18, m, dc, c1);
		m.setRot(pb.X, pb.Y, pb.X ^ pb.Y);
		DrawBasisInDC(center, 18, m, dc, c2);

	}
	
	// Do not call CDialog::OnPaint() for painting messages
}

void CBasisEdit::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	UpdateData();
	if (nSBCode == SB_THUMBPOSITION || nSBCode == SB_THUMBTRACK)
	{
		NLMISC::CVector angles(2.f * (float) NLMISC::Pi * m_PsiCtrl.GetScrollPos() / 360.f
					   , 2.f * (float) NLMISC::Pi * m_ThetaCtrl.GetScrollPos() / 360.f
					   , 2.f * (float) NLMISC::Pi * m_PhiCtrl.GetScrollPos() / 360.f
					  );
		if (pScrollBar == &m_PsiCtrl)
		{
			angles.x =  2.f * (float) NLMISC::Pi * nPos / 360.f;
			m_PsiCtrl.SetScrollPos(nPos);				
		}

		if (pScrollBar == &m_ThetaCtrl)
		{
			angles.y =  2.f * (float) NLMISC::Pi * nPos / 360.f;
			m_ThetaCtrl.SetScrollPos(nPos);				
		}

		if (pScrollBar == &m_PhiCtrl)
		{
			angles.z =  2.f * (float) NLMISC::Pi * nPos / 360.f;
			m_PhiCtrl.SetScrollPos(nPos);				
		}

		NLMISC::CMatrix mat = BuildEulerMatrix(angles.x, angles.y, angles.z);
		NL3D::CPlaneBasis pb;
		pb.X = mat.getI();
		pb.Y = mat.getJ();
		_Wrapper->setAndUpdateModifiedFlag(pb);
		Invalidate();
	}		
	CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
	UpdateData(FALSE);
}