// 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 ¢er, 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); }