//ROMTEAM WorldEditor
////////////////////////////////////////////////////////////////////////////////////////
// File        : Indexed Triangular Mesh editor
// Author      : Nicolae Suparatu
// Date        : 
// Description :
////////////////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ctriedit.h"
#include <math.h>
#define PI	3.1415926


static float distance(float dx, float dy){ return dx*dx + dy*dy;}

//**************************************** Class CTriEdit ********************************************
//-----------------------------------------------------------------------------------
CTriEdit::CTriEdit()
{	
	m_mesh = NULL;
	m_bStackTopOK = TRUE;
	m_ux = m_uy = 0;
	m_iUnderCursor = m_iCrtVertex = -1;
	m_SelRect.left = -1;	// no selection rectangle
	m_fLinearity = 1.f;
	m_bVisibility = 0;
	hdcmem = NULL;
	bmp = NULL;
	m_GreenPen = CreatePen(PS_SOLID, 1, PALETTERGB(160, 255, 160));
	m_RedPen = CreatePen(PS_SOLID, 1, PALETTERGB(255, 20, 20));

	m_iUndoCnt = m_iRedoCnt = m_iUndoSP = 0;
	ZeroMemory(m_UndoStack, sizeof(m_UndoStack));
}


//------------------------------------------------------------------------------------
CTriEdit::~CTriEdit()
{	
	if(m_mesh) delete m_mesh;
	if(bmp) DeleteObject(SelectObject(hdcmem, bmp));
	if(hdcmem) DeleteDC(hdcmem);
	DeleteObject(m_GreenPen);
	DeleteObject(m_RedPen);
	for(int i=0; i<MAXUNDOSTACK; i++)	
		if(m_UndoStack[i]) delete m_UndoStack[i];
}


//------------------------------------------------------------------------------------
// Set the editor window
void CTriEdit::SetHWnd(HWND hwnd)
{ 
	m_hwnd = hwnd; 
	if(bmp) DeleteObject(SelectObject(hdcmem, bmp)), bmp = NULL;
	if(hdcmem) DeleteDC(hdcmem);
	HDC hdc = GetDC(hwnd);
	hdcmem = CreateCompatibleDC(hdc);
	ReleaseDC(hwnd, hdc);
	Invalidate();
}



//------------------------------------------------------------------------------------
// Paint the scene (Double buffering method, to avoid image flashing)
void CTriEdit::OnPaint(HDC hdc)
{
	int i, j, k;
	if(!m_hwnd) return;

	HPEN oldpen = (HPEN)SelectObject(hdcmem, m_GreenPen);
	HBRUSH holdbrush = (HBRUSH)SelectObject(hdcmem, GetStockObject(BLACK_BRUSH));
	PatBlt(hdcmem, 0, 0, m_r.right, m_r.bottom, BLACKNESS);

	if(m_mesh)
	{	
		if(m_bVisibility)
		{

// Draw with hidden faces
			POINT p[3];
			char *sel = new char[m_mesh->m_iVerts];
			for(i=0; i<m_mesh->m_iVerts; i++) sel[i] = m_mesh->m_sel[i];
			for(i=0; i<m_mesh->m_iFaces; i++)
			{	
				WORD *face = (WORD*)(m_mesh->m_face + m_mesh->m_sort[i]);
				for(k=0; k<3; k++)
				{
					p[k].x = (int)m_mesh->m_prj[face[k]].x;
					p[k].y = (int)m_mesh->m_prj[face[k]].y;
				}
				Polygon(hdcmem, p, 3);
				for(k=0; k<3; k++) if((sel[face[k]]-=2) == 1) Select(hdcmem, p[k].x, p[k].y, m_RedPen);
			}
			delete []sel;
		}
		else
		{

// Draw all edges, without hidden faces
			for(int i=0; i<m_mesh->m_iEdges; i++)
			{	
				CVertex *p = m_mesh->m_prj + m_mesh->m_edge[i][0];
				MoveToEx(hdcmem, (int)p->x, (int)p->y, NULL);
				p = m_mesh->m_prj + m_mesh->m_edge[i][1];
				LineTo(hdcmem, (int)p->x, (int)p->y);
			}

// Draw selected vertices, as small red rectangles.
			if(m_mesh->m_iVerts > 1) 
				for(i=0; i<m_mesh->m_iVerts; i++)
					if(m_mesh->Selected(i)) Select(hdcmem, (int)m_mesh->m_prj[i].x, (int)m_mesh->m_prj[i].y, m_RedPen);
		}
	}
	SelectObject(hdcmem, oldpen);
	SelectObject(hdcmem, holdbrush);

// Draw the selection rectangle
	if(m_SelRect.left != -1) 
	{	
		RECT r;
		NormalizeRect(m_SelRect, r);
		DrawFocusRect(hdcmem, &r);
	}

// draw the deformation filter
	oldpen = (HPEN)SelectObject(hdcmem, GetStockObject(WHITE_PEN));
	int dx = (m_r.right - m_r.left)/6;
	int dy = (m_r.bottom - m_r.top)/6;
	MoveToEx(hdcmem, 0, dy, NULL);
	for(i=0; i<=20; i++)
	{
		j = (int)((Effect((float)(i-10.f)*(i-10.f)/400)*dy));
		LineTo(hdcmem, i*dx/20, dy - j);
	}
	LineTo(hdcmem, dx, dy);
	SelectObject(hdcmem, oldpen);

// Copy the BackBuffer to the FrameBuffer
	BitBlt(hdc, 0, 0, m_r.right, m_r.bottom, hdcmem, 0, 0, SRCCOPY);
}



//------------------------------------------------------------------------------------
// Select specified point
void CTriEdit::Select(HDC hdc, int x, int y, HPEN pen)
{
	HPEN oldpen = (HPEN)SelectObject(hdc, pen);
	Rectangle(hdc, x-2, y-2, x+2, y+2);
	SelectObject(hdc, oldpen);
}


//------------------------------------------------------------------------------------
// Create the mesh (vertex list + edge list)
void CTriEdit::InitMesh(int x, int y)
{
// Create a new mesh
	if(m_mesh) delete m_mesh;
	m_mesh = new CMesh(x, y);

// set the initial rotation and create the screen projections list
	m_ux = 20;
	m_uy = 20;
	Rotate(0, 0);
	m_mesh->SortZ();
	m_iUndoCnt = m_iRedoCnt = m_iUndoSP = 0;
	Invalidate();
}



//------------------------------------------------------------------------------------
// Rotate the scene and create a new projection list
void CTriEdit::Rotate(float dux, float duy)
{
	if(!m_mesh || m_iCrtVertex != -1) return;	// if not mesh or a vertex is just in move, no rotation done
	m_ux += dux;
	m_uy += duy;
	m_mrot.SetRot(m_ux, m_uy);
	m_mesh->ComputePrj(m_r, m_mrot);
	if(m_bVisibility) m_mesh->SortZ();
	Invalidate();
}


//------------------------------------------------------------------------------------
// Called when the window is resized
void CTriEdit::Resize(int cx, int cy)
{
	SetRect(&m_r, 0, 0, cx, cy);
	if(bmp) DeleteObject(SelectObject(hdcmem, bmp));
	if(cx) 
	{	HDC hdc = GetDC(m_hwnd);
		bmp = CreateCompatibleBitmap(hdc, m_r.right, m_r.bottom);
		ReleaseDC(m_hwnd, hdc);
		bmp = (HBITMAP)SelectObject(hdcmem, bmp);
	}
	if(m_mesh) m_mesh->ComputePrj(m_r, m_mrot);	 
}


//------------------------------------------------------------------------------------
//	Set the cursor shape
int CTriEdit::OnSetCursor(int x, int y)
{
	if(!m_mesh) return 0;
	float dist = 10000;
	m_iUnderCursor = -1;

	for(int i=0; i<m_mesh->m_iVerts; i++)
	{	
		float d = distance(x - m_mesh->m_prj[i].x, y - m_mesh->m_prj[i].y);
		if(d <= dist && d < 26)
		{	
			dist = d;
			m_iUnderCursor = i;
		}
	}
	return m_iUnderCursor >= 0;
}


//------------------------------------------------------------------------------------
// Manage all mouse actions (vertex or group moving + draw selection rectangle)
void CTriEdit::OnMouseMove(POINT p)
{

	m_SelRect.right = p.x;
	m_SelRect.bottom = p.y;
	if(m_SelRect.left != -1) Invalidate();
	if(m_iCrtVertex == -1) return;
	int i = m_iUndoSP;
	AddModulo(i, -1);
	CMesh *tmpMesh = m_UndoStack[i];

	float sx = p.x - tmpMesh->m_prj[m_iCrtVertex].x;
	float sy = p.y - tmpMesh->m_prj[m_iCrtVertex].y;
	for(i=0; i<m_mesh->m_iVerts; i++)
	{	
		if(m_mesh->Selected(i)) 
		{	
			float eff = Effect(tmpMesh->m_v[i] - tmpMesh->m_v[m_iCrtVertex]);
			float dx = (tmpMesh->m_prj[i].x + sx*eff)/(m_r.right - m_r.left) - 0.5f;
			float dy = (m_r.bottom - tmpMesh->m_prj[i].y - sy*eff)/(m_r.bottom - m_r.top) - 0.5f;
			CVertex v(dx, dy, tmpMesh->m_prj[i].z);
			m_mesh->m_v[i] = v / m_mrot;
		}
	}
	m_bStackTopOK = TRUE;  // validate the stack top for Undo/Redo
	m_mesh->ComputePrj(m_r, m_mrot);
	Invalidate();
}


//------------------------------------------------------------------------------------
// Manage the mouse LBDown message
void CTriEdit::OnLButtonDown(UINT flags, POINT p)
{
	int i;
	SetCapture(m_hwnd);
	m_iCrtVertex = m_iUnderCursor;
	if(m_iCrtVertex != -1)
	{	
		if(flags & MK_CONTROL) m_mesh->Toggle(m_iCrtVertex);
		else 
			if(!m_mesh->Selected(m_iCrtVertex)) 
				for(i =0; i < m_mesh->m_iVerts; i++) m_mesh->Select(i, m_iCrtVertex == i);
			else;
		if(m_UndoStack[m_iUndoSP]) delete m_UndoStack[m_iUndoSP]; 
		m_UndoStack[m_iUndoSP] = new CMesh(*m_mesh); 
		AddModulo(m_iUndoSP, 1);
		if(++m_iUndoCnt >= MAXUNDOSTACK) m_iUndoCnt = MAXUNDOSTACK - 1;
		m_bStackTopOK = FALSE;  // the stack top is not yet valid
		m_iRedoCnt = 0;
		m_SelRect.left = -1;		// no selection rectangle
	}
	else if(m_mesh)
	{	
		if(!(flags & MK_CONTROL)) 
			for(i =0; i < m_mesh->m_iVerts; i++) m_mesh->Select(i, 0);
		SetRect(&m_SelRect, p.x, p.y, p.x, p.y);
	}
	Invalidate();
}


//------------------------------------------------------------------------------------
// Manage the mouse LBUp message
void CTriEdit::OnLButtonUp(UINT flags, POINT p)
{
	ReleaseCapture();
	m_iCrtVertex = -1;
	if(!m_bStackTopOK)  // if the stack top is not valid
	{
		AddModulo(m_iUndoSP, -1);	 // clean the stack top
		m_iUndoCnt--;
	}
	if(m_SelRect.left == -1) return;
	int i;
	RECT r;
	NormalizeRect(m_SelRect, r);
	for(i=0; i<m_mesh->m_iVerts; i++)
	{	
		POINT pt = {(int)m_mesh->m_prj[i].x, (int)m_mesh->m_prj[i].y}; 
		if(flags & MK_CONTROL) m_mesh->Or(i, PtInRect(&r, pt)); 
		else m_mesh->Select(i, PtInRect(&r, pt)); 
	}
	m_SelRect.left = -1;		// hide the selection rectangle
	Invalidate();
}


//------------------------------------------------------------------------------------
// Transform a source rectangle in a normalized one (left<right and top<bottom)
void CTriEdit::NormalizeRect(RECT &s, RECT &d)
{
	SetRect(&d, min(s.left, s.right), min(s.bottom, s.top),max(s.left, s.right), max(s.bottom, s.top));
}


void CTriEdit::SetLinearity(float l)
{
	m_fLinearity = 1 - l;
	Invalidate();
}


float CTriEdit::Effect(float dist)
{ 
	return (float)exp(-m_fLinearity*MAXLIN*dist);
}


void CTriEdit::OnUndo()
{
	if(!m_iUndoCnt)	return;
	if(!m_iRedoCnt) 
	{	
		if(m_UndoStack[m_iUndoSP]) delete m_UndoStack[m_iUndoSP]; 
		m_UndoStack[m_iUndoSP] = new CMesh(*m_mesh); 
	}
	AddModulo(m_iUndoSP, -1);
	*m_mesh = *m_UndoStack[m_iUndoSP];
	m_mesh->ComputePrj(m_r, m_mrot);
	m_iRedoCnt++;
	m_iUndoCnt--;
	Invalidate();
}


void CTriEdit::OnRedo()
{
	if(!m_iRedoCnt)	return;
	AddModulo(m_iUndoSP, 1);
	*m_mesh = *m_UndoStack[m_iUndoSP];
	m_mesh->ComputePrj(m_r, m_mrot);
	m_iRedoCnt--;
	m_iUndoCnt++;
	Invalidate();
}


void CTriEdit::AddModulo(int &i, int inc, int mod)
{
	i = (i + inc) % mod;
	if(i < 0) i += mod;
}
//*****************************************************************************************************



//******************************************** Class CMatrix ******************************************
//------------------------------------------------------------------------------------
CMatrix3::CMatrix3()
{	
	for(int i=0; i<3; i++)
		for(int j=0; j<3; j++)
			m[i][j] = (float)(i == j);
}


//------------------------------------------------------------------------------------
// Initialize a linear rotation matrix from 2 angles: around Y axis and around X axis, in this order
void CMatrix3::SetRot(float ux, float uy)
{ 
  float s1 = (float)sin(ux*PI/180.0); 
	float c1 = (float)cos(ux*PI/180.0);
  float s2 = (float)sin(uy*PI/180.0); 
	float c2 = (float)cos(uy*PI/180.0);
  m[0][0]	=	c1;
  m[0][1]	=	0;
  m[0][2]	=	s1;
  m[1][0]	=	s1*s2;
  m[1][1]	=	c2;
  m[1][2]	=	-s2*c1;
  m[2][0]	=	-s1*c2;
  m[2][1]	=	s2;
  m[2][2]	=	c1*c2;
}
//*****************************************************************************************************



//******************************************** Class CVertex ******************************************
//------------------------------------------------------------------------------------
// Multiply a CVertex with a 3X3 matrix
CVertex CVertex::operator *(CMatrix3& m)
{		
	CVertex v;
	for(int i=0; i<3; i++) 
		(&v.x)[i] = m(i, 0)*x + m(i, 1)*y + m(i, 2)*z;	
	return v;
}


//------------------------------------------------------------------------------------
// Multiply a CVertex with a orthogonal 3X3 matrix inverse
CVertex CVertex::operator /(CMatrix3& m)
{		
	CVertex v;
	for(int i=0; i<3; i++) 
		(&v.x)[i] = m(0, i)*x + m(1, i)*y + m(2, i)*z;	
	return v;
}


//------------------------------------------------------------------------------------
float CVertex::operator-(CVertex &v)
{
	return distance(x - v.x, z - v.z) + (y - v.y)*(y - v.y);
}
//*****************************************************************************************************



//******************************************** Class CMesh ******************************************
CMesh *CMesh::s_me;

//------------------------------------------------------------------------------------
// CMesh constructor
CMesh::CMesh(int x, int y)
{
	int i, j, k, l;

	m_iDimx = x;
	m_iDimy = y;
	m_iVerts = (x + 1) * (y + 1);
	m_iFaces = x * y * 2;
	m_iEdges = 3 * x * y + x + y;

// create the vertex list
	m_v = new CVertex[m_iVerts];
	m_sel = new char[m_iVerts];
	ZeroMemory(m_sel, m_iVerts);
	for(j=k=0; j<=y; j++)
		for(i=0; i<=x; i++, k++)
			m_v[k] = CVertex((float)i/x - 0.5f, 0, (float)j/y - 0.5f);
	
// create the edge list
	m_edge = (WORD(*)[2])malloc(2*sizeof(WORD)*m_iEdges);
	for(j=k=l=0; j<=y; j++)
		for(i=0; i<=x; i++, l++)
		{	
			if(i < x) 
			{	
				m_edge[k][0] = l;
				m_edge[k++][1] = l + 1;
				if(j < y)
				{	
					m_edge[k][0] = l;
					m_edge[k++][1] = l + x + 2;
				}
			}
			if(j < y)
			{	
				m_edge[k][0] = l;
				m_edge[k++][1] = l + x + 1;
			}	
		}

// create the faces list
	m_face = (WORD(*)[3])malloc(3*sizeof(WORD)*m_iFaces);
	for(j=k=l=0; j<y; j++, l++)
		for(i=0; i<x; i++, k+=2, l++)
		{
			m_face[k][0] = l;
			m_face[k][1] = l + 1;
			m_face[k][2] = l + x + 2;

			m_face[k+1][0] = l + x + 1;
			m_face[k+1][1] = l;
			m_face[k+1][2] = l + x + 2;

			m_sel[l] += 2;
			m_sel[l+1] += 4;
			m_sel[l+x+1] += 4;
			m_sel[l+x+2] += 2;
		}

// create the projections list
	m_prj = new CVertex [m_iVerts];

// create a sorted faces list
	m_sort = new int[m_iFaces];
	for(i=0; i<m_iFaces; i++) m_sort[i] = i;
}


//------------------------------------------------------------------------------------
// CMesh constructor
CMesh::CMesh(CMesh &m)
{
	m_iDimx = m.m_iDimx;
	m_iDimy = m.m_iDimy;
	m_iVerts = m.m_iVerts;
	m_iFaces = m.m_iFaces;
	m_iEdges = m.m_iEdges;
	m_sort = NULL;
	m_sel = NULL;
	m_edge = NULL;
	m_face = NULL;

	m_v = new CVertex[m_iVerts];
//	m_sel = new char[m_iVerts];
//	m_edge = (WORD(*)[2])malloc(2*sizeof(WORD)*m_iEdges);
//	m_face = (WORD(*)[3])malloc(3*sizeof(WORD)*m_iFaces);
	m_prj = new CVertex[m_iVerts];
	*this = m;
}


//------------------------------------------------------------------------------------
// CMesh destructor
CMesh::~CMesh()
{
	if(m_v) delete []m_v;
	if(m_edge) free(m_edge);
	if(m_sel) delete []m_sel;
	if(m_prj) delete []m_prj;
	if(m_face) free(m_face);
	if(m_sort) delete []m_sort;
}

//------------------------------------------------------------------------------------
// CMesh ZSort function
void CMesh::SortZ()
{
	s_me = this;
	qsort(m_sort, m_iFaces, sizeof(int), Compare);
}


//------------------------------------------------------------------------------------
// Compute the scene projection. All vertices are parallel projected on the XY plane, and values
// are stored in m_prj CVertex array
void CMesh::ComputePrj(RECT &r, CMatrix3 &m)
{
// compute the vertex screen projections
	for(int i=0; i<m_iVerts; i++)
	{	
		CVertex v = m_v[i] * m;
		m_prj[i].x = (v.x + 0.5f)*(r.right - r.left);
		m_prj[i].y = r.bottom - (v.y + 0.5f)*(r.bottom - r.top);
		m_prj[i].z = v.z;
	}
}


//------------------------------------------------------------------------------------
// CMesh compare function
int CMesh::Compare(const void *p1, const void *p2)
{ 
	WORD *f1 = s_me->m_face[*(int*)p1];
	WORD *f2 = s_me->m_face[*(int*)p2];
	CVertex *prj = s_me->m_prj;
	float a = max(max(prj[f1[0]].z, prj[f1[1]].z), prj[f1[2]].z);
	float b = max(max(prj[f2[0]].z, prj[f2[1]].z), prj[f2[2]].z);
	return  a < b ? -1 : 1;
}



void CMesh::operator = (CMesh &m)
{ 
	CopyMemory(m_v, m.m_v, m_iVerts*sizeof(CVertex)); 
	CopyMemory(m_prj, m.m_prj, m_iVerts*sizeof(CVertex));
//	CopyMemory(m_sel, m.m_sel, m_iVerts);
//	CopyMemory(m_edge, m.m_edge, 2*sizeof(WORD)*m_iEdges);
//	CopyMemory(m_face, m.m_face, 3*sizeof(WORD)*m_iFaces);
}
//*****************************************************************************************************
