// Everything that is a part of a LeoCAD project goes here.
//

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <float.h>
#include <math.h>
#include <locale.h>
#include "opengl.h"
#include "vector.h"
#include "matrix.h"
#include "pieceinf.h"
#include "texture.h"
#include "piece.h"
#include "camera.h"
#include "light.h"
#include "group.h"
#include "terrain.h"
#include "project.h"
#include "image.h"
#include "system.h"
#include "globals.h"
#include "minifig.h"
#include "config.h"
#include "message.h"
#include "curve.h"
#include "mainwnd.h"
#include "view.h"
#include "library.h"
#include "texfont.h"
#include "algebra.h"
#include "debug.h"
#include "lc_application.h"

// FIXME: temporary function, replace the code !!!
void SystemUpdateFocus (void* p)
{
  messenger->Dispatch (LC_MSG_FOCUS_CHANGED, p);
}

static void ProjectListener(int Message, void* Data, void* User)
{
	((Project*)User)->HandleMessage(Message, Data);
}

typedef struct
{
  unsigned char n;
  float dim [4][4];
} LC_VIEWPORT;

static LC_VIEWPORT viewports[14] = {
  { 1,  {{ 0.0f, 0.0f, 1.0f, 1.0f }, { 0.0f, 0.0f, 0.0f, 0.0f },
	 { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 1
  { 2,  {{ 0.0f, 0.0f, 0.5f, 1.0f }, { 0.5f, 0.0f, 0.5f, 1.0f },
	 { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 2V
  { 2,  {{ 0.0f, 0.0f, 1.0f, 0.5f }, { 0.0f, 0.5f, 1.0f, 0.5f },
	 { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 2H
  { 2,  {{ 0.0f, 0.0f, 1.0f, 0.7f }, { 0.0f, 0.7f, 1.0f, 0.3f },
	 { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 2HT
  { 2,  {{ 0.0f, 0.0f, 1.0f, 0.3f }, { 0.0f, 0.3f, 1.0f, 0.7f },
	 { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 2HB
  { 3,  {{ 0.0f, 0.0f, 0.5f, 0.5f }, { 0.0f, 0.5f, 0.5f, 0.5f },
	 { 0.5f, 0.0f, 0.5f, 1.0f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 3VL
  { 3,  {{ 0.0f, 0.0f, 0.5f, 1.0f }, { 0.5f, 0.0f, 0.5f, 0.5f },
	 { 0.5f, 0.5f, 0.5f, 0.5f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 3VR
  { 3,  {{ 0.0f, 0.0f, 1.0f, 0.5f }, { 0.0f, 0.5f, 0.5f, 0.5f },
	 { 0.5f, 0.5f, 0.5f, 0.5f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 3HB
  { 3,  {{ 0.0f, 0.0f, 0.5f, 0.5f }, { 0.5f, 0.0f, 0.5f, 0.5f },
	 { 0.0f, 0.5f, 1.0f, 0.5f }, { 0.0f, 0.0f, 0.0f, 0.0f } }}, // 3HT
  { 4,  {{ 0.0f, 0.0f, 0.3f, 0.3f }, { 0.0f, 0.3f, 0.3f, 0.4f },
	 { 0.0f, 0.7f, 0.3f, 0.3f }, { 0.3f, 0.0f, 0.7f, 1.0f } }}, // 4VL
  { 4,  {{ 0.0f, 0.0f, 0.7f, 1.0f }, { 0.7f, 0.0f, 0.3f, 0.3f },
	 { 0.7f, 0.3f, 0.3f, 0.4f }, { 0.7f, 0.7f, 0.3f, 0.3f } }}, // 4VR
  { 4,  {{ 0.0f, 0.0f, 1.0f, 0.7f }, { 0.0f, 0.7f, 0.3f, 0.3f },
	 { 0.3f, 0.7f, 0.4f, 0.3f }, { 0.7f, 0.7f, 0.3f, 0.3f } }}, // 4HT
  { 4,  {{ 0.0f, 0.0f, 0.3f, 0.3f }, { 0.3f, 0.0f, 0.4f, 0.3f },
	 { 0.7f, 0.0f, 0.3f, 0.3f }, { 0.0f, 0.3f, 1.0f, 0.7f } }}, // 4HB
  { 4,  {{ 0.0f, 0.0f, 0.5f, 0.5f }, { 0.5f, 0.0f, 0.5f, 0.5f },
	 { 0.0f, 0.5f, 0.5f, 0.5f }, { 0.5f, 0.5f, 0.5f, 0.5f } }}};// 4

/////////////////////////////////////////////////////////////////////////////
// Project construction/destruction

Project::Project()
{
	int i;

	m_bModified = false;
	m_bTrackCancel = false;
	m_nTracking = LC_TRACK_NONE;
	m_pPieces = NULL;
	m_pCameras = NULL;
	m_pLights = NULL;
	m_pGroups = NULL;
	m_pUndoList = NULL;
	m_pRedoList = NULL;
	m_nGridList = 0;
  m_pTrackFile = NULL;
	m_nCurClipboard = 0;
	m_nCurAction = 0;
	m_pTerrain = new Terrain();
	m_pBackground = new Texture();
	m_nAutosave = Sys_ProfileLoadInt ("Settings", "Autosave", 10);
	m_nMouse = Sys_ProfileLoadInt ("Default", "Mouse", 11);
	strcpy(m_strModelsPath, Sys_ProfileLoadString ("Default", "Projects", ""));

	if (messenger == NULL)
		messenger = new Messenger ();
	messenger->AddRef();
	messenger->Listen(&ProjectListener, this);

  for (i = 0; i < LC_CONNECTIONS; i++)
	{
		m_pConnections[i].entries = NULL;
		m_pConnections[i].numentries = 0;
	}

	for (i = 0; i < 10; i++)
		m_pClipboard[i] = NULL;

	m_pScreenFont = new TexFont();
}

Project::~Project()
{
	DeleteContents(false);
	SystemFinish();

  if (m_pTrackFile)
  {
    delete m_pTrackFile;
    m_pTrackFile = NULL;
  }

	for (int i = 0; i < 10; i++)
		if (m_pClipboard[i] != NULL)
			delete m_pClipboard[i];

  messenger->DecRef ();

	delete m_pTerrain;
	delete m_pBackground;
	delete m_pScreenFont;
}


/////////////////////////////////////////////////////////////////////////////
// Project attributes, general services

void Project::UpdateInterface()
{
	// Update all user interface elements.
	SystemUpdateUndoRedo(m_pUndoList->pNext ? m_pUndoList->strText : NULL, m_pRedoList ? m_pRedoList->strText : NULL);
	SystemUpdatePaste(m_pClipboard[m_nCurClipboard] != NULL);
	SystemUpdatePlay(true, false);
	SystemUpdateCategories(false);
	SetTitle(m_strTitle);

	SystemUpdateFocus(NULL);
	SetAction(m_nCurAction);
	SystemUpdateViewport(m_nViewportMode, 0);
	SystemUpdateColorList(m_nCurColor);
	SystemUpdateAnimation(m_bAnimation, m_bAddKeys);
	SystemUpdateRenderingMode((m_nDetail & LC_DET_BACKGROUND) != 0, (m_nDetail & LC_DET_FAST) != 0);
	SystemUpdateSnap(m_nSnap);
	SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
	SystemUpdateCameraMenu(m_pCameras);
	SystemUpdateCurrentCamera(NULL, m_pViewCameras[m_nActiveViewport], m_pCameras);
	UpdateSelection();
	if (m_bAnimation)
		SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
	else
		SystemUpdateTime(m_bAnimation, m_nCurStep, 255);

	for (int i = 0; i < m_ViewList.GetSize(); i++)
	{
		m_ViewList[i]->MakeCurrent();
		RenderInitialize();
	}

	UpdateSelection();
}

void Project::SetTitle(const char* lpszTitle)
{
	if (lpszTitle != m_strTitle)
		strcpy(m_strTitle, lpszTitle);

	char title[LC_MAXPATH], *ptr, ext[4];
	strcpy(title, m_strTitle);

	ptr = strrchr(title, '.');
	if (ptr != NULL)
	{
		strncpy(ext, ptr+1, 3);
		ext[3] = 0;
		strlwr(ext);

		if (strcmp(ext, "lcd") == 0)
			*ptr = 0;
		if (strcmp(ext, "dat") == 0)
			*ptr = 0;
		if (strcmp(ext, "ldr") == 0)
			*ptr = 0;
	}

	strcat(title, " - LeoCAD");

	SystemSetWindowCaption(title);
}

void Project::DeleteContents(bool bUndo)
{
	Piece* pPiece;
	Camera* pCamera;
	Light* pLight;
	Group* pGroup;

	memset(m_strAuthor, 0, sizeof(m_strAuthor));
	memset(m_strDescription, 0, sizeof(m_strDescription));
	memset(m_strComments, 0, sizeof(m_strComments));

	if (!bUndo)
	{
		LC_UNDOINFO* pUndo;

		while (m_pUndoList)
		{
			pUndo = m_pUndoList;
			m_pUndoList = m_pUndoList->pNext;
			delete pUndo;
		}

		while (m_pRedoList)
		{
			pUndo = m_pRedoList;
			m_pRedoList = m_pRedoList->pNext;
			delete pUndo;
		}

		m_pRedoList = NULL;
		m_pUndoList = NULL;

		m_pBackground->Unload();
		m_pTerrain->LoadDefaults((m_nDetail & LC_DET_LINEAR) != 0);
	}

	while (m_pPieces)
	{
		pPiece = m_pPieces;
		m_pPieces = m_pPieces->m_pNext;
		delete pPiece;
	}

	for (int i = 0; i < LC_CONNECTIONS; i++)
	{
		for (int j = 0; j < m_pConnections[i].numentries; j++)
			delete (m_pConnections[i].entries[j].cons);

		delete m_pConnections[i].entries;
		m_pConnections[i].entries = NULL;
		m_pConnections[i].numentries = 0;
	}

	while (m_pCameras)
	{
		pCamera = m_pCameras;
		m_pCameras = m_pCameras->m_pNext;
		delete pCamera;
	}

	while (m_pLights)
	{
		pLight = m_pLights;
		m_pLights = m_pLights->m_pNext;
		delete pLight;
	}

	while (m_pGroups)
	{
		pGroup = m_pGroups;
		m_pGroups = m_pGroups->m_pNext;
		delete pGroup;
	}


/*
	if (!m_strTempFile.IsEmpty())
	{
		DeleteFile (m_strTempFile);
		m_strTempFile.Empty();
	}
*/
}

// Only call after DeleteContents()
void Project::LoadDefaults(bool cameras)
{
	int i;
	unsigned long rgb;

	// Default values
	m_nActiveViewport = 0;
	SystemUpdateViewport(0, m_nViewportMode);
	m_nViewportMode = 0;
	SetAction(0);
	m_nCurColor = 0;
	SystemUpdateColorList(m_nCurColor);
	m_bAnimation = false;
	m_bAddKeys = false;
	SystemUpdateAnimation(m_bAnimation, m_bAddKeys);
	m_bUndoOriginal = true;
	SystemUpdateUndoRedo(NULL, NULL);
	m_nDetail = Sys_ProfileLoadInt ("Default", "Detail", LC_DET_BRICKEDGES);
	SystemUpdateRenderingMode((m_nDetail & LC_DET_BACKGROUND) != 0, (m_nDetail & LC_DET_FAST) != 0);
	m_nAngleSnap = (unsigned short)Sys_ProfileLoadInt ("Default", "Angle", 30);
	m_nSnap = Sys_ProfileLoadInt ("Default", "Snap", LC_DRAW_SNAP_A | LC_DRAW_SNAP_X | LC_DRAW_SNAP_Y | LC_DRAW_SNAP_Z | LC_DRAW_MOVE);
	SystemUpdateSnap(m_nSnap);
	m_nMoveSnap = 0x0304;
	SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
	m_fLineWidth = (float)Sys_ProfileLoadInt ("Default", "Line", 100)/100;
	m_fFogDensity = (float)Sys_ProfileLoadInt ("Default", "Density", 10)/100;
	rgb = Sys_ProfileLoadInt ("Default", "Fog", 0xFFFFFF);
	m_fFogColor[0] = (float)((unsigned char) (rgb))/255;
	m_fFogColor[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
	m_fFogColor[2] = (float)((unsigned char) ((rgb) >> 16))/255;
	m_fFogColor[3] = 1.0f;
	m_nGridSize = (unsigned short)Sys_ProfileLoadInt ("Default", "Grid", 20);
	rgb = Sys_ProfileLoadInt ("Default", "Ambient", 0x4B4B4B);
	m_fAmbient[0] = (float)((unsigned char) (rgb))/255;
	m_fAmbient[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
	m_fAmbient[2] = (float)((unsigned char) ((rgb) >> 16))/255;
	m_fAmbient[3] = 1.0f;
	rgb = Sys_ProfileLoadInt ("Default", "Background", 0xFFFFFF);
	m_fBackground[0] = (float)((unsigned char) (rgb))/255;
	m_fBackground[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
	m_fBackground[2] = (float)((unsigned char) ((rgb) >> 16))/255;
	m_fBackground[3] = 1.0f;
	rgb = Sys_ProfileLoadInt ("Default", "Gradient1", 0xBF0000);
	m_fGradient1[0] = (float)((unsigned char) (rgb))/255;
	m_fGradient1[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
	m_fGradient1[2] = (float)((unsigned char) ((rgb) >> 16))/255;
	rgb = Sys_ProfileLoadInt ("Default", "Gradient2", 0xFFFFFF);
	m_fGradient2[0] = (float)((unsigned char) (rgb))/255;
	m_fGradient2[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
	m_fGradient2[2] = (float)((unsigned char) ((rgb) >> 16))/255;
	m_nFPS = Sys_ProfileLoadInt ("Default", "FPS", 24);
	m_nCurStep = 1;
	m_nCurFrame = 1;
	m_nTotalFrames = 100;
	SystemUpdateTime(false, 1, 255);
	m_nScene = Sys_ProfileLoadInt ("Default", "Scene", 0);
	m_nSaveTimer = 0;
	strcpy(m_strHeader, Sys_ProfileLoadString ("Default", "Header", ""));
	strcpy(m_strFooter, Sys_ProfileLoadString ("Default", "Footer", "Page &P"));
	strcpy(m_strBackground, Sys_ProfileLoadString ("Default", "BMP", ""));
	m_pTerrain->LoadDefaults((m_nDetail & LC_DET_LINEAR) != 0);
	m_OverlayActive = false;

	for (i = 0; i < m_ViewList.GetSize (); i++)
	{
		m_ViewList[i]->MakeCurrent ();
		RenderInitialize();
	}

	if (cameras)
	{
		Camera* pCam;
		for (pCam = NULL, i = 0; i < 7; i++)
		{
			pCam = new Camera(i, pCam);
			if (m_pCameras == NULL)
				m_pCameras = pCam;

			switch (i) 
			{
			case LC_CAMERA_MAIN:  m_pViewCameras[0] = pCam; break;
			case LC_CAMERA_FRONT: m_pViewCameras[1] = pCam; break;
			case LC_CAMERA_TOP:   m_pViewCameras[2] = pCam; break;
			case LC_CAMERA_RIGHT: m_pViewCameras[3] = pCam; break;
			}
		}
		SystemUpdateCameraMenu(m_pCameras);
		SystemUpdateCurrentCamera(NULL, m_pViewCameras[0], m_pCameras);
	}
	SystemPieceComboAdd(NULL);
	UpdateSelection();
}

/////////////////////////////////////////////////////////////////////////////
// Standard file menu commands

// Read a .lcd file
bool Project::FileLoad(File* file, bool bUndo, bool bMerge)
{
	int i, count;
	char id[32];
	unsigned long rgb;
	float fv = 0.4f;
	unsigned char ch, action = m_nCurAction;
	unsigned short sh;

	file->Seek(0, SEEK_SET);
	file->Read(id, 32);
	sscanf(&id[7], "%f", &fv);

	// Fix the ugly floating point reading on computers with different decimal points.
	if (fv == 0.0f)
	{
		lconv *loc = localeconv();
		id[8] = loc->decimal_point[0];
		sscanf(&id[7], "%f", &fv);

		if (fv == 0.0f)
			return false;
	}

	if (fv > 0.4f)
		file->ReadFloat (&fv, 1);

	file->ReadLong (&rgb, 1);
	if (!bMerge)
	{
		m_fBackground[0] = (float)((unsigned char) (rgb))/255;
		m_fBackground[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
		m_fBackground[2] = (float)((unsigned char) ((rgb) >> 16))/255;
	}

	if (fv < 0.6f) // old view
	{
		Camera* pCam;
		for (pCam = NULL, i = 0; i < 7; i++)
		{
			pCam = new Camera(i, pCam);
			if (m_pCameras == NULL)
				m_pCameras = pCam;

			switch (i) 
			{
			case LC_CAMERA_MAIN:  m_pViewCameras[0] = pCam; break;
			case LC_CAMERA_FRONT: m_pViewCameras[1] = pCam; break;
			case LC_CAMERA_TOP:   m_pViewCameras[2] = pCam; break;
			case LC_CAMERA_RIGHT: m_pViewCameras[3] = pCam; break;
			}
		}

		double eye[3], target[3];
		file->ReadDouble (&eye, 3);
		file->ReadDouble (&target, 3);
		float tmp[3] = { (float)eye[0], (float)eye[1], (float)eye[2] };
		pCam->ChangeKey(1, false, false, tmp, LC_CK_EYE);
		pCam->ChangeKey(1, true, false, tmp, LC_CK_EYE);
		tmp[0] = (float)target[0]; tmp[1] = (float)target[1]; tmp[2] = (float)target[2];
		pCam->ChangeKey(1, false, false, tmp, LC_CK_TARGET);
		pCam->ChangeKey(1, true, false, tmp, LC_CK_TARGET);

		// Create up vector
		Vector upvec(0,0,1), frontvec((float)(eye[0]-target[0]), (float)(eye[1]-target[1]), (float)(eye[2]-target[2])), sidevec;
		frontvec.Normalize();
		if (frontvec == upvec)
			sidevec = Vector(1,0,0);
		else
			sidevec.Cross(frontvec, upvec);
		upvec.Cross(sidevec, frontvec);
		upvec.Normalize();
		upvec.ToFloat(tmp);
		pCam->ChangeKey(1, false, false, tmp, LC_CK_UP);
		pCam->ChangeKey(1, true, false, tmp, LC_CK_UP);
	}

	if (bMerge)
		file->Seek(32, SEEK_CUR);
	else
	{
		file->ReadLong (&i, 1); m_nAngleSnap = i;
		file->ReadLong (&m_nSnap, 1);
		file->ReadFloat (&m_fLineWidth, 1);
		file->ReadLong (&m_nDetail, 1);
		file->ReadLong (&i, 1); //m_nCurGroup = i;
		file->ReadLong (&i, 1); m_nCurColor = i;
		file->ReadLong (&i, 1); action = i;
		file->ReadLong (&i, 1); m_nCurStep = i;
	}

	if (fv > 0.8f)
		file->ReadLong (&m_nScene, 1);

	file->ReadLong (&count, 1);
	SystemStartProgressBar(0, count, 1, "Loading project...");

	while (count--)
	{	
		if (fv > 0.4f)
		{
			char name[9];
			Piece* pPiece = new Piece(NULL);
			pPiece->FileLoad(*file, name);
			PieceInfo* pInfo = lcGetPiecesLibrary()->FindPieceInfo(name);
			if (pInfo)
			{
				pPiece->SetPieceInfo(pInfo);

				if (bMerge)
					for (Piece* p = m_pPieces; p; p = p->m_pNext)
						if (strcmp(p->GetName(), pPiece->GetName()) == 0)
						{
							pPiece->CreateName(m_pPieces);
							break;
						}

				if (strlen(pPiece->GetName()) == 0)
					pPiece->CreateName(m_pPieces);

				AddPiece(pPiece);
				if (!bUndo)
					SystemPieceComboAdd(pInfo->m_strDescription);
			}
			else 
				delete pPiece;
		}
		else
		{
			char name[9];
			float pos[3], rot[3], param[4];
			unsigned char color, step, group;
		
			file->ReadFloat (pos, 3);
			file->ReadFloat (rot, 3);
			file->ReadByte (&color, 1);
			file->Read(name, sizeof(name));
			file->ReadByte (&step, 1);
			file->ReadByte (&group, 1);

			const unsigned char conv[20] = { 0,2,4,9,7,6,22,8,10,11,14,16,18,9,21,20,22,8,10,11 };
			color = conv[color];

			PieceInfo* pInfo = lcGetPiecesLibrary()->FindPieceInfo(name);
			if (pInfo != NULL)
			{
				Piece* pPiece = new Piece(pInfo);
				Matrix mat;

				pPiece->Initialize(pos[0], pos[1], pos[2], step, 1, color);
				pPiece->CreateName(m_pPieces);
				AddPiece(pPiece);
				mat.CreateOld(0,0,0, rot[0],rot[1],rot[2]);
				mat.ToAxisAngle(param);
				pPiece->ChangeKey(1, false, false, param, LC_PK_ROTATION);
				pPiece->ChangeKey(1, true, false, param, LC_PK_ROTATION);
//				pPiece->SetGroup((Group*)group);
				SystemPieceComboAdd(pInfo->m_strDescription);
			}
		}
		SytemStepProgressBar();
	}
	SytemEndProgressBar();

	if (!bMerge)
	{
		if (fv >= 0.4f)
		{
			file->Read(&ch, 1);
			if (ch == 0xFF) file->ReadShort (&sh, 1); else sh = ch;
			if (sh > 100)
				file->Seek(sh, SEEK_CUR);
			else
				file->Read(m_strAuthor, sh);

			file->Read(&ch, 1);
			if (ch == 0xFF) file->ReadShort (&sh, 1); else sh = ch;
			if (sh > 100)
				file->Seek(sh, SEEK_CUR);
			else
				file->Read(m_strDescription, sh);

			file->Read(&ch, 1);
			if (ch == 0xFF && fv < 1.3f) file->ReadShort (&sh, 1); else sh = ch;
			if (sh > 255)
				file->Seek(sh, SEEK_CUR);
			else
				file->Read(m_strComments, sh);
		}
	}
	else
	{
		if (fv >= 0.4f)
		{
			file->Read (&ch, 1);
			if (ch == 0xFF) file->ReadShort (&sh, 1); else sh = ch;
			file->Seek (sh, SEEK_CUR);

			file->Read (&ch, 1);
			if (ch == 0xFF) file->ReadShort (&sh, 1); else sh = ch;
			file->Seek (sh, SEEK_CUR);

			file->Read (&ch, 1);
			if (ch == 0xFF && fv < 1.3f) file->ReadShort (&sh, 1); else sh = ch;
			file->Seek (sh, SEEK_CUR);
		}
	}

	if (fv >= 0.5f)
	{
		file->ReadLong (&count, 1);

		Group* pGroup;
		Group* pLastGroup = NULL;
		for (pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
			pLastGroup = pGroup;

		pGroup = pLastGroup;
		for (i = 0; i < count; i++)
		{
			if (pGroup)
			{
				pGroup->m_pNext = new Group();
				pGroup = pGroup->m_pNext;
			}
			else
				m_pGroups = pGroup = new Group();
		}
		pLastGroup = pLastGroup ? pLastGroup->m_pNext : m_pGroups;

		for (pGroup = pLastGroup; pGroup; pGroup = pGroup->m_pNext)
		{
			if (fv < 1.0f)
			{
				file->Read(pGroup->m_strName, 65);
				file->Read(&ch, 1);
				pGroup->m_fCenter[0] = 0;
				pGroup->m_fCenter[1] = 0;
				pGroup->m_fCenter[2] = 0;
				pGroup->m_pGroup = (Group*)-1;
			}
			else
				pGroup->FileLoad(file);
		}

		for (pGroup = pLastGroup; pGroup; pGroup = pGroup->m_pNext)
		{
			i = LC_POINTER_TO_INT(pGroup->m_pGroup);
			pGroup->m_pGroup = NULL;

			if (i > 0xFFFF || i == -1)
				continue;

			for (Group* g2 = pLastGroup; g2; g2 = g2->m_pNext)
			{
				if (i == 0)
				{
					pGroup->m_pGroup = g2;
					break;
				}

				i--;
			}
		}


		Piece* pPiece;
		for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		{
			i = LC_POINTER_TO_INT(pPiece->GetGroup());
			pPiece->SetGroup(NULL);

			if (i > 0xFFFF || i == -1)
				continue;

			for (pGroup = pLastGroup; pGroup; pGroup = pGroup->m_pNext)
			{
				if (i == 0)
				{
					pPiece->SetGroup(pGroup);
					break;
				}

				i--;
			}
		}

		RemoveEmptyGroups();
	}

	if (!bMerge)
	{
		if (fv >= 0.6f)
		{
			if (fv < 1.0f)
			{
				file->ReadLong (&i, 1);
				m_nViewportMode = i;
			}
			else
			{
				file->ReadByte (&m_nViewportMode, 1);
				file->ReadByte (&m_nActiveViewport, 1);
			}

			file->ReadLong (&count, 1);
			Camera* pCam = NULL;
			for (i = 0; i < count; i++)
			{
				pCam = new Camera(i, pCam);
				if (m_pCameras == NULL)
					m_pCameras = pCam;

				if (i < 4 && fv == 0.6f)
					m_pViewCameras[i] = pCam;
			}

			if (count < 7)
			{
				pCam = new Camera(0, NULL);
				for (i = 0; i < count; i++)
					pCam->FileLoad(*file);
				delete pCam;
			}
			else
				for (pCam = m_pCameras; pCam; pCam = pCam->m_pNext)
					pCam->FileLoad(*file);
		}

		if (fv >= 0.7f)
		{
			for (count = 0; count < 4; count++)
			{
				file->ReadLong (&i, 1);

				Camera* pCam = m_pCameras;
				while (i--)
					pCam = pCam->m_pNext;
				m_pViewCameras[count] = pCam;
			}

			file->ReadLong (&rgb, 1);
			m_fFogColor[0] = (float)((unsigned char) (rgb))/255;
			m_fFogColor[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
			m_fFogColor[2] = (float)((unsigned char) ((rgb) >> 16))/255;

			if (fv < 1.0f)
			{
				file->ReadLong (&rgb, 1);
				m_fFogDensity = (float)rgb/100;
			}
			else
				file->ReadFloat (&m_fFogDensity, 1);

			if (fv < 1.3f)
			{
				file->ReadByte (&ch, 1);
				if (ch == 0xFF)
					file->ReadShort (&sh, 1);
				sh = ch;
			}
			else
				file->ReadShort (&sh, 1);

			if (sh < LC_MAXPATH)
				file->Read (m_strBackground, sh);
			else
				file->Seek (sh, SEEK_CUR);
		}

		if (fv >= 0.8f)
		{
			file->Read(&ch, 1);
			file->Read(m_strHeader, ch);
			file->Read(&ch, 1);
			file->Read(m_strFooter, ch);
		}

		if (fv > 0.9f)
		{
			file->ReadLong (&rgb, 1);
			m_fAmbient[0] = (float)((unsigned char) (rgb))/255;
			m_fAmbient[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
			m_fAmbient[2] = (float)((unsigned char) ((rgb) >> 16))/255;

			if (fv < 1.3f)
			{
				file->ReadLong (&i, 1); m_bAnimation = (i != 0);
				file->ReadLong (&i, 1); m_bAddKeys = (i != 0);
				file->ReadByte (&m_nFPS, 1);
				file->ReadLong (&i, 1); m_nCurFrame = i;
				file->ReadShort (&m_nTotalFrames, 1);
				file->ReadLong (&i, 1); m_nGridSize = i;
				file->ReadLong (&i, 1); //m_nMoveSnap = i;
			}
			else
			{
				file->ReadByte (&ch, 1); m_bAnimation = (ch != 0);
				file->ReadByte (&ch, 1); m_bAddKeys = (ch != 0);
				file->ReadByte (&m_nFPS, 1);
				file->ReadShort (&m_nCurFrame, 1);
				file->ReadShort (&m_nTotalFrames, 1);
				file->ReadShort (&m_nGridSize, 1);
				file->ReadShort (&sh, 1);
				if (fv >= 1.4f)
					m_nMoveSnap = sh;
			}
		}
			
		if (fv > 1.0f)
		{
			file->ReadLong (&rgb, 1);
			m_fGradient1[0] = (float)((unsigned char) (rgb))/255;
			m_fGradient1[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
			m_fGradient1[2] = (float)((unsigned char) ((rgb) >> 16))/255;
			file->ReadLong (&rgb, 1);
			m_fGradient2[0] = (float)((unsigned char) (rgb))/255;
			m_fGradient2[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
			m_fGradient2[2] = (float)((unsigned char) ((rgb) >> 16))/255;
			
			if (fv > 1.1f)
				m_pTerrain->FileLoad (file);
			else
			{
				file->Seek (4, SEEK_CUR);
				file->Read (&ch, 1);
				file->Seek (ch, SEEK_CUR);
			}
		}
	}

	for (i = 0; i < m_ViewList.GetSize (); i++)
	{
		m_ViewList[i]->MakeCurrent ();
		RenderInitialize();
	}
	CalculateStep();
	if (!bUndo)
		SelectAndFocusNone(false);
	if (!bMerge)
		SystemUpdateFocus(NULL);
	SetAction(action);
	SystemUpdateViewport(m_nViewportMode, 0);
	SystemUpdateColorList(m_nCurColor);
	SystemUpdateAnimation(m_bAnimation, m_bAddKeys);
	SystemUpdateRenderingMode((m_nDetail & LC_DET_BACKGROUND) != 0, (m_nDetail & LC_DET_FAST) != 0);
	SystemUpdateSnap(m_nSnap);
	SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
	SystemUpdateCameraMenu(m_pCameras);
	SystemUpdateCurrentCamera(NULL, m_pViewCameras[m_nActiveViewport], m_pCameras);
	UpdateSelection();
	if (m_bAnimation)
		SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
	else
		SystemUpdateTime(m_bAnimation, m_nCurStep, 255);
	UpdateAllViews ();

	return true;
}

void Project::FileSave(File* file, bool bUndo)
{
	float ver_flt = 1.4f; // LeoCAD 0.75 - (and this should have been an integer).
	unsigned long rgb;
	unsigned char ch;
	unsigned short sh;
	int i, j;

	file->Seek (0, SEEK_SET);
	file->Write (LC_STR_VERSION, 32);
	file->WriteFloat (&ver_flt, 1);

	rgb = FLOATRGB(m_fBackground);
	file->WriteLong (&rgb, 1);

	i = m_nAngleSnap; file->WriteLong (&i, 1);
	file->WriteLong (&m_nSnap, 1);
	file->WriteFloat (&m_fLineWidth, 1);
	file->WriteLong (&m_nDetail, 1);
	//i = m_nCurGroup;
	file->WriteLong (&i, 1);
	i = m_nCurColor; file->WriteLong (&i, 1);
	i = m_nCurAction; file->WriteLong (&i, 1);
	i = m_nCurStep; file->WriteLong (&i, 1);
	file->WriteLong (&m_nScene, 1);

	Piece* pPiece;
	for (i = 0, pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		i++;
	file->WriteLong (&i, 1);

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		pPiece->FileSave (*file, m_pGroups);

	ch = strlen(m_strAuthor);
	file->Write(&ch, 1);
	file->Write(m_strAuthor, ch);
	ch = strlen(m_strDescription);
	file->Write(&ch, 1);
	file->Write(m_strDescription, ch);
	ch = strlen(m_strComments);
	file->Write(&ch, 1);
	file->Write(m_strComments, ch);

	Group* pGroup;
	for (i = 0, pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
		i++;
	file->WriteLong (&i, 1);

	for (pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
		pGroup->FileSave(file, m_pGroups);

	file->WriteByte (&m_nViewportMode, 1);
	file->WriteByte (&m_nActiveViewport, 1);

	Camera* pCamera;
	for (i = 0, pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
		i++;
	file->WriteLong (&i, 1);

	for (i = 0, pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
		pCamera->FileSave(*file);

	for (j = 0; j < 4; j++)
	{
		for (i = 0, pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
			if (pCamera == m_pViewCameras[j])
				break;
			else
				i++;

		file->WriteLong (&i, 1);
	}

	rgb = FLOATRGB(m_fFogColor);
	file->WriteLong (&rgb, 1);
	file->WriteFloat (&m_fFogDensity, 1);
	sh = strlen(m_strBackground);
	file->WriteShort (&sh, 1);
	file->Write(m_strBackground, sh);
	ch = strlen(m_strHeader);
	file->Write(&ch, 1);
	file->Write(m_strHeader, ch);
	ch = strlen(m_strFooter);
	file->Write(&ch, 1);
	file->Write(m_strFooter, ch);
	// 0.60 (1.0)
	rgb = FLOATRGB(m_fAmbient);
	file->WriteLong (&rgb, 1);
	ch = m_bAnimation;
	file->Write(&ch, 1);
	ch = m_bAddKeys;
	file->WriteByte (&ch, 1);
	file->WriteByte (&m_nFPS, 1);
	file->WriteShort (&m_nCurFrame, 1);
	file->WriteShort (&m_nTotalFrames, 1);
	file->WriteShort (&m_nGridSize, 1);
	file->WriteShort (&m_nMoveSnap, 1);
	// 0.62 (1.1)
	rgb = FLOATRGB(m_fGradient1);
	file->WriteLong (&rgb, 1);
	rgb = FLOATRGB(m_fGradient2);
	file->WriteLong (&rgb, 1);
	// 0.64 (1.2)
	m_pTerrain->FileSave(file);

	if (!bUndo)
	{
		unsigned long pos = 0;

		i = Sys_ProfileLoadInt ("Default", "Save Preview", 0);
		if (i != 0) 
		{
			pos = file->GetPosition();

      Image* image = new Image[1];
			LC_IMAGE_OPTS opts;
			opts.interlaced = false;
			opts.transparent = false;
			opts.format = LC_IMAGE_GIF;

			i = m_bAnimation ? m_nCurFrame : m_nCurStep;
			CreateImages(image, 120, 100, i, i, false);
			image[0].FileSave (*file, &opts);
			delete []image;
		}

		file->WriteLong (&pos, 1);
		m_nSaveTimer = 0;
	}
}

void Project::FileReadMPD(File& MPD, PtrArray<File>& FileArray) const
{
  FileMem* CurFile = NULL;
	char Buf[1024];

	while (MPD.ReadLine(Buf, 1024))
  {
    String Line(Buf);

    Line.TrimLeft();

    if (Line[0] != '0')
    {
      // Copy current line.
      if (CurFile != NULL)
        CurFile->Write(Buf, strlen(Buf));

      continue;
    }

    Line.TrimRight();
    Line = Line.Right(Line.GetLength() - 1);
    Line.TrimLeft();

    // Find where a subfile starts.
    if (Line.CompareNoCase("FILE", 4) == 0)
    {
      Line = Line.Right(Line.GetLength() - 4);
      Line.TrimLeft();

      // Create a new file.
      CurFile = new FileMem();
      CurFile->SetFileName(Line);
      FileArray.Add(CurFile);
    }
    else if (Line.CompareNoCase("ENDFILE", 7) == 0)
    {
      // File ends here.
      CurFile = NULL;
    }
    else if (CurFile != NULL)
    {
      // Copy current line.
      CurFile->Write(Buf, strlen(Buf));
    }
  }
}

void Project::FileReadLDraw(File* file, Matrix* prevmat, int* nOk, int DefColor, int* nStep, PtrArray<File>& FileArray)
{
	char buf[1024];

  // Save file offset.
  lcuint32 Offset = file->GetPosition();
  file->Seek(0, SEEK_SET);

  while (file->ReadLine(buf, 1024))
	{
		strupr(buf);
		if (strstr(buf, "STEP"))
		{
			(*nStep)++;
			continue;
		}

		bool read = true;
		char *ptr, tmp[LC_MAXPATH], pn[LC_MAXPATH];
		int color, cmd;
		float fmat[12];

		if (sscanf(buf, "%d %d %g %g %g %g %g %g %g %g %g %g %g %g %s[12]",
			&cmd, &color, &fmat[0], &fmat[1], &fmat[2], &fmat[3], &fmat[4], &fmat[5], &fmat[6], 
			&fmat[7], &fmat[8], &fmat[9], &fmat[10], &fmat[11], &tmp[0]) != 15)
			continue;

		Matrix incmat, tmpmat;
		incmat.FromLDraw(fmat);
		tmpmat.Multiply(*prevmat, incmat);

		if (cmd == 1)
		{
			int cl = 0;
			if (color == 16) 
				cl = DefColor;
			else
				cl = ConvertColor(color);

			strcpy(pn, tmp);
			ptr = strrchr(tmp, '.');

			if (ptr != NULL)
				*ptr = 0;

      // See if it's a piece in the library
			if (strlen(tmp) < 9)
			{
				char name[9];
				strcpy(name, tmp);

				PieceInfo* pInfo = lcGetPiecesLibrary()->FindPieceInfo(name);
				if (pInfo != NULL)
				{
					float x, y, z, rot[4];
					Piece* pPiece = new Piece(pInfo);
					read = false;

					tmpmat.GetTranslation(&x, &y, &z);
					pPiece->Initialize(x, y, z, *nStep, 1, cl);
					pPiece->CreateName(m_pPieces);
					AddPiece(pPiece);
					tmpmat.ToAxisAngle(rot);
					pPiece->ChangeKey(1, false, false, rot, LC_PK_ROTATION);
					pPiece->ChangeKey(1, true, false, rot, LC_PK_ROTATION);
					SystemPieceComboAdd(pInfo->m_strDescription);
					(*nOk)++;
				}
			}

      // Check for MPD files first.
      if (read)
      {
        for (int i = 0; i < FileArray.GetSize(); i++)
        {
          if (stricmp(FileArray[i]->GetFileName(), pn) == 0)
          {
  					FileReadLDraw(FileArray[i], &tmpmat, nOk, cl, nStep, FileArray);
  					read = false;
            break;
          }
        }
      }

      // Try to read the file from disk.
			if (read)
			{
				FileDisk tf;

        if (tf.Open(pn, "rt"))
        {
					FileReadLDraw(&tf, &tmpmat, nOk, cl, nStep, FileArray);
					read = false;
        }
			}
		}
	}

  // Restore file offset.
  file->Seek(Offset, SEEK_SET);
}

bool Project::DoFileSave()
{
/*
	DWORD dwAttrib = GetFileAttributes(m_strPathName);
	if (dwAttrib & FILE_ATTRIBUTE_READONLY)
	{
		// we do not have read-write access or the file does not (now) exist
		if (!DoSave(NULL, true))
			return false;
	}
	else
*/	{
		if (!DoSave(m_strPathName, true))
			return false;
	}
	return true;
}

// Save the document data to a file
// lpszPathName = path name where to save document file
// if lpszPathName is NULL then the user will be prompted (SaveAs)
// note: lpszPathName can be different than 'm_strPathName'
// if 'bReplace' is TRUE will change file name if successful (SaveAs)
// if 'bReplace' is FALSE will not change path name (SaveCopyAs)
bool Project::DoSave(char* lpszPathName, bool bReplace)
{
	FileDisk file;
	char newName[LC_MAXPATH];
	memset(newName, 0, sizeof(newName));
	if (lpszPathName)
		strcpy(newName, lpszPathName);

	char ext[4], *ptr;
	memset(ext, 0, 4);
	ptr = strrchr(newName, '.');
	if (ptr != NULL)
	{
		ptr++;
		strncpy(ext, ptr, 3);
		strlwr(ext);

		if ((strcmp(ext, "dat") == 0) || (strcmp(ext, "ldr") == 0))
		{
			*ptr = 0;
			strcat(newName, "lcd");
		}
	}

	if (strlen(newName) == 0)
	{
		strcpy(newName, m_strPathName);
		if (bReplace && strlen(newName) == 0)
		{
			strcpy(newName, m_strTitle);

			// check for dubious filename
			int iBad = strcspn(newName, " #%;/\\");
			if (iBad != -1)
				newName[iBad] = 0;

			strcat(newName, ".lcd");
		}

		if (!SystemDoDialog(LC_DLG_FILE_SAVE_PROJECT, &newName))
			return false; // don't even attempt to save
	}

//	CWaitCursor wait;

	if (!file.Open(newName, "wb"))
	{
//		MessageBox("Failed to save.");

		// be sure to delete the file
		if (lpszPathName == NULL)
			remove(newName);

		return false;
	}

	memset(ext, 0, 4);
	ptr = strrchr(newName, '.');
	if (ptr != NULL)
	{
		strncpy(ext, ptr+1, 3);
		strlwr(ext);
	}

	if ((strcmp(ext, "dat") == 0) || (strcmp(ext, "ldr") == 0))
	{
		const int col[28] = { 4,12,2,10,1,9,14,15,8,0,6,13,13,334,36,44,34,42,33,41,46,47,7,382,6,13,11,383 };
		Piece* pPiece;
		int i, steps = GetLastStep();
		char buf[256], *ptr;

		ptr = strrchr(m_strPathName, '\\');
		if (ptr == NULL)
			ptr = strrchr(m_strPathName, '/');
		if (ptr == NULL)
			ptr = m_strPathName;
		else
			ptr++;

		sprintf(buf, "0 Model exported from LeoCAD\r\n"
					"0 Original name: %s\r\n", ptr);
		if (strlen(m_strAuthor) != 0)
		{
			strcat(buf, "0 Author: ");
			strcat(buf, m_strAuthor);
			strcat(buf, "\r\n");
		}
		strcat(buf, "\r\n");
		file.Write(buf, strlen(buf));

		for (i = 1; i <= steps; i++)
		{
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				if ((pPiece->IsVisible(i, false)) && (pPiece->GetStepShow() == i))
				{
					float f[12], position[3], rotation[4];
					pPiece->GetPosition(position);
					pPiece->GetRotation(rotation);
					Matrix mat(rotation, position);
					mat.ToLDraw(f);
					sprintf (buf, " 1 %d %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %s.DAT\r\n",
						col[pPiece->GetColor()], f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9], f[10], f[11], pPiece->GetPieceInfo()->m_strName);
					file.Write(buf, strlen(buf));
				}
			}

			if (i != steps)
				file.Write("0 STEP\r\n", 8);
		}
		file.Write("0\r\n", 3);
	}
	else
		FileSave(&file, false);     // save me
	file.Close();

	SetModifiedFlag(false);     // back to unmodified

	// reset the title and change the document name
	if (bReplace)
		SetPathName(newName, true);

	return true; // success
}

// return true if ok to continue
bool Project::SaveModified()
{
	if (!IsModified())
		return true;        // ok to continue

	// get name/title of document
	char name[LC_MAXPATH];
	if (strlen(m_strPathName) == 0)
	{
		// get name based on caption
		strcpy(name, m_strTitle);
		if (strlen(name) == 0)
			strcpy(name, "Untitled");
	}
	else
	{
		// get name based on file title of path name
		char* p;

		p = strrchr(m_strPathName, '\\');
		if (!p)
			p = strrchr(m_strPathName, '/');

		if (p)
			strcpy(name, ++p);
		else
			strcpy(name, m_strPathName);
	}

	char prompt[512];
	sprintf(prompt, "Save changes to %s ?", name);

	switch (SystemDoMessageBox(prompt, LC_MB_YESNOCANCEL))
	{
	case LC_CANCEL:
		return false;       // don't continue

	case LC_YES:
		// If so, either Save or Update, as appropriate
		if (!DoFileSave())
			return false;       // don't continue
		break;

	case LC_NO:
		// If not saving changes, revert the document
		break;
	}

	return true;    // keep going
}


/////////////////////////////////////////////////////////////////////////////
// File operations

bool Project::OnNewDocument()
{
	SetTitle("Untitled");
	DeleteContents(false);
	memset(m_strPathName, 0, sizeof(m_strPathName)); // no path name yet
	strcpy(m_strAuthor, Sys_ProfileLoadString ("Default", "User", ""));
	SetModifiedFlag(false); // make clean
	LoadDefaults(true);
	CheckPoint("");

        //	SystemUpdateRecentMenu(m_strRecentFiles);
        messenger->Dispatch (LC_MSG_FOCUS_CHANGED, NULL);

//	CWnd* pFrame = AfxGetMainWnd();
//	if (pFrame != NULL)
//		pFrame->PostMessage (WM_LC_UPDATE_LIST, 1, m_nCurColor+1);
// set cur group to 0
 
	return true;
}

bool Project::OpenProject(const char* FileName)
{
	if (!SaveModified())
		return false;  // Leave the original one

//	CWaitCursor wait;
	bool WasModified = IsModified();
	SetModifiedFlag(false);  // Not dirty for open

	if (!OnOpenDocument(FileName))
	{
		// Check if we corrupted the original document
		if (!IsModified())
			SetModifiedFlag(WasModified);
		else
			OnNewDocument();

		return false;  // Open failed
	}

	SetPathName(FileName, true);

	return true;
}

bool Project::OnOpenDocument (const char* lpszPathName)
{
  FileDisk file;
  bool bSuccess = false;

  if (!file.Open(lpszPathName, "rb"))
  {
//    MessageBox("Failed to open file.");
    return false;
  }

  char ext[4], *ptr;
  memset(ext, 0, 4);
  ptr = strrchr(lpszPathName, '.');
  if (ptr != NULL)
  {
    strncpy(ext, ptr+1, 3);
    strlwr(ext);
  }

  bool datfile = false;
  bool mpdfile = false;

  // Find out what file type we're loading.
  if ((strcmp(ext, "dat") == 0) || (strcmp(ext, "ldr") == 0))
    datfile = true;
  else if (strcmp(ext, "mpd") == 0)
    mpdfile = true;

  // Delete the current project.
  DeleteContents(false);
  LoadDefaults(datfile || mpdfile);
  SetModifiedFlag(true);  // dirty during loading

  SystemDoWaitCursor(1);

  if (file.GetLength() != 0)
  {
    PtrArray<File> FileArray;

    // Unpack the MPD file.
    if (mpdfile)
    {
      FileReadMPD(file, FileArray);

      if (FileArray.GetSize() == 0)
      {
        file.Seek(0, SEEK_SET);
        mpdfile = false;
        datfile = true;
        console.PrintWarning("No files found inside the MPD, trying to load it as a .DAT file.\n");
      }
    }

    if (datfile || mpdfile)
    {
      int ok = 0, step = 1;
      Matrix mat;

      if (mpdfile)
        FileReadLDraw(FileArray[0], &mat, &ok, m_nCurColor, &step, FileArray);
      else
        FileReadLDraw(&file, &mat, &ok, m_nCurColor, &step, FileArray);

      m_nCurStep = step;
      SystemUpdateTime(false, m_nCurStep, 255);
      SystemUpdateFocus(NULL);
      UpdateSelection();
      CalculateStep();
      UpdateAllViews ();

      console.PrintMisc("%d objects imported.\n", ok);
      bSuccess = true;
    }
    else
    {
      // Load a LeoCAD file.
      bSuccess = FileLoad(&file, false, false);
    }

    // Clean up.
    if (mpdfile)
    {
      for (int i = 0; i < FileArray.GetSize(); i++)
        delete FileArray[i];
    }
  }

  file.Close();
  SystemDoWaitCursor(-1);

  if (bSuccess == false)
  {
//    MessageBox("Failed to load.");
    DeleteContents(false);   // remove failed contents
    return false;
  }

  CheckPoint("");
  m_nSaveTimer = 0;

  SetModifiedFlag(false);     // start off with unmodified

  return true;
}

void Project::SetPathName(const char* lpszPathName, bool bAddToMRU)
{
	strcpy(m_strPathName, lpszPathName);

	// always capture the complete file name including extension (if present)
	const char* lpszTemp = lpszPathName;
	for (const char* lpsz = lpszPathName; *lpsz != '\0'; lpsz++)
	{
		// remember last directory/drive separator
		if (*lpsz == '\\' || *lpsz == '/' || *lpsz == ':')
			lpszTemp = lpsz + 1;
	}

	// set the document title based on path name
	SetTitle(lpszTemp);

	// add it to the file MRU list
	if (bAddToMRU)
		main_window->AddToMRU (lpszPathName);
}

/////////////////////////////////////////////////////////////////////////////
// Undo/Redo support

// Save current state.
void Project::CheckPoint (const char* text)
{
	LC_UNDOINFO* pTmp;
	LC_UNDOINFO* pUndo = new LC_UNDOINFO;
	int i;

	strcpy(pUndo->strText, text);
	FileSave(&pUndo->file, true);

	for (pTmp = m_pUndoList, i = 0; pTmp; pTmp = pTmp->pNext, i++)
		if ((i == 30) && (pTmp->pNext != NULL))
		{
			delete pTmp->pNext;
			pTmp->pNext = NULL;
			m_bUndoOriginal = false;
		}

	pUndo->pNext = m_pUndoList;
	m_pUndoList = pUndo;

	while (m_pRedoList)
	{
		pUndo = m_pRedoList;
		m_pRedoList = m_pRedoList->pNext;
		delete pUndo;
	}
	m_pRedoList = NULL;

	SystemUpdateUndoRedo(m_pUndoList->pNext ? m_pUndoList->strText : NULL, NULL);
}

void Project::AddView (View* pView)
{
  m_ViewList.Add (pView);

  pView->MakeCurrent ();
  RenderInitialize ();
}

void Project::RemoveView (View* pView)
{
  m_ViewList.RemovePointer (pView);
}

void Project::UpdateAllViews (View* pSender)
{
  for (int i = 0; i < m_ViewList.GetSize (); i++)
    if (m_ViewList[i] != pSender)
      m_ViewList[i]->Redraw ();
}

/////////////////////////////////////////////////////////////////////////////
// Project rendering

// Only this function should be called.
void Project::Render(bool bToMemory)
{
	if (bToMemory)
	{
//		m_bDenyRender = TRUE;
//		m_bRendering = TRUE;
		RenderScene(true, false);
//		m_bRendering = FALSE;
//		m_bDenyRender = FALSE;
		return;
	}

//	if (m_bDenyRender)
//		return;

#ifdef _DEBUG
#ifdef LC_WINDOWS
#define BENCHMARK
#endif
#endif

#ifdef BENCHMARK
	DWORD dwMillis = GetTickCount();
#endif

	m_bStopRender = false;
	m_bRendering = true;

	if ((m_nDetail & LC_DET_FAST) && (m_nTracking != LC_TRACK_NONE))
		RenderScene(false, true);
	else
		RenderScene(true, true);

	m_bRendering = false;

#ifdef BENCHMARK
	dwMillis = GetTickCount() - dwMillis;
	char szMsg[30];
	static int FrameCount = 0;
	FrameCount++;
	sprintf(szMsg, "%d - %.4f sec", FrameCount, (float)dwMillis/1000);
	AfxGetMainWnd()->SetWindowText(szMsg);
#endif
}

typedef struct LC_BSPNODE
{
	float plane[4];
	Piece* piece;
	LC_BSPNODE* front;
	LC_BSPNODE* back;

	~LC_BSPNODE()
	{
		if (piece == NULL)
		{
			if (front)
				delete front;
			if (back)
				delete back;
		}
	}
} LC_BSPNODE;

static void RenderBSP(LC_BSPNODE* node, float* eye, bool* bSel,
	bool bLighting, bool bEdges, unsigned char* nLastColor, bool* bTrans)
{
	if (node->piece)
	{
		if (node->piece->IsSelected())
		{
			if (!*bSel)
			{
				*bSel = true;
				glLineWidth (2);//*m_fLineWidth);
			}
		}
		else
		{
			if (*bSel)
			{
				*bSel = false;
				glLineWidth(1);//m_fLineWidth);
			}
		}

		node->piece->Render(bLighting, bEdges, nLastColor, bTrans);
		return;
	}

	if (eye[0]*node->plane[0] + eye[1]*node->plane[1] +
		eye[2]*node->plane[2] + node->plane[3] > 0.0f)
	{
		RenderBSP(node->back, eye, bSel, bLighting, bEdges, nLastColor, bTrans);
		RenderBSP(node->front, eye, bSel, bLighting, bEdges, nLastColor, bTrans);
	}
	else
	{
		RenderBSP(node->front, eye, bSel, bLighting, bEdges, nLastColor, bTrans);
		RenderBSP(node->back, eye, bSel, bLighting, bEdges, nLastColor, bTrans);
	}
}

static void BuildBSP(LC_BSPNODE* node, Piece* pList)
{
	Piece *front_list = NULL, *back_list = NULL;
	Piece *pPiece, *pNext;

	node->piece = NULL;

	if (pList->m_pLink == NULL)
	{
		// This is a leaf
		node->piece = pList;
		return;
	}

	float dx, dy, dz, bs[6] = { 10000, 10000, 10000, -10000, -10000, -10000 };
	const float *pos;

	for (pPiece = pList; pPiece; pPiece = pPiece->m_pLink)
	{
		pos = pPiece->GetConstPosition();
		if (pos[0] < bs[0]) bs[0] = pos[0];
		if (pos[1] < bs[1]) bs[1] = pos[1];
		if (pos[2] < bs[2]) bs[2] = pos[2];
		if (pos[0] > bs[3]) bs[3] = pos[0];
		if (pos[1] > bs[4]) bs[4] = pos[1];
		if (pos[2] > bs[5]) bs[5] = pos[2];
	}

	dx = ABS(bs[0]-bs[3]);
	dy = ABS(bs[1]-bs[4]);
	dz = ABS(bs[2]-bs[5]);

	node->plane[0] = node->plane[1] = node->plane[2] = 0.0f;

	if (dx > dy)
	{
		if (dx > dz)
			node->plane[0] = 1.0f;
		else
			node->plane[2] = 1.0f;
	}
	else
	{
		if (dy > dz)
			node->plane[1] = 1.0f;
		else
			node->plane[2] = 1.0f;
	}

	// D = -Ax -By -Cz
	node->plane[3] = -(node->plane[0]*(bs[0]+bs[3])/2)-(node->plane[1]*(bs[1]+bs[4])/2)-(node->plane[2]*(bs[2]+bs[5])/2);

	for (pPiece = pList; pPiece;)
	{
		pos = pPiece->GetConstPosition();
		pNext = pPiece->m_pLink;

		if (pos[0]*node->plane[0] + pos[1]*node->plane[1] +
			pos[2]*node->plane[2] + node->plane[3] > 0.0f)
		{
			pPiece->m_pLink = front_list;
			front_list = pPiece;
		}
		else
		{
			pPiece->m_pLink = back_list;
			back_list = pPiece;
		}

		pPiece = pNext;
	}

	if (bs[0] == bs[3] && bs[1] == bs[4] && bs[2] == bs[5])
	{
		if (back_list)
		{
			front_list = back_list;
			back_list = back_list->m_pLink;
			front_list->m_pLink = NULL;
		}
		else
		{
			back_list = front_list;
			front_list = front_list->m_pLink;
			back_list->m_pLink = NULL;
		}
	}

	if (front_list)
	{
		node->front = new LC_BSPNODE;
		BuildBSP(node->front, front_list);
	}
	else
		node->front = NULL;

	if (back_list)
	{
		node->back = new LC_BSPNODE;
		BuildBSP(node->back, back_list);
	}
	else
		node->back = NULL;
}

void Project::RenderScene(bool bShaded, bool bDrawViewports)
{
	glViewport (0, 0, m_nViewX, m_nViewY);
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (bDrawViewports)
	{
		if (bShaded)
		{
			if (m_nDetail & LC_DET_LIGHTING)
				glDisable (GL_LIGHTING);
			if (m_nScene & LC_SCENE_FOG)
				glDisable (GL_FOG);
			RenderViewports(true, true);
			if (m_nDetail & LC_DET_LIGHTING)
				glEnable(GL_LIGHTING);
			if (m_nScene & LC_SCENE_FOG)
				glEnable (GL_FOG);
		}
		else
			RenderViewports(true, true);
	}
	else
		RenderViewports(true, false);

	for (int vp = 0; vp < viewports[m_nViewportMode].n; vp++)
	{
		int x = (int)(viewports[m_nViewportMode].dim[vp][0] * ((float)m_nViewX));
		int y = (int)(viewports[m_nViewportMode].dim[vp][1] * ((float)m_nViewY));
		int w = (int)(viewports[m_nViewportMode].dim[vp][2] * ((float)m_nViewX));
		int h = (int)(viewports[m_nViewportMode].dim[vp][3] * ((float)m_nViewY));

		float ratio = (float)w/h;
		glViewport(x, y, w, h);

		// Disable lighting.
		if ((m_nSnap & LC_DRAW_AXIS) || (m_nSnap & LC_DRAW_GRID))
		{
			if ((bShaded) && (m_nDetail & LC_DET_LIGHTING))
				glDisable(GL_LIGHTING);
		}
		
		if (m_nSnap & LC_DRAW_AXIS)
		{
	    Matrix Mats[3];
		  Mats[0].CreateLookat(m_pViewCameras[vp]->GetEyePos(), m_pViewCameras[vp]->GetTargetPos(), m_pViewCameras[vp]->GetUpVec());
			Mats[0].SetTranslation(0, 0, 0);

			float m1[] = { 0, 1, 0, 0,  1, 0, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1 };
			float m2[] = { 0, 0, 1, 0,  0, 1, 0, 0,  1, 0, 0, 0,  0, 0, 0, 1 };
			Mats[1].Multiply(Mats[0], Matrix(m1));
			Mats[2].Multiply(Mats[0], Matrix(m2));

			float pts[3][3] = { { 20, 0, 0 }, { 0, 20, 0 }, { 0, 0, 20 } };
			Mats[0].TransformPoints(&pts[0][0], 3);

			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho(0, w, 0, h, -50, 50);
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			glTranslatef(25.375f, 25.375f, 0.0f);

			// Draw the arrows.
			for (int i = 0; i < 3; i++)
			{
				switch (i)
				{
				case 0:
					glColor3f(0.8f, 0.0f, 0.0f);
					break;
				case 1:
					glColor3f(0.0f, 0.8f, 0.0f);
					break;
				case 2:
					glColor3f(0.0f, 0.0f, 0.8f);
					break;
				}

				glBegin(GL_LINES);
				glVertex3f(pts[i][0], pts[i][1], pts[i][2]);
				glVertex3f(0, 0, 0);
				glEnd();

				glBegin(GL_TRIANGLE_FAN);
				glVertex3f(pts[i][0], pts[i][1], pts[i][2]);
				for (int j = 0; j < 9; j++)
				{
					float pt[3] = { 12.0f, cosf(LC_2PI * j / 8) * 3.0f, sinf(LC_2PI * j / 8) * 3.0f };
					Mats[i].TransformPoints(pt, 1);
					glVertex3f(pt[0], pt[1], pt[2]);
				}
				glEnd();
			}

			// Draw the text.
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
			m_pScreenFont->MakeCurrent();
			glEnable(GL_TEXTURE_2D);
			glEnable(GL_ALPHA_TEST);

			glBegin(GL_QUADS);
			glColor3f(0, 0, 0);
			m_pScreenFont->PrintText(pts[0][0], pts[0][1], 40.0f, "X");
			m_pScreenFont->PrintText(pts[1][0], pts[1][1], 40.0f, "Y");
			m_pScreenFont->PrintText(pts[2][0], pts[2][1], 40.0f, "Z");
			glEnd();

			glDisable(GL_TEXTURE_2D);
			glDisable(GL_ALPHA_TEST);
		}
					
		m_pViewCameras[vp]->LoadProjection(ratio);

		if ((m_nSnap & LC_DRAW_AXIS) || (m_nSnap & LC_DRAW_GRID))
		{
			glColor3f(1.0f - m_fBackground[0], 1.0f - m_fBackground[1], 1.0f - m_fBackground[2]);

			if (m_nSnap & LC_DRAW_GRID)
				glCallList (m_nGridList);

			if ((bShaded) && (m_nDetail & LC_DET_LIGHTING))
				glEnable(GL_LIGHTING);
		}

		if ((m_nDetail & LC_DET_LIGHTING) != 0)
		{
			int index = 0;
			Light *pLight;

			for (pLight = m_pLights; pLight; pLight = pLight->m_pNext, index++)
				pLight->Setup (index);
		}

    //    glDisable (GL_COLOR_MATERIAL);
    /*
      {
	for (int i = -100; i < 100; i+=5)
	{
	  glBegin (GL_QUAD_STRIP);
	  glNormal3f (0,0,1);
	  for (int j = -100; j < 100; j+=5)
	  {
	    glVertex3f ((float)i/10, (float)j/10,0);
	    glVertex3f ((float)(i+5)/10, (float)j/10,0);
	  }
	  glEnd();
	}
      }
    */
    /*
    {
      LC_RENDER_INFO info;
      info.lighting = (m_nDetail & LC_DET_LIGHTING) != 0;
      info.edges = (m_nDetail & LC_DET_BRICKEDGES) != 0;
      info.fLineWidth = m_fLineWidth;

      info.lastcolor = 255;
      info.transparent = false;


      float pos[] = { 0,0,0 };
      Curve curve (NULL, pos, 0);
      curve.Render (&info);
    }
    */
		if (bShaded)
		{
			if (m_nScene & LC_SCENE_FLOOR)
				m_pTerrain->Render(m_pViewCameras[vp], ratio);

			unsigned char nLastColor = 255;
			bool bTrans = false;
			bool bSel = false;
			bool bCull = false;
			Piece* pPiece;

			LC_BSPNODE tree;
			tree.front = tree.back = NULL;
			Piece* pList = NULL;

			// Draw opaque pieces first
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
//				if (m_nDetail & LC_DET_BACKGROUND)
//				{
//					SystemPumpMessages();
//					if (m_bStopRender)
//						return;
//				}

				if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
				{
					if (!pPiece->IsTransparent())
					{
						if (pPiece->IsSelected())
						{
							if (!bSel)
							{
								bSel = true;
								glLineWidth (2*m_fLineWidth);
							}
						}
						else
						{
							if (bSel)
							{
								bSel = false;
								glLineWidth(m_fLineWidth);
							}
						}
/*
						if (pPiece->m_pInfo->m_nConnectionCount == 1)
						{
							if (bCull)
							{
								bCull = false;
								glDisable(GL_CULL_FACE);
							}
						}
						else
						{
							if (!bCull)
							{
								bCull = true;
								glEnable(GL_CULL_FACE);
							}
						}
*/
						pPiece->Render((m_nDetail & LC_DET_LIGHTING) != 0, (m_nDetail & LC_DET_BRICKEDGES) != 0, &nLastColor, &bTrans);
					}
					else
					{
						pPiece->m_pLink = pList;
						pList = pPiece;
					}
				}
			}

			if (pList)
			{
				float eye[3];
				m_pViewCameras[vp]->GetEyePos (eye);
				BuildBSP(&tree, pList);
				RenderBSP(&tree, eye, &bSel,
					(m_nDetail & LC_DET_LIGHTING) != 0, (m_nDetail & LC_DET_BRICKEDGES) != 0, &nLastColor, &bTrans);
			}


#if 0
			// Draw transparent pieces
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
//				MSG msg;
//				while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
//				{
//					TranslateMessage(&msg);
//					DispatchMessage(&msg);  
//					if (m_bStopRender) 
//						return;
//				}

				if ((pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation)) &&
					(pPiece->IsTransparent()))
				{
					if (pPiece->IsSelected())
					{
						if (!bSel)
						{
							bSel = true;
							glLineWidth (2*m_fLineWidth);
						}
					}
					else
					{
						if (bSel)
						{
							bSel = false;
							glLineWidth(m_fLineWidth);
						}
					}
/*
					if (pPiece->m_pInfo->m_nConnectionCount == 1)
					{
						if (bCull)
						{
							bCull = FALSE;
							glDisable(GL_CULL_FACE);
						}
					}
					else
					{
						if (!bCull)
						{
							bCull = TRUE;
							glEnable(GL_CULL_FACE);
						}
					}
*/
					pPiece->Render((m_nDetail & LC_DET_LIGHTING) != 0, (m_nDetail & LC_DET_BRICKEDGES) != 0, &nLastColor, &bTrans);
				}
			}
#endif
			if (bTrans)
			{
				glDepthMask(GL_TRUE);
				glDisable(GL_BLEND);
			}
			if (bSel)
				glLineWidth(m_fLineWidth);
			if (bCull)
				glDisable(GL_CULL_FACE);
		}
		else
		{
//			glEnable (GL_CULL_FACE);
//			glShadeModel (GL_FLAT);
//			glDisable (GL_LIGHTING);
			if ((m_nDetail & LC_DET_BOX_FILL) == 0)
			{
				if ((m_nDetail & LC_DET_HIDDEN_LINE) != 0)
				{
					// Wireframe with hidden lines removed
					glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
					RenderBoxes(false);
					glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
					glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
					RenderBoxes(true);
					glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
				}
				else
				{
					// Wireframe
					glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
					RenderBoxes(true);
					glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
				}
			}
			else
				RenderBoxes(true);
		}

#ifdef LC_DEBUG
		RenderDebugPrimitives();
#endif

		// Draw cameras & lights
		if (bDrawViewports)
		{
			if (m_nCurAction == LC_ACTION_INSERT)
			{
				Vector3 Pos;
				Vector4 Rot;
				GetPieceInsertPosition(m_nDownX, m_nDownY, Pos, Rot);

				glPushMatrix();
				glTranslatef(Pos[0], Pos[1], Pos[2]);
				glRotatef(Rot[3], Rot[0], Rot[1], Rot[2]);
				glLineWidth(2*m_fLineWidth);
				m_pCurPiece->RenderPiece(m_nCurColor);
				glLineWidth(m_fLineWidth);
				glPopMatrix();
			}

			if (m_nDetail & LC_DET_LIGHTING)
			{
				glDisable (GL_LIGHTING);
				int index = 0;
				Light *pLight;
				
				for (pLight = m_pLights; pLight; pLight = pLight->m_pNext, index++)
					glDisable ((GLenum)(GL_LIGHT0+index));
			}
			
			Camera* pCamera;
			Light* pLight;
			
			for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
			{
				if ((pCamera == m_pViewCameras[vp]) || !pCamera->IsVisible())
					continue;
				pCamera->Render(m_fLineWidth);
			}

			for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
				if (pLight->IsVisible ())
					pLight->Render(m_fLineWidth);
				
				if (m_nDetail & LC_DET_LIGHTING)
					glEnable (GL_LIGHTING);
		}

		// Draw the selection rectangle.
		if ((m_nCurAction == LC_ACTION_SELECT) && (m_nTracking == LC_TRACK_LEFT))
		{
			int x, y, w, h;

			x = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX);
			y = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY);
			w = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX);
			h = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY);

			glViewport(x, y, w, h);

			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho(0, w, 0, h, -1, 1);
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			glTranslatef(0.375, 0.375, 0.0);

			glDisable(GL_DEPTH_TEST);
			glEnable(GL_LINE_STIPPLE);
			glLineStipple(5, 0x5555);
			glColor3f(0, 0, 0);

			float pt1x = (float)(m_nDownX - x);
			float pt1y = (float)(m_nDownY - y);
			float pt2x = m_fTrack[0] - x;
			float pt2y = m_fTrack[1] - y;

			glBegin(GL_LINES);
			glVertex2f(pt1x, pt1y);
			glVertex2f(pt2x, pt1y);
			glVertex2f(pt2x, pt1y);
			glVertex2f(pt2x, pt2y);
			glVertex2f(pt2x, pt2y);
			glVertex2f(pt1x, pt2y);
			glVertex2f(pt1x, pt2y);
			glVertex2f(pt1x, pt1y);
			glEnd();

			glDisable(GL_LINE_STIPPLE);
			glEnable(GL_DEPTH_TEST);
		}

		if (bDrawViewports && m_OverlayActive)
			RenderOverlays(vp);
	}
}

void Project::RenderOverlays(int Viewport)
{
	const float OverlayScale = m_OverlayScale[Viewport];

	if (m_nCurAction == LC_ACTION_MOVE)
	{
		const float OverlayMovePlaneSize = 0.5f;
		const float OverlayMoveArrowSize = 1.5f;
		const float OverlayMoveArrowCapSize = 0.9f;
		const float OverlayMoveArrowCapRadius = 0.1f;

		glDisable(GL_DEPTH_TEST);

		// Find the rotation from the focused piece if relative snap is enabled.
		Object* Focus = NULL;
		float Rot[4];

		if ((m_nSnap & LC_DRAW_GLOBAL_SNAP) == 0)
		{
			Focus = GetFocusObject();

			if ((Focus != NULL) && Focus->IsPiece())
				((Piece*)Focus)->GetRotation(Rot);
			else
				Focus = NULL;
		}

		// Draw a quad if we're moving on a plane.
		if ((m_OverlayMode == LC_OVERLAY_XY) || (m_OverlayMode == LC_OVERLAY_XZ) || (m_OverlayMode == LC_OVERLAY_YZ))
		{
			glPushMatrix();
			glTranslatef(m_OverlayCenter[0], m_OverlayCenter[1], m_OverlayCenter[2]);

			if (Focus)
				glRotatef(Rot[3], Rot[0], Rot[1], Rot[2]);

			if (m_OverlayMode == LC_OVERLAY_XZ)
				glRotatef(90.0f, 0.0f, 0.0f, -1.0f);
			else if (m_OverlayMode == LC_OVERLAY_XY)
				glRotatef(90.0f, 0.0f, 1.0f, 0.0f);

			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			glEnable(GL_BLEND);

			glBegin(GL_QUADS);
			glColor4f(0.8f, 0.8f, 0.0f, 0.3f);
			glVertex3f(0.0f, 0.0f, 0.0f);
			glVertex3f(0.0f, OverlayScale * OverlayMovePlaneSize, 0.0f);
			glVertex3f(0.0f, OverlayScale * OverlayMovePlaneSize, OverlayScale * OverlayMovePlaneSize);
			glVertex3f(0.0f, 0.0f, OverlayScale * OverlayMovePlaneSize);
			glEnd();

			glDisable(GL_BLEND);

			glPopMatrix();
		}

		// Draw the arrows.
		for (int i = 0; i < 3; i++)
		{
			switch (i)
			{
			case 0:
				if ((m_OverlayMode == LC_OVERLAY_X) || (m_OverlayMode == LC_OVERLAY_XY) || (m_OverlayMode == LC_OVERLAY_XZ))
					glColor3f(0.8f, 0.8f, 0.0f);
				else
					glColor3f(0.8f, 0.0f, 0.0f);
				break;
			case 1:
				if ((m_OverlayMode == LC_OVERLAY_Y) || (m_OverlayMode == LC_OVERLAY_XY) || (m_OverlayMode == LC_OVERLAY_YZ))
					glColor3f(0.8f, 0.8f, 0.0f);
				else
					glColor3f(0.0f, 0.8f, 0.0f);
				break;
			case 2:
				if ((m_OverlayMode == LC_OVERLAY_Z) || (m_OverlayMode == LC_OVERLAY_XZ) || (m_OverlayMode == LC_OVERLAY_YZ))
					glColor3f(0.8f, 0.8f, 0.0f);
				else
					glColor3f(0.0f, 0.0f, 0.8f);
				break;
			}

			glPushMatrix();
			glTranslatef(m_OverlayCenter[0], m_OverlayCenter[1], m_OverlayCenter[2]);

			if (Focus)
				glRotatef(Rot[3], Rot[0], Rot[1], Rot[2]);

			if (i == 1)
				glRotatef(90.0f, 0.0f, 0.0f, 1.0f);
			else if (i == 2)
				glRotatef(90.0f, 0.0f, -1.0f, 0.0f);

			glBegin(GL_LINES);
			glVertex3f(0.0f, 0.0f, 0.0f);
			glVertex3f(OverlayScale * OverlayMoveArrowSize, 0.0f, 0.0f);
			glEnd();

			glBegin(GL_TRIANGLE_FAN);
			glVertex3f(OverlayScale * OverlayMoveArrowSize, 0.0f, 0.0f);
			for (int j = 0; j < 9; j++)
			{
				float y = cosf(LC_2PI * j / 8) * OverlayMoveArrowCapRadius * OverlayScale;
				float z = sinf(LC_2PI * j / 8) * OverlayMoveArrowCapRadius * OverlayScale;
				glVertex3f(OverlayScale * OverlayMoveArrowCapSize, y, z);
			}
			glEnd();

			glPopMatrix();
		}

		glEnable(GL_DEPTH_TEST);
	}
	else if (m_nCurAction == LC_ACTION_ROTATE)
	{
		const float OverlayRotateRadius = 2.0f;

		glDisable(GL_DEPTH_TEST);

		Camera* Cam = m_pViewCameras[Viewport];
		Matrix44 Mat;
		int j;

		// Find the rotation from the focused piece if relative snap is enabled.
		Object* Focus = NULL;
		float Rot[4];

		if ((m_nSnap & LC_DRAW_GLOBAL_SNAP) == 0)
		{
			Focus = GetFocusObject();

			if ((Focus != NULL) && Focus->IsPiece())
				((Piece*)Focus)->GetRotation(Rot);
			else
				Focus = NULL;
		}

		// Draw a disc showing the rotation amount.
		if (m_MouseTotalDelta.LengthSquared() != 0.0f && (m_nTracking != LC_TRACK_NONE))
		{
			Vector4 Rotation;
			float Angle, Step;

			switch (m_OverlayMode)
			{
			case LC_OVERLAY_X:
				glColor4f(0.8f, 0.0f, 0.0f, 0.3f);
				Angle = m_MouseTotalDelta[0];
				Rotation = Vector4(0.0f, 0.0f, 0.0f, 0.0f);
				break;
			case LC_OVERLAY_Y:
				glColor4f(0.0f, 0.8f, 0.0f, 0.3f);
				Angle = m_MouseTotalDelta[1];
				Rotation = Vector4(90.0f, 0.0f, 0.0f, 1.0f);
				break;
			case LC_OVERLAY_Z:
				glColor4f(0.0f, 0.0f, 0.8f, 0.3f);
				Angle = m_MouseTotalDelta[2];
				Rotation = Vector4(90.0f, 0.0f, -1.0f, 0.0f);
				break;
			default:
				Angle = 0.0f;
				break;
			};

			if (Angle > 0.0f)
			{
				Step = 360.0f / 32;
			}
			else
			{
				Angle = -Angle;
				Step = -360.0f / 32;
			}

			if (fabsf(Angle) >= fabsf(Step))
			{
				glPushMatrix();
				glTranslatef(m_OverlayCenter[0], m_OverlayCenter[1], m_OverlayCenter[2]);

				if (Focus)
					glRotatef(Rot[3], Rot[0], Rot[1], Rot[2]);

				glRotatef(Rotation[0], Rotation[1], Rotation[2], Rotation[3]);

				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				glEnable(GL_BLEND);

				glBegin(GL_TRIANGLE_FAN);

				glVertex3f(0.0f, 0.0f, 0.0f);

				float StartAngle;
				int i = 0;

				if (Step < 0)
					StartAngle = -Angle;
				else
					StartAngle = Angle;

				do
				{
					float x = cosf((Step * i - StartAngle) * DTOR) * OverlayRotateRadius * OverlayScale;
					float y = sinf((Step * i - StartAngle) * DTOR) * OverlayRotateRadius * OverlayScale;

					glVertex3f(0.0f, x, y);

					i++;
					if (Step > 0)
						Angle -= Step;
					else
						Angle += Step;

				} while (Angle >= 0.0f);

				glEnd();

				glDisable(GL_BLEND);

				glPopMatrix();
			}
		}

		Mat.CreateLookAt(Cam->GetEyePosition(), Cam->GetTargetPosition(), Cam->GetUpVector());
		Mat.Transpose3();
		Mat.SetTranslation(m_OverlayCenter);

		// Draw the circles.
		glBegin(GL_LINE_LOOP);
		glColor3f(0.1f, 0.1f, 0.1f);

		for (j = 0; j < 32; j++)
		{
			Vector3 Pt;

			Pt[0] = cosf(LC_2PI * j / 32) * OverlayRotateRadius * OverlayScale;
			Pt[1] = sinf(LC_2PI * j / 32) * OverlayRotateRadius * OverlayScale;
			Pt[2] = 0.0f;

			Pt = Mul31(Pt, Mat);

			glVertex3f(Pt[0], Pt[1], Pt[2]);
		}

		glEnd();

		Vector3 ViewDir = Cam->GetTargetPosition() - Cam->GetEyePosition();
		ViewDir.Normalize();

		// Transform ViewDir to local space.
		if (Focus)
		{
			Matrix33 RotMat;
			RotMat.CreateFromAxisAngle(Vector3(Rot[0], Rot[1], Rot[2]), -Rot[3] * LC_DTOR);

			ViewDir = Mul(ViewDir, RotMat);
		}

		glTranslatef(m_OverlayCenter[0], m_OverlayCenter[1], m_OverlayCenter[2]);

		if (Focus)
			glRotatef(Rot[3], Rot[0], Rot[1], Rot[2]);

		// Draw each axis circle.
		for (int i = 0; i < 3; i++)
		{
			if (m_OverlayMode == LC_OVERLAY_X + i)
			{
				glColor3f(0.8f, 0.8f, 0.0f);
			}
			else
			{
				switch (i)
				{
				case 0:
					glColor3f(0.8f, 0.0f, 0.0f);
					break;
				case 1:
					glColor3f(0.0f, 0.8f, 0.0f);
					break;
				case 2:
					glColor3f(0.0f, 0.0f, 0.8f);
					break;
				}
			}

			glBegin(GL_LINES);

			for (int j = 0; j < 32; j++)
			{
				Vector3 v1, v2;

				switch (i)
				{
				case 0:
					v1 = Vector3(0.0f, cosf(LC_2PI * j / 32), sinf(LC_2PI * j / 32));
					v2 = Vector3(0.0f, cosf(LC_2PI * (j + 1) / 32), sinf(LC_2PI * (j + 1) / 32));
					break;

				case 1:
					v1 = Vector3(cosf(LC_2PI * j / 32), 0.0f, sinf(LC_2PI * j / 32));
					v2 = Vector3(cosf(LC_2PI * (j + 1) / 32), 0.0f, sinf(LC_2PI * (j + 1) / 32));
					break;

				case 2:
					v1 = Vector3(cosf(LC_2PI * j / 32), sinf(LC_2PI * j / 32), 0.0f);
					v2 = Vector3(cosf(LC_2PI * (j + 1) / 32), sinf(LC_2PI * (j + 1) / 32), 0.0f);
					break;
				}

				if (Dot3(ViewDir, v1+v2) <= 0.0f)
				{
					Vector3 Pt1 = v1 * OverlayRotateRadius * OverlayScale;
					Vector3 Pt2 = v2 * OverlayRotateRadius * OverlayScale;

					glVertex3f(Pt1[0], Pt1[1], Pt1[2]);
					glVertex3f(Pt2[0], Pt2[1], Pt2[2]);
				}
			}

			glEnd();
		}

		// Draw tangent vector.
		if (m_nTracking != LC_TRACK_NONE)
		{
			if ((m_OverlayMode == LC_OVERLAY_X) || (m_OverlayMode == LC_OVERLAY_Y) || (m_OverlayMode == LC_OVERLAY_Z))
			{
				Vector3 Tangent, Normal = m_OverlayTrackStart - m_OverlayCenter;
				Normal.Normalize();
				float Angle;

				switch (m_OverlayMode)
				{
				case LC_OVERLAY_X:
					Angle = m_MouseTotalDelta[0];
					Tangent = Vector3(0.0f, -Normal[2], Normal[1]);
					break;
				case LC_OVERLAY_Y:
					Angle = m_MouseTotalDelta[1];
					Tangent = Vector3(Normal[2], 0.0f, -Normal[0]);
					break;
				case LC_OVERLAY_Z:
					Angle = m_MouseTotalDelta[2];
					Tangent = Vector3(-Normal[1], Normal[0], 0.0f);
					break;
				}

				if (Angle < 0.0f)
				{
					Tangent = -Tangent;
					Angle = -Angle;
				}

				// Draw tangent arrow.
				if (Angle > 0.0f)
				{
					const float OverlayRotateArrowSize = 1.5f;
					const float OverlayRotateArrowCapSize = 0.25f;

					Vector3 Pt = Normal * OverlayScale * OverlayRotateRadius;
					Vector3 Tip = Pt + Tangent * OverlayScale * OverlayRotateArrowSize;
					Vector3 Arrow;
					Matrix33 Rot;

					glBegin(GL_LINES);
					glColor3f(0.8f, 0.8f, 0.0f);

					glVertex3f(Pt[0], Pt[1], Pt[2]);
					glVertex3f(Tip[0], Tip[1], Tip[2]);

					Rot.CreateFromAxisAngle(Normal, LC_PI * 0.15f);
					Arrow = Mul(Tangent, Rot) * OverlayRotateArrowCapSize;

					glVertex3f(Tip[0], Tip[1], Tip[2]);
					glVertex3f(Tip[0] - Arrow[0], Tip[1] - Arrow[1], Tip[2] - Arrow[2]);

					Rot.CreateFromAxisAngle(Normal, -LC_PI * 0.15f);
					Arrow = Mul(Tangent, Rot) * OverlayRotateArrowCapSize;

					glVertex3f(Tip[0], Tip[1], Tip[2]);
					glVertex3f(Tip[0] - Arrow[0], Tip[1] - Arrow[1], Tip[2] - Arrow[2]);

					glEnd();
				}

				// Draw text.
				if (Viewport == m_nActiveViewport)
				{
					GLdouble ScreenX, ScreenY, ScreenZ;
					GLdouble ModelMatrix[16], ProjMatrix[16];
					GLint Vp[4];

					glGetDoublev(GL_MODELVIEW_MATRIX, ModelMatrix);
					glGetDoublev(GL_PROJECTION_MATRIX, ProjMatrix);
					glGetIntegerv(GL_VIEWPORT, Vp);

					gluProject(0, 0, 0, ModelMatrix, ProjMatrix, Vp, &ScreenX, &ScreenY, &ScreenZ);

					glMatrixMode(GL_PROJECTION);
					glPushMatrix();
					glLoadIdentity();
					glOrtho(0, Vp[2], 0, Vp[3], -1, 1);
					glMatrixMode(GL_MODELVIEW);
					glPushMatrix();
					glLoadIdentity();
					glTranslatef(0.375, 0.375, 0.0);

					glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
					m_pScreenFont->MakeCurrent();
					glEnable(GL_TEXTURE_2D);
					glEnable(GL_ALPHA_TEST);

					char buf[32];
					sprintf(buf, "[%.2f]", Angle);

					int cx, cy;
					m_pScreenFont->GetStringDimensions(&cx, &cy, buf);

					glBegin(GL_QUADS);
					glColor3f(0.8f, 0.8f, 0.0f);
					m_pScreenFont->PrintText((float)ScreenX - Vp[0] - (cx / 2), (float)ScreenY - Vp[1] + (cy / 2), 0.0f, buf);
					glEnd();

					glDisable(GL_TEXTURE_2D);
					glDisable(GL_ALPHA_TEST);

					glMatrixMode(GL_PROJECTION);
					glPopMatrix();
					glMatrixMode(GL_MODELVIEW);
					glPopMatrix();
				}
			}
		}

		glEnable(GL_DEPTH_TEST);
	}
	else if (m_nCurAction == LC_ACTION_ROTATE_VIEW)
	{
		int x, y, w, h;

		x = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX);
		y = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY);
		w = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX);
		h = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY);

		glViewport(0, 0, m_nViewX, m_nViewY);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, m_nViewX, 0, m_nViewY, -1, 1);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glTranslatef(0.375f, 0.375f, 0.0f);

		glDisable(GL_DEPTH_TEST);
		glColor3f(0, 0, 0);

		// Draw circle.
		glBegin(GL_LINE_LOOP);

		float r = min(w, h) * 0.35f;
		float cx = x + w / 2.0f;
		float cy = y + h / 2.0f;

		for (int i = 0; i < 32; i++)
		{
			float x = cosf((float)i / 32.0f * (2.0f * LC_PI)) * r + cx;
			float y = sinf((float)i / 32.0f * (2.0f * LC_PI)) * r + cy;

			glVertex2f(x, y);
		}

		glEnd();

		const float OverlayCameraSquareSize = max(8.0f, (w+h)/200);

		// Draw squares.
		glBegin(GL_LINE_LOOP);
		glVertex2f(cx + OverlayCameraSquareSize, cy + r + OverlayCameraSquareSize);
		glVertex2f(cx - OverlayCameraSquareSize, cy + r + OverlayCameraSquareSize);
		glVertex2f(cx - OverlayCameraSquareSize, cy + r - OverlayCameraSquareSize);
		glVertex2f(cx + OverlayCameraSquareSize, cy + r - OverlayCameraSquareSize);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex2f(cx + OverlayCameraSquareSize, cy - r + OverlayCameraSquareSize);
		glVertex2f(cx - OverlayCameraSquareSize, cy - r + OverlayCameraSquareSize);
		glVertex2f(cx - OverlayCameraSquareSize, cy - r - OverlayCameraSquareSize);
		glVertex2f(cx + OverlayCameraSquareSize, cy - r - OverlayCameraSquareSize);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex2f(cx + r + OverlayCameraSquareSize, cy + OverlayCameraSquareSize);
		glVertex2f(cx + r - OverlayCameraSquareSize, cy + OverlayCameraSquareSize);
		glVertex2f(cx + r - OverlayCameraSquareSize, cy - OverlayCameraSquareSize);
		glVertex2f(cx + r + OverlayCameraSquareSize, cy - OverlayCameraSquareSize);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex2f(cx - r + OverlayCameraSquareSize, cy + OverlayCameraSquareSize);
		glVertex2f(cx - r - OverlayCameraSquareSize, cy + OverlayCameraSquareSize);
		glVertex2f(cx - r - OverlayCameraSquareSize, cy - OverlayCameraSquareSize);
		glVertex2f(cx - r + OverlayCameraSquareSize, cy - OverlayCameraSquareSize);
		glEnd();

		glEnable(GL_DEPTH_TEST);
	}
	else if (m_nCurAction == LC_ACTION_ZOOM_REGION)
	{
		int x, y, w, h;

		x = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX);
		y = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY);
		w = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX);
		h = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY);

		glViewport(0, 0, m_nViewX, m_nViewY);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, m_nViewX, 0, m_nViewY, -1, 1);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glTranslatef(0.375f, 0.375f, 0.0f);

		glDisable(GL_DEPTH_TEST);
		glEnable(GL_LINE_STIPPLE);
		glLineStipple(5, 0x5555);
		glColor3f(0, 0, 0);

		float pt1x = (float)(m_nDownX - x);
		float pt1y = (float)(m_nDownY - y);
		float pt2x = m_OverlayTrackStart[0] - x;
		float pt2y = m_OverlayTrackStart[1] - y;

		glBegin(GL_LINES);
		glVertex2f(pt1x, pt1y);
		glVertex2f(pt2x, pt1y);
		glVertex2f(pt2x, pt1y);
		glVertex2f(pt2x, pt2y);
		glVertex2f(pt2x, pt2y);
		glVertex2f(pt1x, pt2y);
		glVertex2f(pt1x, pt2y);
		glVertex2f(pt1x, pt1y);
		glEnd();

		glDisable(GL_LINE_STIPPLE);
		glEnable(GL_DEPTH_TEST);
	}
}

void Project::RenderViewports(bool bBackground, bool bLines)
{
	float x, y, w, h;
	int vp;

	glViewport(0, 0, m_nViewX, m_nViewY);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, m_nViewX, 0, m_nViewY, -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.375, 0.375, 0.0);

	if (bBackground)
	{
		// Draw gradient
		if (m_nScene & LC_SCENE_GRADIENT)
		{
			if ((m_nDetail & LC_DET_SMOOTH) == 0)
				glShadeModel(GL_SMOOTH);
			glDisable(GL_DEPTH_TEST);
			glBegin(GL_QUADS);

			for (vp = 0; vp < viewports[m_nViewportMode].n; vp++)
			{
				x = viewports[m_nViewportMode].dim[vp][0] * (float)m_nViewX;
				y = viewports[m_nViewportMode].dim[vp][1] * (float)m_nViewY;
				w = viewports[m_nViewportMode].dim[vp][2] * (float)m_nViewX;
				h = viewports[m_nViewportMode].dim[vp][3] * (float)m_nViewY;

				glColor3fv(m_fGradient1);
				glVertex2f(x+w, y+h);
				glVertex2f(x, y+h);
				glColor3fv(m_fGradient2);
				glVertex2f(x, y);
				glVertex2f(x+w, y);
			}
			glEnd();
			glEnable(GL_DEPTH_TEST);
			if ((m_nDetail & LC_DET_SMOOTH) == 0)
				glShadeModel(GL_FLAT);
		}

		glEnable(GL_TEXTURE_2D);

		// Draw the background
		if (m_nScene & LC_SCENE_BG)
		{
			glDisable (GL_DEPTH_TEST);
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
			m_pBackground->MakeCurrent();
			glBegin(GL_QUADS);

			for (vp = 0; vp < viewports[m_nViewportMode].n; vp++)
			{
				x = viewports[m_nViewportMode].dim[vp][0] * (float)m_nViewX;
				y = viewports[m_nViewportMode].dim[vp][1] * (float)m_nViewY;
				w = viewports[m_nViewportMode].dim[vp][2] * (float)m_nViewX;
				h = viewports[m_nViewportMode].dim[vp][3] * (float)m_nViewY;

				float tw = 1.0f, th = 1.0f;
				if (m_nScene & LC_SCENE_BG_TILE)
				{
					tw = w/m_pBackground->m_nWidth;
					th = h/m_pBackground->m_nHeight;
				}

				glTexCoord2f(0, 0);
				glVertex2f(x, y+h);
				glTexCoord2f(tw, 0);
				glVertex2f(x+w, y+h);
				glTexCoord2f(tw, th); 
				glVertex2f(x+w, y);
				glTexCoord2f(0, th);
				glVertex2f(x, y);
			}
			glEnd();
			glEnable(GL_DEPTH_TEST);
		}

		if (!bLines)
			glDisable(GL_TEXTURE_2D);
	}

	if (bLines)
	{
		// Draw text
		glColor3f(0, 0, 0);
		if (!bBackground)
			glEnable(GL_TEXTURE_2D);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		m_pScreenFont->MakeCurrent();
		glEnable(GL_ALPHA_TEST);
		glBegin(GL_QUADS);

		for (vp = 0; vp < viewports[m_nViewportMode].n; vp++)
		{
			x = viewports[m_nViewportMode].dim[vp][0] * (float)m_nViewX;
			y = viewports[m_nViewportMode].dim[vp][1] * (float)m_nViewY;
			w = viewports[m_nViewportMode].dim[vp][2] * (float)(m_nViewX - 1);
			h = viewports[m_nViewportMode].dim[vp][3] * (float)(m_nViewY - 1);

      m_pScreenFont->PrintText(x + 3, y + h - 6, 0.0f, m_pViewCameras[vp]->GetName());
		}
		glEnd();
	
		glDisable(GL_ALPHA_TEST);
		glDisable(GL_TEXTURE_2D);

		// Borders
		if (m_fLineWidth != 1.0f)
			glLineWidth (1.0f);

		for (vp = 0; vp < viewports[m_nViewportMode].n; vp++)
		{
			if (vp == m_nActiveViewport)
				continue;

			x = viewports[m_nViewportMode].dim[vp][0] * (float)m_nViewX;
			y = viewports[m_nViewportMode].dim[vp][1] * (float)m_nViewY;
			w = viewports[m_nViewportMode].dim[vp][2] * (float)(m_nViewX - 1);
			h = viewports[m_nViewportMode].dim[vp][3] * (float)(m_nViewY - 1);

			glBegin(GL_LINE_LOOP);
			glVertex2f(x, y);
			glVertex2f(x+w, y);
			glVertex2f(x+w, y+h);
			glVertex2f(x, y+h);
			glEnd();
		}

		x = viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX;
		y = viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY;
		w = viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)(m_nViewX - 1);
		h = viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)(m_nViewY - 1);

		glColor3f(1.0f, 0, 0);
		glBegin(GL_LINE_LOOP);
		glVertex2f(x, y);
		glVertex2f(x+w, y);
		glVertex2f(x+w, y+h);
		glVertex2f(x, y+h);
		glEnd();

		if (m_fLineWidth != 1.0f)
			glLineWidth (m_fLineWidth);
	}
}

// bHilite - Draws focus/selection, not used for the 
// first rendering pass if remove hidden lines is enabled
void Project::RenderBoxes(bool bHilite)
{
	Piece* pPiece;

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
			pPiece->RenderBox(bHilite, m_fLineWidth);
}

// Initialize OpenGL
void Project::RenderInitialize()
{
	int i;
	glLineStipple (1, 65280);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glEnable(GL_POLYGON_OFFSET_FILL);
	glPolygonOffset(0.5f, 0.1f);

	glDrawBuffer(GL_BACK);
	glCullFace(GL_BACK);
	glDisable (GL_CULL_FACE);

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glDepthMask(GL_TRUE);

	if (m_nDetail & LC_DET_DITHER)
		glEnable(GL_DITHER);
	else
		glDisable(GL_DITHER);
/*
	// TODO: use a blending function
	if (m_dwDetail & DET_ANTIALIAS)
	{
		glEnable(GL_LINE_SMOOTH);
		glEnable(GL_POLYGON_SMOOTH);
	}
	else
	{
		glDisable(GL_LINE_SMOOTH);
		glDisable(GL_POLYGON_SMOOTH);
	}
*/
	if (m_nDetail & LC_DET_SMOOTH)
		glShadeModel(GL_SMOOTH);
	else
		glShadeModel(GL_FLAT);

	if (m_nDetail & LC_DET_LIGHTING)
	{
	    glEnable(GL_LIGHTING);
            glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT);
            glEnable(GL_COLOR_MATERIAL);

            GLfloat mat_translucent[] = { (GLfloat)0.8, (GLfloat)0.8, (GLfloat)0.8, (GLfloat)1.0 };
            GLfloat mat_opaque[] = { (GLfloat)0.8, (GLfloat)0.8, (GLfloat)0.8, (GLfloat)1.0 };
            GLfloat medium_shininess[] = { (GLfloat)64.0 };

            glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, medium_shininess);
            glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_opaque);
            glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_translucent);
	}
	else
	{
		glDisable(GL_LIGHTING);
		glDisable(GL_COLOR_MATERIAL);
	}

	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, m_fAmbient);
	glClearColor(m_fBackground[0], m_fBackground[1], m_fBackground[2], 1.0f);

	if (m_nScene & LC_SCENE_FOG)
	{
		glEnable(GL_FOG);
		glFogi(GL_FOG_MODE, GL_EXP);
		glFogf(GL_FOG_DENSITY, m_fFogDensity);
		glFogfv(GL_FOG_COLOR, m_fFogColor);
	}
	else
		glDisable (GL_FOG);

	// Load font
  if (!m_pScreenFont->IsLoaded ())
  {
    char filename[LC_MAXPATH];
    FileDisk file;

    strcpy (filename, lcGetPiecesLibrary()->GetLibraryPath ());
    strcat (filename, "sysfont.txf");

    if (file.Open (filename, "rb"))
      m_pScreenFont->FileLoad (file);
  }

	glAlphaFunc(GL_GREATER, 0.0625);

	if (m_nScene & LC_SCENE_FLOOR)
		m_pTerrain->LoadTexture((m_nDetail & LC_DET_LINEAR) != 0);

	if (m_nScene & LC_SCENE_BG)
		if (!m_pBackground->LoadFromFile(m_strBackground, (m_nDetail & LC_DET_LINEAR) != 0))
		{
			m_nScene &= ~LC_SCENE_BG;
//			AfxMessageBox ("Could not load background");
		}

	// Set the perspective correction hint to fastest or nicest...
//	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);

	// Grid display list
	if (m_nGridList == 0)
		m_nGridList = glGenLists(1);
	glNewList (m_nGridList, GL_COMPILE);
	glEnableClientState(GL_VERTEX_ARRAY);
	i = 2*(4*m_nGridSize+2); // verts needed (2*lines)
	float *grid = (float*)malloc(i*sizeof(float[3]));
	float x = m_nGridSize*0.8f;

	for (int j = 0; j <= m_nGridSize*2; j++)
	{
		grid[j*12] = x;
		grid[j*12+1] = m_nGridSize*0.8f;
		grid[j*12+2] = 0;
		grid[j*12+3] = x;
		grid[j*12+4] = -m_nGridSize*0.8f;
		grid[j*12+5] = 0;
		grid[j*12+6] = m_nGridSize*0.8f;
		grid[j*12+7] = x;
		grid[j*12+8] = 0;
		grid[j*12+9] = -m_nGridSize*0.8f;
		grid[j*12+10] = x;
		grid[j*12+11] = 0;
		x -= 0.8f;
	}
	glVertexPointer(3, GL_FLOAT, 0, grid);
	glDrawArrays(GL_LINES, 0, i);
	glEndList();
	free(grid);
}

/////////////////////////////////////////////////////////////////////////////
// Project functions

void Project::AddPiece(Piece* pPiece)
{
/*
// Sort piece array to avoid OpenGL state changes (needs work)
void CCADDoc::AddPiece(CPiece* pNewPiece)
{
	POSITION pos1, pos2;

	for (pos1 = m_Pieces.GetHeadPosition(); (pos2 = pos1) != NULL;)
	{
		CPiece* pPiece = m_Pieces.GetNext(pos1);
		if (pPiece->IsTransparent())
			break;
	}

	if (pos2 == NULL || pNewPiece->IsTransparent())
		m_Pieces.AddTail(pNewPiece);
	else
		m_Pieces.InsertBefore(pos2, pNewPiece);
}
*/
	if (m_pPieces != NULL)
	{
		pPiece->m_pNext = m_pPieces;
		m_pPieces = pPiece;
	// TODO: sorting and BSP
	}
	else
	{
		m_pPieces = pPiece;
		pPiece->m_pNext = NULL;
	}

	pPiece->AddConnections(m_pConnections);
}

void Project::RemovePiece(Piece* pPiece)
{
	Piece* pTemp, *pLast;
	pLast = NULL;

	for (pTemp = m_pPieces; pTemp; pLast = pTemp, pTemp = pTemp->m_pNext)
		if (pTemp == pPiece)
		{
			if (pLast != NULL)
				pLast->m_pNext = pTemp->m_pNext;
			else
				m_pPieces = pTemp->m_pNext;

			break;
		}

	pPiece->RemoveConnections(m_pConnections);

	// TODO: remove from BSP
}

void Project::CalculateStep()
{
	int PieceCount = 0;
	Piece* pPiece;
	Camera* pCamera;
	Light* pLight;

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
	{
		pPiece->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
		PieceCount++;
	}

  SystemDoWaitCursor(1);
	SystemStartProgressBar(0, PieceCount, 1, "Updating pieces...");

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
	{
		pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, false, false);
		SytemStepProgressBar();
	}

	SytemEndProgressBar();
  SystemDoWaitCursor(-1);

	for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
		pCamera->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);

	for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
		pLight->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
}

// Returns true if anything was removed (used by cut and del)
bool Project::RemoveSelectedObjects()
{
	Piece* pPiece;
	Camera* pCamera;
	Light* pLight;
	void* pPrev;
	bool removed = false;

	pPiece = m_pPieces;
	while (pPiece)
	{
		if (pPiece->IsSelected())
		{
			Piece* pTemp;
			pTemp = pPiece->m_pNext;

			removed = true;
			RemovePiece(pPiece);
			delete pPiece;
			pPiece = pTemp;
		}
		else
			pPiece = pPiece->m_pNext;
	}

	// Cameras can't be removed while being used or default
	for (pPrev = NULL, pCamera = m_pCameras; pCamera; )
	{
		bool bCanDelete = true;
		for (int i = 0; i < 4; i++)
		{
			if (pCamera == m_pViewCameras[i])
			{
				bCanDelete = false;
				break;
			}
		}

		if (bCanDelete && pCamera->IsSelected() && pCamera->IsUser())
		{
			if (pPrev)
			{
				((Camera*)pPrev)->m_pNext = pCamera->m_pNext;
				delete pCamera;
				pCamera = ((Camera*)pPrev)->m_pNext;
			}
			else
			{
				m_pCameras = m_pCameras->m_pNext;
				delete pCamera;
				pCamera = m_pCameras;
			}
			
			removed = true;

			SystemUpdateCameraMenu(m_pCameras);
			SystemUpdateCurrentCamera(NULL, m_pViewCameras[m_nActiveViewport], m_pCameras);
		}
		else
		{
			pPrev = pCamera;
			pCamera = pCamera->m_pNext;
		}
	}

	for (pPrev = NULL, pLight = m_pLights; pLight; )
	{
		if (pLight->IsSelected())
		{
			if (pPrev)
			{
			  ((Light*)pPrev)->m_pNext = pLight->m_pNext;
				delete pLight;
				pLight = ((Light*)pPrev)->m_pNext;
			}
			else
			{
			  m_pLights = m_pLights->m_pNext;
				delete pLight;
				pLight = m_pLights;
			}

			removed = true;
		}
		else
		{
			pPrev = pLight;
			pLight = pLight->m_pNext;
		}
	}

	RemoveEmptyGroups();
//	CalculateStep();
//	AfxGetMainWnd()->PostMessage(WM_LC_UPDATE_INFO, NULL, OT_PIECE);

	return removed;
}

void Project::UpdateSelection()
{
	unsigned long flags = 0;
	int SelectedCount = 0;
	Object* Focus = NULL;

	if (m_pPieces == NULL)
		flags |= LC_SEL_NO_PIECES;
	else
	{
		Piece* pPiece;
		Group* pGroup = NULL;
		bool first = true;

		for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		{
			if (pPiece->IsSelected())
			{
				SelectedCount++;

				if (pPiece->IsFocused())
					Focus = pPiece;

				if (flags & LC_SEL_PIECE)
					flags |= LC_SEL_MULTIPLE;
				else
					flags |= LC_SEL_PIECE;

				if (pPiece->GetGroup() != NULL)
				{
					flags |= LC_SEL_GROUP;
					if (pPiece->IsFocused())
						flags |= LC_SEL_FOCUSGROUP;
				}

				if (first)
				{
					pGroup = pPiece->GetGroup();
					first = false;
				}
				else
				{
					if (pGroup != pPiece->GetGroup())
						flags |= LC_SEL_CANGROUP;
					else
						if (pGroup == NULL)
							flags |= LC_SEL_CANGROUP;
				}
			}
			else
			{
				flags |= LC_SEL_UNSELECTED;

				if (pPiece->IsHidden())
					flags |= LC_SEL_HIDDEN;
			}
		}
	}

	for (Camera* pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
		if (pCamera->IsSelected())
		{
			flags |= LC_SEL_CAMERA;
			SelectedCount++;

			if (pCamera->IsEyeFocused() || pCamera->IsTargetFocused())
				Focus = pCamera;
		}

	for (Light* pLight = m_pLights; pLight; pLight = pLight->m_pNext)
		if (pLight->IsSelected())
		{
			flags |= LC_SEL_LIGHT;
			SelectedCount++;

			if (pLight->IsEyeFocused() || pLight->IsTargetFocused())
				Focus = pLight;
		}

	if (m_nTracking == LC_TRACK_NONE)
	{
		ActivateOverlay();
	}

	SystemUpdateSelected(flags, SelectedCount, Focus);
}

void Project::CheckAutoSave()
{
	m_nSaveTimer += 5;
	if (m_nAutosave & LC_AUTOSAVE_FLAG)
	{
		int nInterval;
		nInterval = m_nAutosave & ~LC_AUTOSAVE_FLAG;

		if (m_nSaveTimer >= (m_nAutosave*60))
		{
			m_nSaveTimer = 0;
/*
			if (m_strTempFile.IsEmpty())
			{
				char tmpFile[_MAX_PATH], out[_MAX_PATH];
				GetTempPath (_MAX_PATH, out);
				GetTempFileName (out, "~LC", 0, tmpFile);
				DeleteFile (tmpFile);
				if (char *ptr = strchr(tmpFile, '.')) *ptr = 0;
				strcat (tmpFile, ".lcd");
				m_strTempFile = tmpFile;
			}

			CFile file (m_strTempFile, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary);
			m_bUndo = TRUE;
			file.SeekToBegin();
			CArchive ar(&file, CArchive::store);
			Serialize(ar); 
			ar.Close();
			m_bUndo = FALSE;
*/		}
	}
}

void Project::SetViewSize(int cx, int cy)
{
	m_nViewX = cx;
	m_nViewY = (cy != 0) ? cy : 1; // Avoid divide by zero.
}

unsigned char Project::GetLastStep()
{
	unsigned char last = 1;
	for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		last = max(last, pPiece->GetStepShow());

	return last;
}

// Create a series of pictures
void Project::CreateImages (Image* images, int width, int height, unsigned short from, unsigned short to, bool hilite)
{
	int oldx, oldy;
	unsigned short oldtime;
	void* render = Sys_StartMemoryRender (width, height);
	unsigned char* buf = (unsigned char*)malloc (width*height*3);
	oldtime = m_bAnimation ? m_nCurFrame : m_nCurStep;
	oldx = m_nViewX;
	oldy = m_nViewY;
	m_nViewX = width;
	m_nViewY = height;

	if (!hilite)
		SelectAndFocusNone(false);

	RenderInitialize();

//	CCamera* pOld = pDoc->GetActiveCamera();
//	pDoc->m_ViewCameras[pDoc->m_nActiveViewport] = pDoc->GetCamera(CAMERA_MAIN);
	for (int i = from; i <= to; i++)
	{
		if (m_bAnimation)
			m_nCurFrame = i;
		else
			m_nCurStep = i;

		if (hilite)
		{
			for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
                          if ((m_bAnimation && pPiece->GetFrameShow() == i) ||
                              (!m_bAnimation && pPiece->GetStepShow() == i))
                            pPiece->Select (true, false, false);
                          else
                            pPiece->Select (false, false, false);
			}
		}

		CalculateStep();
		Render(true);
    images[i-from].FromOpenGL (width, height);
	}
//	pDoc->m_ViewCameras[pDoc->m_nActiveViewport] = pOld;
	m_nViewX = oldx;
	m_nViewY = oldy;
	if (m_bAnimation)
		m_nCurFrame = oldtime;
	else
		m_nCurStep = (unsigned char)oldtime;
	CalculateStep();
	free (buf);
	Sys_FinishMemoryRender (render);
}

void Project::CreateHTMLPieceList(FILE* f, int nStep, bool bImages, char* ext)
{
	Piece* pPiece;
	int col[LC_MAXCOLORS], ID = 0, c;
	memset (&col, 0, sizeof (col));
	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
	{
		if ((pPiece->GetStepShow() == nStep) || (nStep == 0))
			col[pPiece->GetColor()]++;
	}
	fputs("<br><table border=1><tr><td><center>Piece</center></td>\n",f);

	for (c = 0; c < LC_MAXCOLORS; c++)
	if (col[c])
	{
		col[c] = ID;
		ID++;
		fprintf(f, "<td><center>%s</center></td>\n", colornames[c]);
	}
	ID++;
	fputs("</tr>\n",f);

	PieceInfo* pInfo;
	for (int j = 0; j < lcGetPiecesLibrary()->GetPieceCount (); j++)
	{
		bool Add = false;
		int count[LC_MAXCOLORS];
		memset (&count, 0, sizeof (count));
		pInfo = lcGetPiecesLibrary()->GetPieceInfo (j);

		for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		{
			if ((pPiece->GetPieceInfo() == pInfo) && 
				((pPiece->GetStepShow() == nStep) || (nStep == 0)))
			{
				count [pPiece->GetColor()]++;
				Add = true;
			}
		}

		if (Add)
		{
			if (bImages)
				fprintf(f, "<tr><td><IMG SRC=\"%s%s\" ALT=\"%s\"></td>\n", pInfo->m_strName, ext, pInfo->m_strDescription);
			else
				fprintf(f, "<tr><td>%s</td>\n", pInfo->m_strDescription);

			int curcol = 1;
			for (c = 0; c < LC_MAXCOLORS; c++)
				if (count[c])
				{
					while (curcol != col[c] + 1)
					{
						fputs("<td><center>-</center></td>\n", f);
						curcol++;
					}

					fprintf(f, "<td><center>%d</center></td>\n", count[c]);
					curcol++;
				}

			while (curcol != ID)
			{
				fputs("<td><center>-</center></td>\n", f);
				curcol++;
			}

			fputs("</tr>\n", f);
		}
	}
	fputs("</table>\n<br>", f);
}

// Special notifications.
void Project::HandleNotify(LC_NOTIFY id, unsigned long param)
{
	switch (id)
	{
		case LC_COLOR_CHANGED:
		{
			m_nCurColor = (unsigned char)param;
		} break;

		case LC_CAPTURE_LOST:
		{
			if (m_nTracking != LC_TRACK_NONE)
				StopTracking(false);
		} break;

		// Application is (de)activated
		case LC_ACTIVATE:
		{
			if (param == 0)
				SystemExportClipboard(m_pClipboard[m_nCurClipboard]);
			else
			{
				free(m_pClipboard[m_nCurClipboard]);
				m_pClipboard[m_nCurClipboard] = SystemImportClipboard();
				SystemUpdatePaste(m_pClipboard[m_nCurClipboard] != NULL);
			}
		} break;

		// FIXME: don't change the keys with ChangeKey()
		// FIXME: even if pos == prevpos, the user might want to add a key

		case LC_PIECE_MODIFIED:
		{
			LC_PIECE_MODIFY* mod = (LC_PIECE_MODIFY*)param;
			Piece* pPiece = (Piece*)mod->piece;

			float rot[4];
			Vector3 Pos = pPiece->GetPosition();
			pPiece->GetRotation(rot);
			Matrix mat(rot, Pos);
			mat.ToEulerAngles(rot);

			if (Pos != mod->Position)
				pPiece->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, mod->Position, LC_PK_POSITION);

			if (mod->Rotation[0] != rot[0] || mod->Rotation[1] != rot[1] || mod->Rotation[2] != rot[2])
			{
				mat.FromEulerAngles(mod->Rotation[0], mod->Rotation[1], mod->Rotation[2]);
				mat.ToAxisAngle(rot);
				pPiece->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, rot, LC_PK_ROTATION);
			}

			if (m_bAnimation)
			{
				pPiece->SetFrameShow(mod->from);
				pPiece->SetFrameHide(mod->to);
			}
			else
			{
				pPiece->SetStepShow(mod->from);
				pPiece->SetStepHide(mod->to);
			}

			if (mod->hidden)
				pPiece->Hide();
			else
				pPiece->UnHide();

			pPiece->SetName(mod->name);
			pPiece->SetColor(mod->color);
			pPiece->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
			pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, false, true);

			SetModifiedFlag(true);
			CheckPoint("Modifying");
			ActivateOverlay();
			UpdateAllViews();
		} break;

		case LC_CAMERA_MODIFIED:
		{
			LC_CAMERA_MODIFY* mod = (LC_CAMERA_MODIFY*)param;
			Camera* pCamera = (Camera*)mod->camera;

			if (mod->hidden)
				pCamera->Hide();
			else
				pCamera->UnHide();

			if (pCamera->GetEyePosition() != mod->Eye)
				pCamera->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, mod->Eye, LC_CK_EYE);

			if (pCamera->GetTargetPosition() != mod->Target)
				pCamera->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, mod->Target, LC_CK_TARGET);

			if (pCamera->GetUpVector() != mod->Up)
				pCamera->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, mod->Up, LC_CK_UP);

			pCamera->m_fovy = mod->fovy;
			pCamera->m_zNear = mod->znear;
			pCamera->m_zFar = mod->zfar;
			pCamera->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
			UpdateAllViews ();
		} break;

		case LC_LIGHT_MODIFIED:
		{
		} break;
	}
}

void Project::HandleMessage(int Message, void* Data)
{
	if (Message == LC_MSG_FOCUS_CHANGED)
	{
	}
}

// Handle (almost) all menu/toolbar commands here.
void Project::HandleCommand(LC_COMMANDS id, unsigned long nParam)
{
	switch (id)
	{
		case LC_FILE_NEW:
		{
			if (!SaveModified())
				return;  // leave the original one

			OnNewDocument();
			UpdateAllViews ();
		} break;
		
		case LC_FILE_OPEN:
		{
			char filename[LC_MAXPATH];
			strcpy(filename, m_strModelsPath);

			if (SystemDoDialog(LC_DLG_FILE_OPEN_PROJECT, filename))
			{
				OpenProject(filename);
			}
		} break;
		
		case LC_FILE_MERGE:
		{
			char filename[LC_MAXPATH];
			strcpy(filename, m_strModelsPath);

			if (SystemDoDialog(LC_DLG_FILE_MERGE_PROJECT, filename))
			{
				FileDisk file;
				if (file.Open(filename, "rb"))
				{
//				CWaitCursor wait;
					if (file.GetLength() != 0)
					{
						FileLoad(&file, false, true);
						CheckPoint("Merging");
					}
					file.Close();
				}
			}
		} break;

		case LC_FILE_SAVE:
		{
			DoFileSave();
		} break;

		case LC_FILE_SAVEAS:
		{
			DoSave(NULL, true);
		} break;

		case LC_FILE_PICTURE:
		{
			LC_IMAGEDLG_OPTS opts;
			opts.from = 1;
			opts.to = m_bAnimation ? m_nTotalFrames : GetLastStep();
			opts.multiple = m_bAnimation;
			opts.imopts.background[0] = (unsigned char)(m_fBackground[0]*255);
			opts.imopts.background[1] = (unsigned char)(m_fBackground[1]*255);
			opts.imopts.background[2] = (unsigned char)(m_fBackground[2]*255);

			if (SystemDoDialog(LC_DLG_PICTURE_SAVE, &opts))
			{
				if (m_bAnimation)
					opts.to = min(opts.to, m_nTotalFrames);
				else
					opts.to = min(opts.to, 255);
				opts.from = max(1, opts.from);

				if (opts.multiple)
				{
					if (opts.from > opts.to)
					{
						unsigned short t = opts.from;
						opts.from = opts.to;
						opts.to = t;
					}
				}
				else
					opts.from = opts.to = m_bAnimation ? m_nCurFrame : m_nCurStep;

				Image* images = new Image[opts.to-opts.from+1];
				CreateImages (images, opts.width, opts.height, opts.from, opts.to, false);

				char *ptr, ext[4];
				ptr = strrchr(opts.filename, '.');
				if (ptr != NULL)
				{
					strncpy(ext, ptr+1, 3);
					strlwr(ext);
				}

				if (strcmp(ext, "avi") == 0)
				{
					SaveVideo(opts.filename, images, opts.to-opts.from+1, m_bAnimation ? m_nFPS : 60.0f/opts.imopts.pause);
        }
				else
				{
					for (int i = 0; i <= opts.to-opts.from; i++)
					{
						char filename[LC_MAXPATH];
						if (opts.multiple)
						{
							char* ext = strrchr(opts.filename, '.');
							*ext = 0;
							sprintf(filename, "%s%02d.%s", opts.filename, i+1, ext+1);
              *ext = '.';
						}
						else
							strcpy(filename, opts.filename);

						images[i].FileSave (filename, &opts.imopts);
					}
				}
				delete []images;
			}
		} break;

		case LC_FILE_3DS:
		{
#ifdef LC_WINDOWS
			Export3DStudio();
#endif
		} break;

    case LC_FILE_HTML:
    {
      LC_HTMLDLG_OPTS opts;

      strcpy (opts.path, Sys_ProfileLoadString ("Default", "HTML Path", ""));
      if (strlen (opts.path) == 0)
      {
        strcpy (opts.path, m_strPathName);
        if (strlen(opts.path) > 0)
		  	{
			  	char* ptr = strrchr(opts.path, '/');
				  if (ptr == NULL)
					  ptr = strrchr(opts.path, '\\');
				  if (ptr)
          {
            ptr++;
            *ptr = 0;
          }
  			}
      }

      unsigned long image = Sys_ProfileLoadInt ("Default", "HTML Image Options", 1|LC_IMAGE_TRANSPARENT);
			opts.imdlg.imopts.background[0] = (unsigned char)(m_fBackground[0]*255);
			opts.imdlg.imopts.background[1] = (unsigned char)(m_fBackground[1]*255);
			opts.imdlg.imopts.background[2] = (unsigned char)(m_fBackground[2]*255);
			opts.imdlg.from = 1;
			opts.imdlg.to = 1;
			opts.imdlg.multiple = false;
			opts.imdlg.width = Sys_ProfileLoadInt ("Default", "HTML Image Width", 256);
			opts.imdlg.height = Sys_ProfileLoadInt ("Default", "HTML Image Height", 160);
			opts.imdlg.imopts.quality = Sys_ProfileLoadInt ("Default", "JPEG Quality", 70);
			opts.imdlg.imopts.interlaced = (image & LC_IMAGE_PROGRESSIVE) != 0;
			opts.imdlg.imopts.transparent = (image & LC_IMAGE_TRANSPARENT) != 0;
			opts.imdlg.imopts.truecolor = (image & LC_IMAGE_HIGHCOLOR) != 0;
			opts.imdlg.imopts.pause = 1;
			opts.imdlg.imopts.format = (unsigned char)(image & ~(LC_IMAGE_MASK));

      unsigned long ul = Sys_ProfileLoadInt ("Default", "HTML Options", LC_HTML_SINGLEPAGE);
      opts.singlepage = (ul & LC_HTML_SINGLEPAGE) != 0;
      opts.index = (ul & LC_HTML_INDEX) != 0;
      opts.images = (ul & LC_HTML_IMAGES) != 0;
      opts.listend = (ul & LC_HTML_LISTEND) != 0;
      opts.liststep = (ul & LC_HTML_LISTSTEP) != 0;
      opts.highlight = (ul & LC_HTML_HIGHLIGHT) != 0;
      opts.htmlext = (ul & LC_HTML_HTMLEXT) != 0;

			if (SystemDoDialog(LC_DLG_HTML, &opts))
			{
				FILE* f;
				char *ext, *htmlext, fn[LC_MAXPATH];
				int i;
				unsigned short last = GetLastStep();

        // Save HTML options
        ul = 0;
        if (opts.singlepage) ul |= LC_HTML_SINGLEPAGE;
        if (opts.index) ul |= LC_HTML_INDEX;
        if (opts.images) ul |= LC_HTML_IMAGES;
        if (opts.listend) ul |= LC_HTML_LISTEND;
        if (opts.liststep) ul |= LC_HTML_LISTSTEP;
        if (opts.highlight) ul |= LC_HTML_HIGHLIGHT;
        if (opts.htmlext) ul |= LC_HTML_HTMLEXT;
        Sys_ProfileSaveInt ("Default", "HTML Options", ul);

        // Save image options
        ul = opts.imdlg.imopts.format;
        if (opts.imdlg.imopts.interlaced)
          ul |= LC_IMAGE_PROGRESSIVE;
        if (opts.imdlg.imopts.transparent)
          ul |= LC_IMAGE_TRANSPARENT;
        if (opts.imdlg.imopts.truecolor)
          ul |= LC_IMAGE_HIGHCOLOR;
        Sys_ProfileSaveInt ("Default", "HTML Image Options", ul);
			  Sys_ProfileSaveInt ("Default", "HTML Image Width", opts.imdlg.width);
			  Sys_ProfileSaveInt ("Default", "HTML Image Height", opts.imdlg.height);

				switch (opts.imdlg.imopts.format)
				{
				case LC_IMAGE_BMP: ext = ".bmp"; break;
        default:
				case LC_IMAGE_GIF: ext = ".gif"; break;
				case LC_IMAGE_JPG: ext = ".jpg"; break;
				case LC_IMAGE_PNG: ext = ".png"; break;
				}

        if (opts.htmlext)
          htmlext = ".html";
        else
          htmlext = ".htm";

        i = strlen (opts.path);
        if (i && opts.path[i] != '/' && opts.path[i] != '\\')
          strcat (opts.path, "/");
        Sys_ProfileSaveString ("Default", "HTML Path", opts.path);

/*
				// Create destination folder
        char *MyPath = strdup(dlg.m_strFolder);
        char *p = MyPath;
        int psave;
        while(*p)
        {
          while(*p && *p != '\\')
						++p;

					psave = *p;
					if (*p == '\\')
						*p = 0;

					if (strlen(MyPath) > 3)
						CreateDirectory(MyPath, NULL);

					*p = psave;
					if (*p)
						++p;
				}
				free(MyPath);
*/
        main_window->BeginWait ();

				if (opts.singlepage)
				{
					strcpy (fn, opts.path);
					strcat (fn, m_strTitle);
          strcat (fn, htmlext);
					f = fopen (fn, "wt");
					fprintf (f, "<HTML>\n<HEAD>\n<TITLE>Instructions for %s</TITLE>\n</HEAD>\n<BR>\n<CENTER>\n", m_strTitle);

					for (i = 1; i <= last; i++)
					{
						fprintf(f, "<IMG SRC=\"%s-%02d%s\" ALT=\"Step %02d\" WIDTH=%d HEIGHT=%d><BR><BR>\n", 
							m_strTitle, i, ext, i, opts.imdlg.width, opts.imdlg.height);
	
						if (opts.liststep)
							CreateHTMLPieceList(f, i, opts.images, ext);
					}

					if (opts.listend)
						CreateHTMLPieceList(f, 0, opts.images, ext);

					fputs("</CENTER>\n<BR><HR><BR><B><I>Created by <A HREF=\"http://www.leocad.org\">LeoCAD</A></B></I><BR></HTML>\n", f);
					fclose(f);
				}
				else
				{
					if (opts.index)
					{
						strcpy (fn, opts.path);
						strcat (fn, m_strTitle);
						strcat (fn, "-index");
            strcat (fn, htmlext);
						f = fopen (fn, "wt");

						fprintf(f, "<HTML>\n<HEAD>\n<TITLE>Instructions for %s</TITLE>\n</HEAD>\n<BR>\n<CENTER>\n", m_strTitle);

						for (i = 1; i <= last; i++)
							fprintf(f, "<A HREF=\"%s-%02d%s\">Step %d<BR>\n</A>", m_strTitle, i, htmlext, i);

						if (opts.listend)
							fprintf(f, "<A HREF=\"%s-pieces%s\">Pieces Used</A><BR>\n", m_strTitle, htmlext);

						fputs("</CENTER>\n<BR><HR><BR><B><I>Created by <A HREF=\"http://www.leocad.org\">LeoCAD</A></B></I><BR></HTML>\n", f);
						fclose(f);
					}

					// Create each step
					for (i = 1; i <= last; i++)
					{
						sprintf(fn, "%s%s-%02d%s", opts.path, m_strTitle, i, htmlext);
						f = fopen(fn, "wt");

						fprintf(f, "<HTML>\n<HEAD>\n<TITLE>%s - Step %02d</TITLE>\n</HEAD>\n<BR>\n<CENTER>\n", m_strTitle, i);
						fprintf(f, "<IMG SRC=\"%s-%02d%s\" ALT=\"Step %02d\" WIDTH=%d HEIGHT=%d><BR><BR>\n", 
							m_strTitle, i, ext, i, opts.imdlg.width, opts.imdlg.height);
	
						if (opts.liststep)
							CreateHTMLPieceList(f, i, opts.images, ext);

						fputs("</CENTER>\n<BR><HR><BR>", f);
						if (i != 1)
							fprintf(f, "<A HREF=\"%s-%02d%s\">Previous</A> ", m_strTitle, i-1, htmlext);

						if (opts.index)
							fprintf(f, "<A HREF=\"%s-index%s\">Index</A> ", m_strTitle, htmlext);

						if (i != last)
							fprintf(f, "<A HREF=\"%s-%02d%s\">Next</A>", m_strTitle, i+1, htmlext);
						else
							if (opts.listend)
								fprintf(f, "<A HREF=\"%s-pieces%s\">Pieces Used</A>", m_strTitle, htmlext);

						fputs("<BR></HTML>\n",f);
						fclose(f);
					}

					if (opts.listend)
					{
						strcpy (fn, opts.path);
						strcat (fn, m_strTitle);
						strcat (fn, "-pieces");
            strcat (fn, htmlext);
						f = fopen (fn, "wt");
						fprintf (f, "<HTML>\n<HEAD>\n<TITLE>Pieces used by %s</TITLE>\n</HEAD>\n<BR>\n<CENTER>\n", m_strTitle);
				
						CreateHTMLPieceList(f, 0, opts.images, ext);

						fputs("</CENTER>\n<BR><HR><BR>", f);
						fprintf(f, "<A HREF=\"%s-%02d%s\">Previous</A> ", m_strTitle, i-1, htmlext);

						if (opts.index)
							fprintf(f, "<A HREF=\"%s-index%s\">Index</A> ", m_strTitle, htmlext);

						fputs("<BR></HTML>\n",f);
						fclose(f);
					}
				}

				// Save step pictures
				Image* images = new Image[last];
				CreateImages (images, opts.imdlg.width, opts.imdlg.height, 1, last, opts.highlight);

				for (i = 0; i < last; i++)
				{
					sprintf(fn, "%s%s-%02d%s", opts.path, m_strTitle, i+1, ext);
					images[i].FileSave (fn, &opts.imdlg.imopts);
				}
				delete []images;

				if (opts.images)
				{
					int cx = 120, cy = 100;
					void* render = Sys_StartMemoryRender (cx, cy);

					float aspect = (float)cx/(float)cy;
					glViewport(0, 0, cx, cy);

					Piece *p1, *p2;
					PieceInfo* pInfo;
					for (p1 = m_pPieces; p1; p1 = p1->m_pNext)
					{
						bool bSkip = false;
						pInfo = p1->GetPieceInfo();

						for (p2 = m_pPieces; p2; p2 = p2->m_pNext)
						{
							if (p2 == p1)
								break;

							if (p2->GetPieceInfo() == pInfo)
							{
								bSkip = true;
								break;
							}
						}

						if (bSkip)
							continue;

						glMatrixMode(GL_PROJECTION);
						glLoadIdentity();
						gluPerspective(30.0f, aspect, 1.0f, 50.0f);
						glMatrixMode(GL_MODELVIEW);
						glLoadIdentity();
						glDepthFunc(GL_LEQUAL);
						glClearColor(1,1,1,1); 
						glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
						glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
						glEnable(GL_COLOR_MATERIAL);
						glDisable (GL_DITHER);
						glShadeModel(GL_FLAT);
						pInfo->ZoomExtents(30.0f, aspect);

						float pos[4] = { 0, 0, 10, 0 };
						glLightfv(GL_LIGHT0, GL_POSITION, pos);
						glEnable(GL_LIGHTING);
						glEnable(GL_LIGHT0);
						glEnable(GL_DEPTH_TEST);
						pInfo->RenderPiece(m_nCurColor);
						glFinish();

						Image image;
            image.FromOpenGL (cx, cy);

						sprintf(fn, "%s%s%s", opts.path, pInfo->m_strName, ext);
						image.FileSave (fn, &opts.imdlg.imopts);
					}
					Sys_FinishMemoryRender (render);
				}
        main_window->EndWait ();
			}
		} break;

		// Export to POV-Ray, swap X & Y from our cs to work with LGEO.
		case LC_FILE_POVRAY:
		{
			LC_POVRAYDLG_OPTS opts;
			if (!SystemDoDialog(LC_DLG_POVRAY, &opts))
				break;

//	CWaitCursor wc;
			char fn[LC_MAXPATH], tmp[10], *ptr;
			unsigned long u;
			PieceInfo* pInfo;
			Piece* pPiece;
			FILE* f;
			char *conv = (char*)malloc (9*lcGetPiecesLibrary()->GetPieceCount ());
			char *flags = (char*)malloc (lcGetPiecesLibrary()->GetPieceCount ());
			memset (conv, 0, 9*lcGetPiecesLibrary()->GetPieceCount());
			memset (flags, 0, lcGetPiecesLibrary()->GetPieceCount());

			// read LGEO conversion table
			if (strlen (opts.libpath))
			{
				strcpy (fn, opts.libpath);
				strcat (fn, "l2p_elmt.tab");
				f = fopen(fn, "rb");

				if (f == NULL)
				{
					free (conv);
					free (flags);
					SystemDoMessageBox("Could not find LGEO.", LC_MB_OK|LC_MB_ICONERROR);
					return;
				}

				unsigned char bt[4];
				while (fread (&bt, 4, 1, f))
				{
					u = (((unsigned char)(bt[3])|((unsigned short)(bt[2]) << 8))|
						(((unsigned long)(bt[1])) << 16)) + bt[0] * 16581375;
					sprintf(tmp, "%d", (int)u);
					pInfo = lcGetPiecesLibrary()->FindPieceInfo(tmp);

					fread(&tmp, 9, 1, f);
					if (tmp[8] != 0)
						fread(&tmp[9], 1, 1, f);

					if (pInfo != NULL)
					{
						int idx = lcGetPiecesLibrary()->GetPieceIndex (pInfo);
						memcpy (&conv[idx*9], &tmp[1], 9);
						flags[idx] = tmp[0];
					}
				}
				fclose (f);

				strcpy(fn, opts.libpath);
				strcat(fn, "l2p_ptrn.tab");
				f = fopen(fn, "rb");

				if (f == NULL)
				{
					SystemDoMessageBox("Could not find LGEO.", LC_MB_OK|LC_MB_ICONERROR);
					free (conv);
					free (flags);
					return;
				}

				u = 0;
				do 
				{
					if ((tmp[u] == 0) && (u != 0))
					{
						u = 0;
						pInfo = lcGetPiecesLibrary()->FindPieceInfo(tmp);
						fread(&tmp, 8, 1, f);

						if (pInfo != NULL)
						{
							int idx = lcGetPiecesLibrary()->GetPieceIndex (pInfo);
							memcpy(&conv[idx*9], tmp, 9);
						}
					}
					else
						u++;
				}	
				while (fread(&tmp[u], 1, 1, f));
				fclose(f);
			}

			strcpy(fn, opts.outpath);
			if ((ptr = strrchr (fn, '.')))
				*ptr = 0;
			strcat (fn, ".inc");
			f = fopen(fn, "wt");

			if (!f)
			{
				SystemDoMessageBox("Could not open file for writing.", LC_MB_OK|LC_MB_ICONERROR);
				break;
			}

			fputs("// Stuff that doesn't need to be changed\n\n", f);

			if (strlen(opts.libpath))
				fputs("#include \"lg_color.inc\"\n#include \"lg_defs.inc\"\n\n", f);

			// Add include files
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				Piece* pNext;

				for (pNext = m_pPieces; pNext; pNext = pNext->m_pNext)
				{
					pInfo = pNext->GetPieceInfo();

					if (pNext == pPiece)
					{
						int idx = lcGetPiecesLibrary()->GetPieceIndex (pInfo);
						char pat[] = "patterns/";
						if (conv[idx*9+1] != 'p')
							strcpy(pat, "");

						if (conv[idx*9] != 0)
							fprintf(f, "#include \"%s%s.inc\"\n", pat, &conv[idx*9]);
						break;
					}

					if (pInfo == pPiece->GetPieceInfo())
						break;
				}
			}

			const char* lg_colors[28] = { "red", "Orange", "green", "mint", "blue", "LightBlue", "yellow", 
				"white", "dark_grey", "black", "brown", "pink", "purple", "gold_chrome", "clear_red",
				"clear_neon_orange", "clear_green", "clear_neon_yellow", "clear_blue", "clear_cyan", 
				"clear_yellow", "clear", "grey", "tan", "LightBrown", "rose", "Turquoise", "chrome" };

			const float mycol[4][4] = { { 1.0f, 0.5f, 0.2f, 1 }, { 0.2f, 0.4f, 0.9f, 5 }, 
				{ 0.6f, 0.4f, 0.4f, 24 }, { 0.1f, 0.7f, 0.8f, 26 }};

			if (strlen(opts.libpath))
			{
				for (u = 0; u < 4; u++)
					fprintf(f, "\n#declare lg_%s = texture {\n pigment { rgb <%.2f, %.2f, %.2f> }\n finish {\n  ambient 0.1\n  phong 0.3\n  phong_size 20\n }\n}\n",
						altcolornames[(int)mycol[u][3]], mycol[u][0], mycol[u][1], mycol[u][2]);
			}
			else
			{
				fputs("#include \"colors.inc\"\n\n", f);

				for (u = 0; u < LC_MAXCOLORS; u++)
					fprintf(f, "\n#declare lg_%s = texture {\n pigment { rgbf <%.2f, %.2f, %.2f, %.2f> }\n finish {\n  ambient 0.1\n  phong 0.3\n  phong_size 20\n }\n}\n",
						lg_colors[u], (float)ColorArray[u][0]/255, (float)ColorArray[u][1]/255, (float)ColorArray[u][2]/255, ((ColorArray[u][3] == 255) ? 0.0f : 0.9f));
			}

			// if not in lgeo, create it
			fputs("\n// The next objects (if any) were generated by LeoCAD.\n\n", f);
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				pInfo = pPiece->GetPieceInfo();
				int idx = lcGetPiecesLibrary()->GetPieceIndex (pInfo);
				if (conv[idx*9] != 0)
					continue;

				for (Piece* pNext = m_pPieces; pNext; pNext = pNext->m_pNext)
				{
					if (pNext == pPiece)
					{
						char name[20];
						strcpy(name, pInfo->m_strName);
						while ((ptr = strchr(name, '-')))
							*ptr = '_';
						fprintf(f, "#declare lc_%s = union {\n", name);

						unsigned short g;
						for (g = 0; g < pInfo->m_nGroupCount; g++)
						if (pInfo->m_nFlags & LC_PIECE_LONGDATA)
						{
							unsigned long* info = (unsigned long*)pInfo->m_pGroups[g].drawinfo;
							unsigned long count, curcolor, colors = *info;
							info++;

							while (colors--)
							{
								curcolor = *info;
								info++;

								// skip if color only have lines
								if ((*info == 0) && (info[1] == 0))
								{
									info += 2;
									info += *info + 1;
									continue;
								}

								fputs(" mesh {\n", f);

								for (count = *info, info++; count; count -= 4)
								{
									fprintf(f, "  triangle { <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f> }\n",
										-pInfo->m_fVertexArray[info[0]*3+1], -pInfo->m_fVertexArray[info[0]*3], pInfo->m_fVertexArray[info[0]*3+2],
										-pInfo->m_fVertexArray[info[1]*3+1], -pInfo->m_fVertexArray[info[1]*3], pInfo->m_fVertexArray[info[1]*3+2],
										-pInfo->m_fVertexArray[info[2]*3+1], -pInfo->m_fVertexArray[info[2]*3], pInfo->m_fVertexArray[info[2]*3+2]);
									fprintf(f, "  triangle { <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f> }\n",
										-pInfo->m_fVertexArray[info[2]*3+1], -pInfo->m_fVertexArray[info[2]*3], pInfo->m_fVertexArray[info[2]*3+2],
										-pInfo->m_fVertexArray[info[3]*3+1], -pInfo->m_fVertexArray[info[3]*3], pInfo->m_fVertexArray[info[3]*3+2],
										-pInfo->m_fVertexArray[info[0]*3+1], -pInfo->m_fVertexArray[info[0]*3], pInfo->m_fVertexArray[info[0]*3+2]);
									info += 4;
								}

								for (count = *info, info++; count; count -= 3)
								{
									fprintf(f, "  triangle { <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f> }\n",
										-pInfo->m_fVertexArray[info[0]*3+1], -pInfo->m_fVertexArray[info[0]*3], pInfo->m_fVertexArray[info[0]*3+2],
										-pInfo->m_fVertexArray[info[1]*3+1], -pInfo->m_fVertexArray[info[1]*3], pInfo->m_fVertexArray[info[1]*3+2],
										-pInfo->m_fVertexArray[info[2]*3+1], -pInfo->m_fVertexArray[info[2]*3], pInfo->m_fVertexArray[info[2]*3+2]);
									info += 3;
								}
								info += *info + 1;

								if (curcolor != LC_COL_DEFAULT && curcolor != LC_COL_EDGES)
									fprintf (f, "  texture { lg_%s }\n", lg_colors[curcolor]);
								fputs(" }\n", f);
							}
						}
						else
						{
							unsigned short* info = (unsigned short*)pInfo->m_pGroups[g].drawinfo;
							unsigned short count, curcolor, colors = *info;
							info++;

							while (colors--)
							{
								curcolor = *info;
								info++;

								// skip if color only have lines
								if ((*info == 0) && (info[1] == 0))
								{
									info += 2;
									info += *info + 1;
									continue;
								}

								fputs(" mesh {\n", f);

								for (count = *info, info++; count; count -= 4)
								{
									fprintf(f, "  triangle { <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f> }\n",
										-pInfo->m_fVertexArray[info[0]*3+1], -pInfo->m_fVertexArray[info[0]*3], pInfo->m_fVertexArray[info[0]*3+2],
										-pInfo->m_fVertexArray[info[1]*3+1], -pInfo->m_fVertexArray[info[1]*3], pInfo->m_fVertexArray[info[1]*3+2],
										-pInfo->m_fVertexArray[info[2]*3+1], -pInfo->m_fVertexArray[info[2]*3], pInfo->m_fVertexArray[info[2]*3+2]);
									fprintf(f, "  triangle { <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f> }\n",
										-pInfo->m_fVertexArray[info[2]*3+1], -pInfo->m_fVertexArray[info[2]*3], pInfo->m_fVertexArray[info[2]*3+2],
										-pInfo->m_fVertexArray[info[3]*3+1], -pInfo->m_fVertexArray[info[3]*3], pInfo->m_fVertexArray[info[3]*3+2],
										-pInfo->m_fVertexArray[info[0]*3+1], -pInfo->m_fVertexArray[info[0]*3], pInfo->m_fVertexArray[info[0]*3+2]);
									info += 4;
								}

								for (count = *info, info++; count; count -= 3)
								{
									fprintf(f, "  triangle { <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f>, <%.2f, %.2f, %.2f> }\n",
										-pInfo->m_fVertexArray[info[0]*3+1], -pInfo->m_fVertexArray[info[0]*3], pInfo->m_fVertexArray[info[0]*3+2],
										-pInfo->m_fVertexArray[info[1]*3+1], -pInfo->m_fVertexArray[info[1]*3], pInfo->m_fVertexArray[info[1]*3+2],
										-pInfo->m_fVertexArray[info[2]*3+1], -pInfo->m_fVertexArray[info[2]*3], pInfo->m_fVertexArray[info[2]*3+2]);
									info += 3;
								}
								info += *info + 1;

								if (curcolor != LC_COL_DEFAULT && curcolor != LC_COL_EDGES)
									fprintf (f, "  texture { lg_%s }\n", lg_colors[curcolor]);
								fputs(" }\n", f);
							}
						}

						fputs("}\n\n", f);
						break;
					}

					if (pNext->GetPieceInfo() == pInfo)
						break;
				}
			}

			fclose(f);
			if ((ptr = strrchr (fn, '.')))
				*ptr = 0;
			strcat (fn, ".pov");
			f = fopen(fn, "wt");

			if (!f)
			{
				SystemDoMessageBox("Could not open file for writing.", LC_MB_OK|LC_MB_ICONERROR);
				break;
			}

			if ((ptr = strrchr (fn, '.')))
				*ptr = 0;
			ptr = strrchr (fn, '\\');
			if (!ptr)
				ptr = strrchr (fn, '/');
			if (ptr)
				ptr++;
			else
				ptr = fn;

			fprintf(f, "// File created by LeoCAD\n//\n\n#include \"%s.inc\"\n", ptr);
			float eye[3], target[3], up[3];
			m_pViewCameras[m_nActiveViewport]->GetEyePos (eye);
			m_pViewCameras[m_nActiveViewport]->GetTargetPos (target);
			m_pViewCameras[m_nActiveViewport]->GetUpVec (up);

			fprintf(f, "\ncamera {\n  sky<%1g,%1g,%1g>\n  location <%1g, %1g, %1g>\n  look_at <%1g, %1g, %1g>\n  angle %.0f\n}\n\n",
				up[0], up[1], up[2], eye[1], eye[0], eye[2], target[1], target[0], target[2], m_pViewCameras[m_nActiveViewport]->m_fovy);
			fprintf(f, "background { color rgb <%1g, %1g, %1g> }\n\nlight_source { <0, 0, 20> White shadowless }\n\n",
				m_fBackground[0], m_fBackground[1], m_fBackground[2]);

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				float fl[12], pos[3], rot[4];
				char name[20];
				int idx = lcGetPiecesLibrary()->GetPieceIndex (pPiece->GetPieceInfo ());

				if (conv[idx*9] == 0)
				{
					char* ptr;
					sprintf(name, "lc_%s", pPiece->GetPieceInfo()->m_strName);
					while ((ptr = strchr(name, '-')))
						*ptr = '_';
				}
				else
				{
					strcpy (name, &conv[idx*9]);
					if (pPiece->IsTransparent())
						strcat(name, "_clear");			
				}

				pPiece->GetPosition(pos);
				pPiece->GetRotation(rot);
				Matrix mat(rot, pos);
				mat.ToLDraw(fl);

				// Slope needs to be handled correctly
				if (flags[idx] == 1)
					fprintf (f, "merge {\n object {\n  %s\n  texture { lg_%s }\n }\n"
						 " object {\n  %s_slope\n  texture { lg_%s normal { bumps 0.3 scale 0.02 } }\n }\n"
						 " matrix <%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f>\n}\n",
						name, lg_colors[pPiece->GetColor()], &conv[idx*9], lg_colors[pPiece->GetColor()],
						 -fl[11], -fl[5], fl[8], -fl[9], -fl[3], fl[6],
						 -fl[10], -fl[4], fl[7], pos[1], pos[0], pos[2]);
				else
					fprintf(f, "object {\n %s\n texture { lg_%s }\n matrix <%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f>\n}\n",
						name, lg_colors[pPiece->GetColor()], -fl[11], -fl[5], fl[8], -fl[9], -fl[3], fl[6],
						-fl[10], -fl[4], fl[7], pos[1], pos[0], pos[2]);
			}
			fclose (f);
			free (conv);
			free (flags);

			if (opts.render)
			{
#ifdef LC_WINDOWS
				// TODO: Linux support
				char buf[600];
				char tmp[LC_MAXPATH], out[LC_MAXPATH];
				sprintf(out, "%s.pov", fn);
				GetShortPathName(out, out, LC_MAXPATH);
				GetShortPathName(opts.libpath, tmp, LC_MAXPATH);
				sprintf (buf, "+L%s +I%s", tmp, out);
				ptr = strrchr (out, '\\');
				if (ptr)
					*(ptr+1) = 0;
				ShellExecute(::GetDesktopWindow(), "open", opts.povpath, buf, out, SW_SHOWNORMAL);
#endif
			}
		} break;

		case LC_FILE_WAVEFRONT:
		{
			char filename[LC_MAXPATH];
			if (!SystemDoDialog(LC_DLG_WAVEFRONT, filename))
				break;

			char buf[LC_MAXPATH], *ptr;
			FILE* stream = fopen(filename, "wt");
			unsigned long vert = 1, i;
			Piece* pPiece;

			strcpy(buf, m_strPathName);
			ptr = strrchr(buf, '\\');
			if (ptr)
				ptr++;
			else
			{
				ptr = strrchr(buf, '/');
				if (ptr)
					ptr++;
				else
					ptr = buf;
			}
			fputs("# Model exported from LeoCAD\n", stream);
			if (strlen(buf) != 0)
				fprintf(stream,"# Original name: %s\n", ptr);
			if (strlen(m_strAuthor))
				fprintf(stream, "# Author: %s\n", m_strAuthor);

			strcpy(buf, filename);
			ptr = strrchr(buf, '.');
			if (ptr)
				*ptr = 0;

			strcat(buf, ".mtl");
			ptr = strrchr(buf, '\\');
			if (ptr)
				ptr++;
			else
			{
				ptr = strrchr(buf, '/');
				if (ptr)
					ptr++;
				else
					ptr = buf;
			}

			fprintf(stream, "#\n\nmtllib %s\n\n", ptr);

			FILE* mat = fopen(buf, "wt");
			fputs("# Colors used by LeoCAD\n# You need to add transparency values\n#\n\n", mat);
			for (i = 0; i < LC_MAXCOLORS; i++)
				fprintf(mat, "newmtl %s\nKd %.2f %.2f %.2f\n\n", altcolornames[i], (float)FlatColorArray[i][0]/255, (float)FlatColorArray[i][1]/255, (float)FlatColorArray[i][2]/255);
			fclose(mat);

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				float pos[3], rot[4], tmp[3];
				pPiece->GetPosition(pos);
				pPiece->GetRotation(rot);
				Matrix mat(rot, pos);
				PieceInfo* pInfo = pPiece->GetPieceInfo();

				for (i = 0; i < pInfo->m_nVertexCount*3; i += 3)
				{
					mat.TransformPoint(tmp, &pInfo->m_fVertexArray[i]);
					fprintf(stream, "v %.2f %.2f %.2f\n", tmp[0], tmp[1], tmp[2]);
				}
				fputs("#\n\n", stream);
			}

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				strcpy(buf, pPiece->GetName());
				for (i = 0; i < strlen(buf); i++)
					if ((buf[i] == '#') || (buf[i] == ' '))
						buf[i] = '_';

				fprintf(stream, "g %s\n", buf);
				pPiece->GetPieceInfo()->WriteWavefront(stream, pPiece->GetColor(), &vert);
			}

			fclose(stream);
		} break;

		case LC_FILE_PROPERTIES:
		{
			LC_PROPERTIESDLG_OPTS opts;

			opts.strTitle = m_strTitle;
			strcpy(opts.strAuthor, m_strAuthor);
			strcpy(opts.strDescription, m_strDescription);
			strcpy(opts.strComments, m_strComments);
			opts.strFilename = m_strPathName;

			opts.lines = lcGetPiecesLibrary()->GetPieceCount();
			opts.count = (unsigned short*)malloc(lcGetPiecesLibrary()->GetPieceCount()*LC_MAXCOLORS*sizeof(unsigned short));
			memset (opts.count, 0, lcGetPiecesLibrary()->GetPieceCount()*LC_MAXCOLORS*sizeof(unsigned short));
			opts.names = (char**)malloc(lcGetPiecesLibrary()->GetPieceCount()*sizeof(char*));
			for (int i = 0; i < lcGetPiecesLibrary()->GetPieceCount(); i++)
				opts.names[i] = lcGetPiecesLibrary()->GetPieceInfo (i)->m_strDescription;

			for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				int idx = lcGetPiecesLibrary()->GetPieceIndex (pPiece->GetPieceInfo ());
				opts.count[idx*LC_MAXCOLORS+pPiece->GetColor()]++;
			}

			if (SystemDoDialog(LC_DLG_PROPERTIES, &opts))
			{
				if (strcmp(m_strAuthor, opts.strAuthor) ||
					strcmp(m_strDescription, opts.strDescription) ||
					strcmp(m_strComments, opts.strComments))
				{
					strcpy(m_strAuthor, opts.strAuthor);
					strcpy(m_strDescription, opts.strDescription);
					strcpy(m_strComments, opts.strComments);
					SetModifiedFlag(true);
				}
			}

			free(opts.count);
			free(opts.names);
		} break;

		case LC_FILE_TERRAIN:
		{
			Terrain* temp = new Terrain();
			*temp = *m_pTerrain;

			if (SystemDoDialog(LC_DLG_TERRAIN, temp))
			{
				*m_pTerrain = *temp;
				m_pTerrain->LoadTexture((m_nDetail & LC_DET_LINEAR) != 0);
			}
			delete temp;
		} break;

		case LC_FILE_LIBRARY:
		{
			FileMem file;
			FileSave(&file, true);

			if (SystemDoDialog(LC_DLG_LIBRARY, NULL))
			{
				if (lcGetPiecesLibrary()->m_Modified)
				{
					DeleteContents(true);
					FileLoad(&file, true, false);
				}
			}
		} break;

		case LC_FILE_RECENT:
		{
			OpenProject(main_window->GetMRU(nParam));
		} break;

		case LC_EDIT_UNDO:
		case LC_EDIT_REDO:
		{
			LC_UNDOINFO *pUndo, *pTmp;
			int i;

			if (id == LC_EDIT_UNDO)
			{
				if ((m_pUndoList != NULL) && (m_pUndoList->pNext != NULL))
				{
					// Remove the first item from the undo list.
					pUndo = m_pUndoList;
					m_pUndoList = pUndo->pNext;

					// Check if we need to delete the last redo info.
					for (pTmp = m_pRedoList, i = 0; pTmp; pTmp = pTmp->pNext, i++)
						if ((i == 29) && (pTmp->pNext != NULL))
						{
							delete pTmp->pNext;
							pTmp->pNext = NULL;
						}

					pUndo->pNext = m_pRedoList;
					m_pRedoList = pUndo;
						
					pUndo = m_pUndoList;
					DeleteContents(true);
					FileLoad(&pUndo->file, true, false);
				}
				
				if (m_bUndoOriginal && (m_pUndoList != NULL) && (m_pUndoList->pNext == NULL))
					SetModifiedFlag(false);
			}
			else
			{
				if (m_pRedoList != NULL)
				{
					// Remove the first element from the redo list.
					pUndo = m_pRedoList;
					m_pRedoList = pUndo->pNext;
					
					// Check if we can delete the last undo info.
					for (pTmp = m_pUndoList, i = 0; pTmp; pTmp = pTmp->pNext, i++)
						if ((i == 30) && (pTmp->pNext != NULL))
						{
							delete pTmp->pNext;
							pTmp->pNext = NULL;
						}

					// Add info to the start of the undo list.
					pUndo->pNext = m_pUndoList;
					m_pUndoList = pUndo;

					// Load state.
					DeleteContents(true);
					FileLoad(&pUndo->file, true, false);
				}
			}

			SystemUpdateUndoRedo(m_pUndoList->pNext ? m_pUndoList->strText : NULL, m_pRedoList ? m_pRedoList->strText : NULL);
		} break;

		case LC_EDIT_CUT:
		case LC_EDIT_COPY:
		{
			if (IsDrawing())
				return;

			if (m_pClipboard[m_nCurClipboard] != NULL)
				delete m_pClipboard[m_nCurClipboard];
			m_pClipboard[m_nCurClipboard] = new FileMem;

			int i = 0;
			Piece* pPiece;
			Camera* pCamera;
			Group* pGroup;
//			Light* pLight;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
					i++;
			m_pClipboard[m_nCurClipboard]->Write(&i, sizeof(i));

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
					pPiece->FileSave(*m_pClipboard[m_nCurClipboard], m_pGroups);

			for (i = 0, pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
				i++;
			m_pClipboard[m_nCurClipboard]->Write(&i, sizeof(i));

			for (pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
				pGroup->FileSave(m_pClipboard[m_nCurClipboard], m_pGroups);

			for (i = 0, pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
				if (pCamera->IsSelected())
					i++;
			m_pClipboard[m_nCurClipboard]->Write(&i, sizeof(i));

			for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
				if (pCamera->IsSelected())
					pCamera->FileSave(*m_pClipboard[m_nCurClipboard]);
/*
			for (i = 0, pLight = m_pLights; pLight; pLight = pLight->m_pNext)
				if (pLight->IsSelected())
					i++;
			m_pClipboard[m_nCurClipboard]->Write(&i, sizeof(i));

			for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
				if (pLight->IsSelected())
					pLight->FileSave(m_pClipboard[m_nCurClipboard]);
*/
			if (id == LC_EDIT_CUT)
			{
				RemoveSelectedObjects();
				SystemUpdateFocus(NULL);
				UpdateSelection();
				UpdateAllViews ();
				SetModifiedFlag(true);
				CheckPoint("Cutting");
			}
			SystemExportClipboard(m_pClipboard[m_nCurClipboard]);
			SystemUpdatePaste(true);
		} break;

		case LC_EDIT_PASTE:
		{
			if (IsDrawing())
				return;

			int i, j;
			Piece* pPasted = NULL;
			File* file = m_pClipboard[m_nCurClipboard];
			if (file == NULL)
				break;
			file->Seek(0, SEEK_SET);
			SelectAndFocusNone(false);

			file->Read(&i, sizeof(i));
			while (i--)
			{
				char name[9];
				Piece* pPiece = new Piece(NULL);
				pPiece->FileLoad(*file, name);
				PieceInfo* pInfo = lcGetPiecesLibrary()->FindPieceInfo(name);
				if (pInfo)
				{
					pPiece->SetPieceInfo(pInfo);
					pPiece->m_pNext = pPasted;
					pPasted = pPiece;
				}
				else 
					delete pPiece;
			}

			file->Read(&i, sizeof(i));
			Piece* pPiece;
			Group** groups = (Group**)malloc(i*sizeof(Group**));
			for (j = 0; j < i; j++)
			{
				groups[j] = new Group();
				groups[j]->FileLoad(file);
			}

			while (pPasted)
			{
				pPiece = pPasted;
				pPasted = pPasted->m_pNext;
				pPiece->CreateName(m_pPieces);
				pPiece->SetFrameShow(m_nCurFrame);
				pPiece->SetStepShow(m_nCurStep);
				AddPiece(pPiece);
				pPiece->Select(true, false, false);

				j = LC_POINTER_TO_INT(pPiece->GetGroup());
				if (j != -1)
					pPiece->SetGroup(groups[j]);
				else
					pPiece->UnGroup(NULL);
			}

			for (j = 0; j < i; j++)
			{
				int g = LC_POINTER_TO_INT(groups[j]->m_pGroup);
				groups[j]->m_pGroup = (g != -1) ? groups[g] : NULL;
			}

			for (j = 0; j < i; j++)
			{
				Group* pGroup;
				bool add = false;
				for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				{
					for (pGroup = pPiece->GetGroup(); pGroup; pGroup = pGroup->m_pGroup)
						if (pGroup == groups[j])
						{
							add = true;
							break;
						}

					if (add)
						break;
				}

				if (add)
				{
					int a, max = 0;

					for (pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
						if (strncmp("Pasted Group #", pGroup->m_strName, 14) == 0)
							if (sscanf(pGroup->m_strName + 14, "%d", &a) == 1)
								if (a > max) 
									max = a;

					sprintf(groups[j]->m_strName, "Pasted Group #%.2d", max+1);
					groups[j]->m_pNext = m_pGroups;
					m_pGroups = groups[j];
				}
				else
					delete groups[j];
			}
			
			free(groups);

			Camera* pCamera = m_pCameras;
			while (pCamera->m_pNext)
				pCamera = pCamera->m_pNext;
			file->Read(&i, sizeof(i));

			while (i--)
			{
				pCamera = new Camera(8, pCamera);
				pCamera->FileLoad(*file);
				pCamera->Select(true, false, false);
				pCamera->GetTarget ()->Select(true, false, false);
			}

			// TODO: lights
			CalculateStep();
			SetModifiedFlag(true);
			CheckPoint("Pasting");
			SystemUpdateFocus(NULL);
			UpdateSelection();
			UpdateAllViews ();
		} break;

		case LC_EDIT_SELECT_ALL:
		{
			Piece* pPiece;
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
					pPiece->Select(true, false, false);

//	pFrame->UpdateInfo();
			UpdateSelection();
			UpdateAllViews ();
		} break;
		
		case LC_EDIT_SELECT_NONE:
		{
			SelectAndFocusNone(false);
                        messenger->Dispatch (LC_MSG_FOCUS_CHANGED, NULL);
			UpdateSelection();
			UpdateAllViews();
		} break;
		
		case LC_EDIT_SELECT_INVERT:
		{
			Piece* pPiece;
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
				{
                                  if (pPiece->IsSelected())
                                    pPiece->Select(false, false, false);
                                  else
                                    pPiece->Select(true, false, false);
				}

                        messenger->Dispatch (LC_MSG_FOCUS_CHANGED, NULL);
			UpdateSelection();
			UpdateAllViews();
		} break;

		case LC_EDIT_SELECT_BYNAME:
		{
			Piece* pPiece;
			Camera* pCamera;
			Light* pLight;
			Group* pGroup;
			int i = 0;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
					i++;

			for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
				if (pCamera != m_pViewCameras[m_nActiveViewport])
					if (pCamera->IsVisible())
						i++;

			for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
				if (pLight->IsVisible())
					i++;

			// TODO: add only groups with visible pieces
			for (pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
				i++;

			if (i == 0)
			{
				// TODO: say 'Nothing to select'
				break;
			}

			LC_SEL_DATA* opts = (LC_SEL_DATA*)malloc((i+1)*sizeof(LC_SEL_DATA));
			i = 0;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
				{
					opts[i].name = pPiece->GetName();
					opts[i].type = LC_SELDLG_PIECE;
					opts[i].selected = pPiece->IsSelected();
					opts[i].pointer = pPiece;
					i++;
				}

			for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
				if (pCamera != m_pViewCameras[m_nActiveViewport])
					if (pCamera->IsVisible())
					{
						opts[i].name = pCamera->GetName();
						opts[i].type = LC_SELDLG_CAMERA;
						opts[i].selected = pCamera->IsSelected();
						opts[i].pointer = pCamera;
						i++;
					}

			for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
				if (pLight->IsVisible())
				{
					opts[i].name = pLight->GetName();
					opts[i].type = LC_SELDLG_LIGHT;
					opts[i].selected = pLight->IsSelected();
					opts[i].pointer = pLight;
					i++;
				}

			// TODO: add only groups with visible pieces
			for (pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
			{
				opts[i].name = pGroup->m_strName;
				opts[i].type = LC_SELDLG_GROUP;
				// TODO: check if selected
				opts[i].selected = false;
				opts[i].pointer = pGroup;
				i++;
			}
			opts[i].pointer = NULL;

			if (SystemDoDialog(LC_DLG_SELECTBYNAME, opts))
			{
				SelectAndFocusNone(false);

				for (i = 0; opts[i].pointer != NULL; i++)
				{
					if (!opts[i].selected)
						continue;

					switch (opts[i].type)
					{
						case LC_SELDLG_PIECE:
						{
							((Piece*)opts[i].pointer)->Select(true, false, false);
						} break;

						case LC_SELDLG_CAMERA:
						{
							((Camera*)opts[i].pointer)->Select(true, false, false);
						} break;

						case LC_SELDLG_LIGHT:
						{
							((Light*)opts[i].pointer)->Select(true, false, false);
						} break;

						case LC_SELDLG_GROUP:
						{
							pGroup = (Group*)opts[i].pointer;
							pGroup = pGroup->GetTopGroup();
							for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
								if (pPiece->GetTopGroup() == pGroup)
									pPiece->Select(true, false, false);
						} break;
					}
				}

				UpdateSelection();
				UpdateAllViews();
//	pFrame->UpdateInfo();
			}

			free(opts);
		} break;

		case LC_PIECE_INSERT:
		{
			if (m_pCurPiece == NULL)
				break;
			Piece* pLast = NULL;
			Piece* pPiece = new Piece(m_pCurPiece);

			for (pLast = m_pPieces; pLast; pLast = pLast->m_pNext)
				if ((pLast->IsFocused()) || (pLast->m_pNext == NULL))
					break;

			if (pLast != NULL)
			{
				Vector3 Pos;
				Vector4 Rot;

				GetPieceInsertPosition(pLast, Pos, Rot);

				pPiece->Initialize(Pos[0], Pos[1], Pos[2], m_nCurStep, m_nCurFrame, m_nCurColor);

				pPiece->ChangeKey(m_nCurStep, false, false, Rot, LC_PK_ROTATION);
				pPiece->ChangeKey(m_nCurFrame, true, false, Rot, LC_PK_ROTATION);
				pPiece->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
			}
			else
				pPiece->Initialize(0, 0, 0, m_nCurStep, m_nCurFrame, m_nCurColor);

			SelectAndFocusNone(false);
			pPiece->CreateName(m_pPieces);
			AddPiece(pPiece);
			pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, true, true);
			pPiece->Select (true, true, false);
			messenger->Dispatch (LC_MSG_FOCUS_CHANGED, pPiece);
			UpdateSelection();
			SystemPieceComboAdd(m_pCurPiece->m_strDescription);

			if (m_nSnap & LC_DRAW_MOVE)
				SetAction(LC_ACTION_MOVE);

//			AfxGetMainWnd()->PostMessage(WM_LC_UPDATE_INFO, (WPARAM)pNew, OT_PIECE);
			UpdateAllViews();
			SetModifiedFlag(true);
			CheckPoint("Inserting");
		} break;

		case LC_PIECE_DELETE:
		{
			if (RemoveSelectedObjects())
			{
                          messenger->Dispatch (LC_MSG_FOCUS_CHANGED, NULL);
				UpdateSelection();
				UpdateAllViews();
				SetModifiedFlag(true);
				CheckPoint("Deleting");
			}
		} break;

		case LC_PIECE_MINIFIG:
		{
		  MinifigWizard *wiz = new MinifigWizard (m_ViewList[0]);
		  int i;

      wiz->IncRef ();

      if (SystemDoDialog (LC_DLG_MINIFIG, wiz))
		  {
		    SelectAndFocusNone(false);

		    for (i = 0; i < LC_MFW_NUMITEMS; i++)
		    {
		      if (wiz->m_Info[i] == NULL)
            continue;

		      Matrix mat;
		      Piece* pPiece = new Piece(wiz->m_Info[i]);

		      pPiece->Initialize(wiz->m_Position[i][0], wiz->m_Position[i][1], wiz->m_Position[i][2],
					 m_nCurStep, m_nCurFrame, wiz->m_Colors[i]);
		      pPiece->CreateName(m_pPieces);
		      AddPiece(pPiece);
		      pPiece->Select(true, false, false);

		      pPiece->ChangeKey(1, false, false, wiz->m_Rotation[i], LC_PK_ROTATION);
		      pPiece->ChangeKey(1, true, false, wiz->m_Rotation[i], LC_PK_ROTATION);
		      pPiece->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
		      pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep,
						   m_bAnimation, false, true);

		      SystemPieceComboAdd(wiz->m_Info[i]->m_strDescription);
		    }

				float bs[6] = { 10000, 10000, 10000, -10000, -10000, -10000 };
				int max = 0;

				Group* pGroup;
				for (pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
					if (strncmp (pGroup->m_strName, "Minifig #", 9) == 0)
						if (sscanf(pGroup->m_strName, "Minifig #%d", &i) == 1)
							if (i > max)
								max = i;
				pGroup = new Group;
				sprintf(pGroup->m_strName, "Minifig #%.2d", max+1);

				pGroup->m_pNext = m_pGroups;
				m_pGroups = pGroup;

				for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
					if (pPiece->IsSelected())
					{
						pPiece->SetGroup(pGroup);
						pPiece->CompareBoundingBox(bs);
					}

				pGroup->m_fCenter[0] = (bs[0]+bs[3])/2;
				pGroup->m_fCenter[1] = (bs[1]+bs[4])/2;
				pGroup->m_fCenter[2] = (bs[2]+bs[5])/2;

        messenger->Dispatch (LC_MSG_FOCUS_CHANGED, NULL);
				UpdateSelection();
				UpdateAllViews();
				SetModifiedFlag(true);
				CheckPoint("Minifig");
			}

			for (i = 0; i < LC_MFW_NUMITEMS; i++)
			  if (wiz->m_Info[i])
			    wiz->m_Info[i]->DeRef();

      wiz->DecRef ();
		} break;

		case LC_PIECE_ARRAY:
		{
			LC_ARRAYDLG_OPTS opts;

			if (SystemDoDialog(LC_DLG_ARRAY, &opts))
			{
				int total;
				
				total = opts.n1DCount;
				if (opts.nArrayDimension > 0)
					total *= opts.n2DCount;
				if (opts.nArrayDimension > 1)
					total *= opts.n3DCount;

				if (total < 2)
				{
					SystemDoMessageBox("Array only have 1 element.", LC_MB_OK|LC_MB_ICONWARNING);
					break;
				}

				if ((m_nSnap & LC_DRAW_CM_UNITS) == 0)
				{
					opts.f2D[0] *= 0.08f;
					opts.f2D[1] *= 0.08f;
					opts.f2D[2] *= 0.08f;
					opts.f3D[0] *= 0.08f;
					opts.f3D[1] *= 0.08f;
					opts.f3D[2] *= 0.08f;
					opts.fMove[0] *= 0.08f;
					opts.fMove[1] *= 0.08f;
					opts.fMove[2] *= 0.08f;
				}

				Piece *pPiece, *pFirst = NULL, *pLast = NULL;
				float bs[6] = { 10000, 10000, 10000, -10000, -10000, -10000 };
				int sel = 0;
				unsigned long i, j, k;

				for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
					if (pPiece->IsSelected())
					{
						pPiece->CompareBoundingBox(bs);
						sel++;
					}

				for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				{
					if (!pPiece->IsSelected())
						continue;

					for (i = 0; i < opts.n1DCount; i++)
					{
						float pos[3], param[4];
						pPiece->GetRotation(param);
						pPiece->GetPosition(pos);
						Matrix mat(param, pos);

						if (sel == 1)
						{
							mat.GetTranslation(pos);
							mat.Rotate(i*opts.fRotate[0], 1, 0, 0);
							mat.Rotate(i*opts.fRotate[1], 0, 1, 0);
							mat.Rotate(i*opts.fRotate[2], 0, 0, 1);
							mat.SetTranslation(pos);
						}
						else
						{
							mat.RotateCenter(i*opts.fRotate[0],1,0,0,(bs[0]+bs[3])/2,(bs[1]+bs[4])/2,(bs[2]+bs[5])/2);
							mat.RotateCenter(i*opts.fRotate[1],0,1,0,(bs[0]+bs[3])/2,(bs[1]+bs[4])/2,(bs[2]+bs[5])/2);
							mat.RotateCenter(i*opts.fRotate[2],0,0,1,(bs[0]+bs[3])/2,(bs[1]+bs[4])/2,(bs[2]+bs[5])/2);
						}
						mat.ToAxisAngle(param);
						mat.GetTranslation(pos);

						if (i != 0)
						{
							if (pLast)
							{
								pLast->m_pNext = new Piece(pPiece->GetPieceInfo());
								pLast = pLast->m_pNext;
							}
							else
								pLast = pFirst = new Piece(pPiece->GetPieceInfo());

							pLast->Initialize(pos[0]+i*opts.fMove[0], pos[1]+i*opts.fMove[1], pos[2]+i*opts.fMove[2], 
								m_nCurStep, m_nCurFrame, pPiece->GetColor());
							pLast->ChangeKey(1, false, false, param, LC_PK_ROTATION);
							pLast->ChangeKey(1, true, false, param, LC_PK_ROTATION);
						}

						if (opts.nArrayDimension == 0)
							continue;
						
						for (j = 0; j < opts.n2DCount; j++)
						{
							if (j != 0)
							{
								if (pLast)
								{
									pLast->m_pNext = new Piece(pPiece->GetPieceInfo());
									pLast = pLast->m_pNext;
								}
								else
									pLast = pFirst = new Piece(pPiece->GetPieceInfo());

								pLast->Initialize(pos[0]+i*opts.fMove[0]+j*opts.f2D[0], pos[1]+i*opts.fMove[1]+j*opts.f2D[1], pos[2]+i*opts.fMove[2]+j*opts.f2D[2],
									m_nCurStep, m_nCurFrame, pPiece->GetColor());
								pLast->ChangeKey(1, false, false, param, LC_PK_ROTATION);
								pLast->ChangeKey(1, true, false, param, LC_PK_ROTATION);
							}

							if (opts.nArrayDimension == 1)
								continue;

							for (k = 1; k < opts.n3DCount; k++)
							{
								if (pLast)
								{
									pLast->m_pNext = new Piece(pPiece->GetPieceInfo());
									pLast = pLast->m_pNext;
								}
								else
									pLast = pFirst = new Piece(pPiece->GetPieceInfo());

								pLast->Initialize(pos[0]+i*opts.fMove[0]+j*opts.f2D[0]+k*opts.f3D[0], pos[1]+i*opts.fMove[1]+j*opts.f2D[1]+k*opts.f3D[1], pos[2]+i*opts.fMove[2]+j*opts.f2D[2]+k*opts.f3D[2],
									m_nCurStep, m_nCurFrame, pPiece->GetColor());
								pLast->ChangeKey(1, false, false, param, LC_PK_ROTATION);
								pLast->ChangeKey(1, true, false, param, LC_PK_ROTATION);
							}
						}
					}
				}

				while (pFirst)
				{
					pPiece = pFirst->m_pNext;
					pFirst->CreateName(m_pPieces);
					pFirst->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
					AddPiece(pFirst);
					pFirst->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, false, true);
					pFirst = pPiece;
				}

				SelectAndFocusNone(true);
//				SystemUpdateFocus(NULL);
				UpdateSelection();
				UpdateAllViews();
				SetModifiedFlag(true);
				CheckPoint("Array");
			}
		} break;

		case LC_PIECE_COPYKEYS:
		{
			float move[3], rot[4];
			Piece* pPiece;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
				{
					pPiece->CalculateSingleKey (m_bAnimation ? m_nCurStep : m_nCurFrame, !m_bAnimation, LC_PK_POSITION, move);
					pPiece->CalculateSingleKey (m_bAnimation ? m_nCurStep : m_nCurFrame, !m_bAnimation, LC_PK_ROTATION, rot);
					pPiece->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, move, LC_PK_POSITION);
					pPiece->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, rot, LC_PK_ROTATION);
				}

			// TODO: cameras and lights

			CalculateStep();
			UpdateAllViews();
		} break;

		case LC_PIECE_GROUP:
		{
			Group* pGroup;
			int i, max = 0;
			char name[65];

			for (pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
				if (strncmp (pGroup->m_strName, "Group #", 7) == 0)
					if (sscanf(pGroup->m_strName, "Group #%d", &i) == 1)
						if (i > max)
							max = i;
			sprintf(name, "Group #%.2d", max+1);

			if (SystemDoDialog(LC_DLG_GROUP, name))
			{
				pGroup = new Group();
				strcpy(pGroup->m_strName, name);
				pGroup->m_pNext = m_pGroups;
				m_pGroups = pGroup;
				float bs[6] = { 10000, 10000, 10000, -10000, -10000, -10000 };

				for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
					if (pPiece->IsSelected())
					{
						pPiece->DoGroup(pGroup);
						pPiece->CompareBoundingBox(bs);
					}
	
				pGroup->m_fCenter[0] = (bs[0]+bs[3])/2;
				pGroup->m_fCenter[1] = (bs[1]+bs[4])/2;
				pGroup->m_fCenter[2] = (bs[2]+bs[5])/2;

				RemoveEmptyGroups();
				SetModifiedFlag(true);
				CheckPoint("Grouping");
			}
		} break;

		case LC_PIECE_UNGROUP:
		{
			Group* pList = NULL;
			Group* pGroup;
			Group* tmp;
			Piece* pPiece;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
				{
					pGroup = pPiece->GetTopGroup();

					// Check if we already removed the group
					for (tmp = pList; tmp; tmp = tmp->m_pNext)
						if (pGroup == tmp)
							pGroup = NULL;

					if (pGroup != NULL)
					{
						// First remove the group from the array
						for (tmp = m_pGroups; tmp->m_pNext; tmp = tmp->m_pNext)
							if (tmp->m_pNext == pGroup)
							{
								tmp->m_pNext = pGroup->m_pNext;
								break;
							}

						if (pGroup == m_pGroups)
							m_pGroups = pGroup->m_pNext;

						// Now add it to the list of top groups
						pGroup->m_pNext = pList;
						pList = pGroup;
					}
				}

			while (pList)
			{
				for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
					if (pPiece->IsSelected())
						pPiece->UnGroup(pList);

				pGroup = pList;
				pList = pList->m_pNext;
				delete pGroup;
			}

			RemoveEmptyGroups();
			SetModifiedFlag(true);
			CheckPoint("Ungrouping");
		} break;

		case LC_PIECE_GROUP_ADD:
		{
			Group* pGroup = NULL;
			Piece* pPiece;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
				{
					pGroup = pPiece->GetTopGroup();
					if (pGroup != NULL)
						break;
				}

			if (pGroup != NULL)
			{
				for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
					if (pPiece->IsFocused())
					{
						pPiece->SetGroup(pGroup);
						break;
					}
			}

			RemoveEmptyGroups();
			SetModifiedFlag(true);
			CheckPoint("Grouping");
		} break;

		case LC_PIECE_GROUP_REMOVE:
		{
			Piece* pPiece;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsFocused())
				{
					pPiece->UnGroup(NULL);
					break;
				}

			RemoveEmptyGroups();
			SetModifiedFlag(true);
			CheckPoint("Ungrouping");
		} break;

		case LC_PIECE_GROUP_EDIT:
		{
			int i;
			Group* pGroup;
			Piece* pPiece;
			LC_GROUPEDITDLG_OPTS opts;

			for (i = 0, pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				i++;
			opts.piececount = i;
			opts.pieces = (Piece**)malloc(i*sizeof(Piece*));
			opts.piecesgroups = (Group**)malloc(i*sizeof(Group*));

			for (i = 0, pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext, i++)
			{
				opts.pieces[i] = pPiece;
				opts.piecesgroups[i] = pPiece->GetGroup();
			}

			for (i = 0, pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
				i++;
			opts.groupcount = i;
			opts.groups = (Group**)malloc(i*sizeof(Group*));
			opts.groupsgroups = (Group**)malloc(i*sizeof(Group*));

			for (i = 0, pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext, i++)
			{
				opts.groups[i] = pGroup;
				opts.groupsgroups[i] = pGroup->m_pGroup;
			}

			if (SystemDoDialog(LC_DLG_EDITGROUPS, &opts))
			{
				for (i = 0; i < opts.piececount; i++)
					opts.pieces[i]->SetGroup(opts.piecesgroups[i]);

				for (i = 0; i < opts.groupcount; i++)
					opts.groups[i]->m_pGroup = opts.groupsgroups[i];

				RemoveEmptyGroups();
				SelectAndFocusNone(false);
				SystemUpdateFocus(NULL);
				UpdateSelection();
				UpdateAllViews();
				SetModifiedFlag(true);
				CheckPoint("Editing");
			}

			free(opts.pieces);
			free(opts.piecesgroups);
			free(opts.groups);
			free(opts.groupsgroups);
		} break;

		case LC_PIECE_HIDE_SELECTED:
		{
			Piece* pPiece;
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
					pPiece->Hide();
			UpdateSelection();
			messenger->Dispatch (LC_MSG_FOCUS_CHANGED, NULL);
			UpdateAllViews();
		} break;

		case LC_PIECE_HIDE_UNSELECTED:
		{
			Piece* pPiece;
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (!pPiece->IsSelected())
					pPiece->Hide();
			UpdateSelection();
			UpdateAllViews();
		} break;

		case LC_PIECE_UNHIDE_ALL:
		{
			Piece* pPiece;
			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				pPiece->UnHide();
			UpdateSelection();
			UpdateAllViews();
		} break;

		case LC_PIECE_PREVIOUS:
		{
			bool redraw = false;

			for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
				{
					if (m_bAnimation)
					{
						unsigned short t = pPiece->GetFrameShow();
						if (t > 1)
						{
							redraw = true;
							pPiece->SetFrameShow(t-1);
						}
					}
					else
					{
						unsigned char t = pPiece->GetStepShow();
						if (t > 1)
						{
							redraw = true;
							pPiece->SetStepShow(t-1);
						}
					}
				}

			if (redraw)
			{
				SetModifiedFlag(true);
				CheckPoint("Modifying");
				UpdateAllViews();
			}
		} break;

		case LC_PIECE_NEXT:
		{
			bool redraw = false;

			for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
				{
					if (m_bAnimation)
					{
						unsigned short t = pPiece->GetFrameShow();
						if (t < m_nTotalFrames)
						{
							redraw = true;
							pPiece->SetFrameShow(t+1);

							if (pPiece->IsSelected () && t == m_nCurFrame)
								pPiece->Select (false, false, false);
						}
					}
					else
					{
						unsigned char t = pPiece->GetStepShow();
						if (t < 255)
						{
							redraw = true;
							pPiece->SetStepShow(t+1);

							if (pPiece->IsSelected () && t == m_nCurStep)
								pPiece->Select (false, false, false);
						}
					}
				}

			if (redraw)
			{
				SetModifiedFlag(true);
				CheckPoint("Modifying");
				UpdateAllViews();
				UpdateSelection ();
			}
		} break;

		case LC_VIEW_PREFERENCES:
		{
			LC_PREFERENCESDLG_OPTS opts;
			opts.nMouse = m_nMouse;
			opts.nSaveInterval = m_nAutosave;
			strcpy(opts.strUser, Sys_ProfileLoadString ("Default", "User", ""));
			strcpy(opts.strPath, m_strModelsPath);
			opts.nDetail = m_nDetail;
			opts.fLineWidth = m_fLineWidth;
			opts.nSnap = m_nSnap;
			opts.nAngleSnap = m_nAngleSnap;
			opts.nGridSize = m_nGridSize;
			opts.nScene = m_nScene;
			opts.fDensity = m_fFogDensity;
			strcpy(opts.strBackground, m_strBackground);
			memcpy(opts.fBackground, m_fBackground, sizeof(m_fBackground));
			memcpy(opts.fFog, m_fFogColor, sizeof(m_fFogColor));
			memcpy(opts.fAmbient, m_fAmbient, sizeof(m_fAmbient));
			memcpy(opts.fGrad1, m_fGradient1, sizeof(m_fGradient1));
			memcpy(opts.fGrad2, m_fGradient2, sizeof(m_fGradient2));
			strcpy(opts.strFooter, m_strFooter);
			strcpy(opts.strHeader, m_strHeader);

			if (SystemDoDialog(LC_DLG_PREFERENCES, &opts))
			{
				m_nMouse = opts.nMouse;
				m_nAutosave = opts.nSaveInterval;
				strcpy(m_strModelsPath, opts.strPath);
				Sys_ProfileSaveString ("Default", "User", opts.strUser);
				m_nDetail = opts.nDetail;
				m_fLineWidth = opts.fLineWidth;
				m_nSnap = opts.nSnap;
				m_nAngleSnap = opts.nAngleSnap;
				m_nGridSize = opts.nGridSize;
				m_nScene = opts.nScene;
				m_fFogDensity = opts.fDensity;
				strcpy(m_strBackground, opts.strBackground);
				memcpy(m_fBackground, opts.fBackground, sizeof(m_fBackground));
				memcpy(m_fFogColor, opts.fFog, sizeof(m_fFogColor));
				memcpy(m_fAmbient, opts.fAmbient, sizeof(m_fAmbient));
				memcpy(m_fGradient1, opts.fGrad1, sizeof(m_fGradient1));
				memcpy(m_fGradient2, opts.fGrad2, sizeof(m_fGradient2));
				strcpy(m_strFooter, opts.strFooter);
				strcpy(m_strHeader, opts.strHeader);
				SystemUpdateSnap(m_nSnap);
				SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);

				for (int i = 0; i < m_ViewList.GetSize (); i++)
				{
					m_ViewList[i]->MakeCurrent ();
					RenderInitialize();
				}

				UpdateAllViews();
			}
		} break;

		case LC_VIEW_ZOOM:
		{
			m_pViewCameras[m_nActiveViewport]->DoZoom(nParam, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys);
			SystemUpdateFocus(NULL);
			UpdateOverlayScale();
			UpdateAllViews();
		} break;

		case LC_VIEW_ZOOMIN:
		{
			m_pViewCameras[m_nActiveViewport]->DoZoom(-1, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys);
			SystemUpdateFocus(NULL);
			UpdateOverlayScale();
			UpdateAllViews();
		} break;

		case LC_VIEW_ZOOMOUT:
		{
			m_pViewCameras[m_nActiveViewport]->DoZoom(1, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys);
			SystemUpdateFocus(NULL);
			UpdateOverlayScale();
			UpdateAllViews();
		} break;

		case LC_VIEW_ZOOMEXTENTS:
		{
			// FIXME: rewrite using the FustrumCull function
			if (m_pPieces == 0) break;
			bool bControl = Sys_KeyDown (KEY_CONTROL);

			GLdouble modelMatrix[16], projMatrix[16];
			float up[3], eye[3], target[3];
			float bs[6] = { 10000, 10000, 10000, -10000, -10000, -10000 };
			GLint viewport[4], out, x, y, w, h;

			for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
					pPiece->CompareBoundingBox(bs);

			float v[24] = {
				bs[0], bs[1], bs[5],
				bs[3], bs[1], bs[5],
				bs[0], bs[1], bs[2],
				bs[3], bs[4], bs[5],
				bs[3], bs[4], bs[2],
				bs[0], bs[4], bs[2],
				bs[0], bs[4], bs[5],
				bs[3], bs[1], bs[2] };

			for (int vp = 0; vp < viewports[m_nViewportMode].n; vp++)
			{
				Camera* pCam;
				if (bControl)
					pCam = m_pViewCameras[vp];
				else
					pCam = m_pViewCameras[m_nActiveViewport];

				if (!bControl)
					vp = m_nActiveViewport;

				x = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX);
				y = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY);
				w = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX);
				h = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY);
				float ratio = (float)w/h;
	
				glViewport(x,y,w,h);
				pCam->LoadProjection(ratio);

				if (!bControl)
					vp = 4;

				pCam->GetTargetPos (target);
				pCam->GetEyePos (eye);

				up[0] = (bs[0] + bs[3])/2 - target[0];
				up[1] = (bs[1] + bs[4])/2 - target[1];
				up[2] = (bs[2] + bs[5])/2 - target[2];

				if (pCam->IsSide())
				{
					eye[0] += up[0];
					eye[1] += up[1];
					eye[2] += up[2];
				}
				target[0] += up[0];
				target[1] += up[1];
				target[2] += up[2];

				pCam->GetUpVec (up);
				Vector upvec(up), frontvec(eye[0]-target[0], eye[1]-target[1], eye[2]-target[2]), sidevec;
				frontvec.Normalize();
				sidevec.Cross(frontvec, upvec);
				upvec.Cross(sidevec, frontvec);
				upvec.Normalize();
				upvec.ToFloat(up);
				frontvec *= 0.25f;

				glMatrixMode(GL_MODELVIEW);
				glGetDoublev(GL_PROJECTION_MATRIX,projMatrix);
				glGetIntegerv(GL_VIEWPORT,viewport);

				for (out = 0; out < 10000; out++) // Zoom in
				{
					eye[0] -= frontvec[0];
					eye[1] -= frontvec[1];
					eye[2] -= frontvec[2];
					glLoadIdentity();
					gluLookAt(eye[0], eye[1], eye[2], target[0], target[1], target[2], up[0], up[1], up[2]);
					glGetDoublev(GL_MODELVIEW_MATRIX,modelMatrix);

					for (int i = 0; i < 24; i+=3)
					{
						double winx, winy, winz;
						gluProject (v[i], v[i+1], v[i+2], modelMatrix, projMatrix, viewport, &winx, &winy, &winz);
						if ((winx < viewport[0] + 1) || (winy < viewport[1] + 1) || 
							(winx > viewport[0] + viewport[2] - 1) || (winy > viewport[1] + viewport[3] - 1))
						{
							out = 10000;
							continue;
						}
					}
				}

				bool stp = false;
				for (out = 0; out < 10000 && !stp; out++) // zoom out
				{
					stp = true;
					eye[0] += frontvec[0];
					eye[1] += frontvec[1];
					eye[2] += frontvec[2];
					glLoadIdentity();
					gluLookAt(eye[0], eye[1], eye[2], target[0], target[1], target[2], up[0], up[1], up[2]);
					glGetDoublev(GL_MODELVIEW_MATRIX,modelMatrix);

					for (int i = 0; i < 24; i+=3)
					{
						double winx, winy, winz;
						gluProject (v[i], v[i+1], v[i+2], modelMatrix, projMatrix, viewport, &winx, &winy, &winz);
						if ((winx < viewport[0] + 1) || (winy < viewport[1] + 1) || 
							(winx > viewport[0] + viewport[2] - 1) || (winy > viewport[1] + viewport[3] - 1))
						{
							stp = false;
							continue;
						}
					}
				}

				pCam->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, eye, LC_CK_EYE);
				pCam->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, target, LC_CK_TARGET);
				pCam->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
			}

			SystemUpdateFocus(NULL);
			UpdateOverlayScale();
			UpdateAllViews();
		} break;

		case LC_VIEW_VIEWPORTS:
		{
			// Safety check
			if (m_nActiveViewport >= viewports[nParam].n)
			{
				SystemUpdateCurrentCamera(m_pViewCameras[m_nActiveViewport], m_pViewCameras[0], m_pCameras);
				m_nActiveViewport = 0;
			}

			SystemUpdateViewport(nParam, m_nViewportMode);
			m_nViewportMode = (unsigned char)nParam;
			UpdateOverlayScale();
			UpdateAllViews();
		} break;

		case LC_VIEW_STEP_NEXT:
		{
			if (m_bAnimation)
				m_nCurFrame++;
			else
				m_nCurStep++;

			CalculateStep();
			UpdateSelection();
			SystemUpdateFocus(NULL);
			UpdateAllViews();

			if (m_bAnimation)
				SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
			else
				SystemUpdateTime(m_bAnimation, m_nCurStep, 255);
		} break;
		
		case LC_VIEW_STEP_PREVIOUS:
		{
			if (m_bAnimation)
				m_nCurFrame--;
			else
				m_nCurStep--;

			CalculateStep();
			UpdateSelection();
			SystemUpdateFocus(NULL);
			UpdateAllViews();

			if (m_bAnimation)
				SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
			else
				SystemUpdateTime(m_bAnimation, m_nCurStep, 255);
		} break;
		
		case LC_VIEW_STEP_FIRST:
		{
			if (m_bAnimation)
				m_nCurFrame = 1;
			else
				m_nCurStep = 1;

			CalculateStep();
			UpdateSelection();
			SystemUpdateFocus(NULL);
			UpdateAllViews();

			if (m_bAnimation)
				SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
			else
				SystemUpdateTime(m_bAnimation, m_nCurStep, 255);
		} break;

		case LC_VIEW_STEP_LAST:
		{
			if (m_bAnimation)
				m_nCurFrame = m_nTotalFrames;
			else
				m_nCurStep = GetLastStep ();

			CalculateStep();
			UpdateSelection();
			SystemUpdateFocus(NULL);
			UpdateAllViews();

			if (m_bAnimation)
				SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
			else
				SystemUpdateTime(m_bAnimation, m_nCurStep, 255);
		} break;

		case LC_VIEW_STEP_CHOOSE:
		{
			SystemDoDialog(LC_DLG_STEPCHOOSE, NULL);
			if (m_bAnimation)
				SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
			else
				SystemUpdateTime(m_bAnimation, m_nCurStep, 255);
		} break;

		case LC_VIEW_STEP_SET:
		{
			if (IsDrawing())
				break;

			if (m_bAnimation)
				m_nCurFrame = (nParam < m_nTotalFrames) ? (unsigned short)nParam : m_nTotalFrames;
			else
				m_nCurStep = (nParam < 255) ? (unsigned char)nParam : 255;

			CalculateStep();
			UpdateSelection();
			SystemUpdateFocus(NULL);
			UpdateAllViews();

			if (m_bAnimation)
				SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
			else
				SystemUpdateTime(m_bAnimation, m_nCurStep, 255);
		} break;

    case LC_VIEW_STEP_INSERT:
    {
      for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
        pPiece->InsertTime (m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, 1);

      for (Camera* pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
        pCamera->InsertTime (m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, 1);

      for (Light* pLight = m_pLights; pLight; pLight = pLight->m_pNext)
        pLight->InsertTime (m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, 1);

      SetModifiedFlag (true);
      if (m_bAnimation)
        CheckPoint ("Adding Frame");
      else
        CheckPoint ("Adding Step");
			CalculateStep ();
      UpdateAllViews ();
      UpdateSelection ();
    } break;

    case LC_VIEW_STEP_DELETE:
    {
      for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
        pPiece->RemoveTime (m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, 1);

      for (Camera* pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
        pCamera->RemoveTime (m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, 1);

      for (Light* pLight = m_pLights; pLight; pLight = pLight->m_pNext)
        pLight->RemoveTime (m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, 1);

      SetModifiedFlag (true);
      if (m_bAnimation)
        CheckPoint ("Removing Frame");
      else
        CheckPoint ("Removing Step");
			CalculateStep ();
      UpdateAllViews ();
      UpdateSelection ();
    } break;

		case LC_VIEW_STOP:
		{
			m_bStopRender = true;
		} break;

		case LC_VIEW_PLAY:
		{ 
			SelectAndFocusNone(false);
			UpdateSelection();
			m_bStopRender = false;
			m_bRendering = true;
			SystemUpdatePlay(false, true);
			long time = SystemGetTicks();
			unsigned short tics;
			float rate = 1000.0f/m_nFPS;

			while (!m_bStopRender)
			{
				tics = (unsigned short)(SystemGetTicks() - time);
				if (tics < rate)
					continue; // nothing to do

				time = SystemGetTicks();
				m_nCurFrame += (unsigned short)((float)tics/rate);
				while (m_nCurFrame > m_nTotalFrames)
					m_nCurFrame -= m_nTotalFrames;
				CalculateStep();
				SystemUpdateTime(true, m_nCurFrame, m_nTotalFrames);
                                //				RenderScene((m_nDetail & LC_DET_FAST) == 0, true);
                                //				SystemSwapBuffers();
                                UpdateAllViews ();
				SystemPumpMessages();
			}
			m_bRendering = false;
			SystemUpdatePlay(true, false);
			SystemUpdateFocus(NULL);
		} break;

		case LC_VIEW_CAMERA_FRONT:
		{
			HandleCommand(LC_VIEW_CAMERA_MENU, LC_CAMERA_FRONT);
		} break;

		case LC_VIEW_CAMERA_BACK:
		{
			HandleCommand(LC_VIEW_CAMERA_MENU, LC_CAMERA_BACK);
		} break;

		case LC_VIEW_CAMERA_TOP:
		{
			HandleCommand(LC_VIEW_CAMERA_MENU, LC_CAMERA_TOP);
		} break;

		case LC_VIEW_CAMERA_BOTTOM:
		{
			HandleCommand(LC_VIEW_CAMERA_MENU, LC_CAMERA_UNDER);
		} break;

		case LC_VIEW_CAMERA_LEFT:
		{
			HandleCommand(LC_VIEW_CAMERA_MENU, LC_CAMERA_LEFT);
		} break;

		case LC_VIEW_CAMERA_RIGHT:
		{
			HandleCommand(LC_VIEW_CAMERA_MENU, LC_CAMERA_RIGHT);
		} break;

		case LC_VIEW_CAMERA_MAIN:
		{
			HandleCommand(LC_VIEW_CAMERA_MENU, LC_CAMERA_MAIN);
		} break;

		case LC_VIEW_CAMERA_MENU:
		{
			Camera* pCamera = m_pCameras;

			while (nParam--)
				pCamera = pCamera->m_pNext;

			SystemUpdateCurrentCamera(m_pViewCameras[m_nActiveViewport], pCamera, m_pCameras);
			m_pViewCameras[m_nActiveViewport] = pCamera;
			UpdateOverlayScale();
			UpdateAllViews();
		} break;

		case LC_VIEW_CAMERA_RESET:
		{
			Camera* pCamera;
			int i;

			while (m_pCameras)
			{
				pCamera = m_pCameras;
				m_pCameras = m_pCameras->m_pNext;
				delete pCamera;
			}

			for (m_pCameras = pCamera = NULL, i = 0; i < 7; i++)
			{
				pCamera = new Camera(i, pCamera);
				if (m_pCameras == NULL)
					m_pCameras = pCamera;
				
				switch (i) 
				{
				case LC_CAMERA_MAIN:  m_pViewCameras[0] = pCamera; break;
				case LC_CAMERA_FRONT: m_pViewCameras[1] = pCamera; break;
				case LC_CAMERA_TOP:   m_pViewCameras[2] = pCamera; break;
				case LC_CAMERA_RIGHT: m_pViewCameras[3] = pCamera; break;
				}
			}

			SystemUpdateCameraMenu(m_pCameras);
			SystemUpdateCurrentCamera(NULL, m_pViewCameras[m_nActiveViewport], m_pCameras);
			SystemUpdateFocus(NULL);
			UpdateOverlayScale();
			UpdateAllViews();
			SetModifiedFlag(true);
			CheckPoint("Reset Cameras");
		} break;

		case LC_VIEW_AUTOPAN:
		{
			if (IsDrawing())
				break;

			short x = (short)nParam;
			short y = (short)((nParam >> 16) & 0xFFFF);

			x -= x > 0 ? 5 : -5;
			y -= y > 0 ? 5 : -5;

			m_pViewCameras[m_nActiveViewport]->DoPan(x/4, y/4, 1, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys);
			m_nDownX = x;
			m_nDownY = y;
			UpdateOverlayScale();
			SystemUpdateFocus(NULL);
			UpdateAllViews();
		} break;

		case LC_HELP_ABOUT:
		{
		  SystemDoDialog(LC_DLG_ABOUT, 0);
		} break;

		case LC_TOOLBAR_ANIMATION:
		{
			m_bAnimation = !m_bAnimation;

			CalculateStep();
			SystemUpdateFocus(NULL);
			UpdateAllViews();

			SystemUpdateAnimation(m_bAnimation, m_bAddKeys);
			if (m_bAnimation)
				SystemUpdateTime(m_bAnimation, m_nCurFrame, m_nTotalFrames);
			else
				SystemUpdateTime(m_bAnimation, m_nCurStep, 255);
		} break;
		
		case LC_TOOLBAR_ADDKEYS:
		{
			m_bAddKeys = !m_bAddKeys;
			SystemUpdateAnimation(m_bAnimation, m_bAddKeys);
		} break;

		// Change snap X, Y, Z, All, None or Angle.
		case LC_TOOLBAR_SNAPMENU:
		{
			switch (nParam)
			{
			case 0:
				if (m_nSnap & LC_DRAW_SNAP_X)
					m_nSnap &= ~LC_DRAW_SNAP_X;
				else
					m_nSnap |= LC_DRAW_SNAP_X;
				break;
			case 1:
				if (m_nSnap & LC_DRAW_SNAP_Y)
					m_nSnap &= ~LC_DRAW_SNAP_Y;
				else
					m_nSnap |= LC_DRAW_SNAP_Y;
				break;
			case 2:
				if (m_nSnap & LC_DRAW_SNAP_Z)
					m_nSnap &= ~LC_DRAW_SNAP_Z;
				else
					m_nSnap |= LC_DRAW_SNAP_Z;
				break;
			case 3:
				if ((m_nSnap & LC_DRAW_SNAP_XYZ) == LC_DRAW_SNAP_XYZ)
					m_nSnap &= ~LC_DRAW_SNAP_XYZ;
				else
					m_nSnap |= LC_DRAW_SNAP_XYZ;
				break;
			case 4:
				m_nSnap &= ~LC_DRAW_SNAP_XYZ;
				break;
			case 5:
				if (m_nSnap & LC_DRAW_SNAP_A)
					m_nSnap &= ~LC_DRAW_SNAP_A;
				else
					m_nSnap |= LC_DRAW_SNAP_A;
				break;
			}
			SystemUpdateSnap(m_nSnap);
		} break;

		case LC_TOOLBAR_LOCKMENU:
		{
			switch (nParam)
			{
			case 0:
				if (m_nSnap & LC_DRAW_LOCK_X)
					m_nSnap &= ~LC_DRAW_LOCK_X;
				else
					m_nSnap |= LC_DRAW_LOCK_X;
				break;
			case 1:
				if (m_nSnap & LC_DRAW_LOCK_Y)
					m_nSnap &= ~LC_DRAW_LOCK_Y;
				else
					m_nSnap |= LC_DRAW_LOCK_Y;
				break;
			case 2:
				if (m_nSnap & LC_DRAW_LOCK_Z)
					m_nSnap &= ~LC_DRAW_LOCK_Z;
				else
					m_nSnap |= LC_DRAW_LOCK_Z;
				break;
			case 3:
				m_nSnap &= ~LC_DRAW_LOCK_XYZ;
				break;
			}
			SystemUpdateSnap(m_nSnap);
		} break;

		case LC_TOOLBAR_BACKGROUND:
		case LC_TOOLBAR_FASTRENDER:
		{
			if (id == LC_TOOLBAR_BACKGROUND)
			{
				if (m_nDetail & LC_DET_BACKGROUND) 
					m_nDetail &= ~LC_DET_BACKGROUND; 
				else
					m_nDetail |= LC_DET_BACKGROUND;
			}
			else
			{
				if (m_nDetail & LC_DET_FAST) 
					m_nDetail &= ~(LC_DET_FAST | LC_DET_BACKGROUND); 
				else
					m_nDetail |= LC_DET_FAST; 
				UpdateAllViews();
			}

			SystemUpdateRenderingMode((m_nDetail & LC_DET_BACKGROUND) != 0, (m_nDetail & LC_DET_FAST) != 0);
		} break;

		case LC_EDIT_MOVEXY_SNAP_0:
		case LC_EDIT_MOVEXY_SNAP_1:
		case LC_EDIT_MOVEXY_SNAP_2:
		case LC_EDIT_MOVEXY_SNAP_3:
		case LC_EDIT_MOVEXY_SNAP_4:
		case LC_EDIT_MOVEXY_SNAP_5:
		case LC_EDIT_MOVEXY_SNAP_6:
		case LC_EDIT_MOVEXY_SNAP_7:
		case LC_EDIT_MOVEXY_SNAP_8:
		case LC_EDIT_MOVEXY_SNAP_9:
		{
			m_nMoveSnap = (id - LC_EDIT_MOVEXY_SNAP_0) | (m_nMoveSnap & ~0xff);
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_MOVEZ_SNAP_0:
		case LC_EDIT_MOVEZ_SNAP_1:
		case LC_EDIT_MOVEZ_SNAP_2:
		case LC_EDIT_MOVEZ_SNAP_3:
		case LC_EDIT_MOVEZ_SNAP_4:
		case LC_EDIT_MOVEZ_SNAP_5:
		case LC_EDIT_MOVEZ_SNAP_6:
		case LC_EDIT_MOVEZ_SNAP_7:
		case LC_EDIT_MOVEZ_SNAP_8:
		case LC_EDIT_MOVEZ_SNAP_9:
		{
			m_nMoveSnap = (((id - LC_EDIT_MOVEZ_SNAP_0) << 8) | (m_nMoveSnap & ~0xff00));
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_0:
		{
			m_nAngleSnap = 1;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_1:
		{
			m_nAngleSnap = 5;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_2:
		{
			m_nAngleSnap = 10;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_3:
		{
			m_nAngleSnap = 15;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_4:
		{
			m_nAngleSnap = 30;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_5:
		{
			m_nAngleSnap = 45;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_6:
		{
			m_nAngleSnap = 60;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_7:
		{
			m_nAngleSnap = 90;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ANGLE_SNAP_8:
		{
			m_nAngleSnap = 180;
			SystemUpdateSnap(m_nMoveSnap, m_nAngleSnap);
		} break;

		case LC_EDIT_ACTION_SELECT:
		{
			SetAction(LC_ACTION_SELECT);
		} break;

		case LC_EDIT_ACTION_INSERT:
		{
			SetAction(LC_ACTION_INSERT);
		} break;

		case LC_EDIT_ACTION_LIGHT:
		{
			SetAction(LC_ACTION_LIGHT);
		} break;

		case LC_EDIT_ACTION_SPOTLIGHT:
		{
			SetAction(LC_ACTION_SPOTLIGHT);
		} break;

		case LC_EDIT_ACTION_CAMERA:
		{
			SetAction(LC_ACTION_CAMERA);
		} break;

		case LC_EDIT_ACTION_MOVE:
		{
			SetAction(LC_ACTION_MOVE);
		} break;

		case LC_EDIT_ACTION_ROTATE:
		{
			SetAction(LC_ACTION_ROTATE);
		} break;

		case LC_EDIT_ACTION_ERASER:
		{
			SetAction(LC_ACTION_ERASER);
		} break;

		case LC_EDIT_ACTION_PAINT:
		{
			SetAction(LC_ACTION_PAINT);
		} break;

		case LC_EDIT_ACTION_ZOOM:
		{
			SetAction(LC_ACTION_ZOOM);
		} break;

		case LC_EDIT_ACTION_ZOOM_REGION:
		{
			SetAction(LC_ACTION_ZOOM_REGION);
		} break;

		case LC_EDIT_ACTION_PAN:
		{
			SetAction(LC_ACTION_PAN);
		} break;

		case LC_EDIT_ACTION_ROTATE_VIEW:
		{
			SetAction(LC_ACTION_ROTATE_VIEW);
		} break;

		case LC_EDIT_ACTION_ROLL:
		{
			SetAction(LC_ACTION_ROLL);
		} break;
	}
}

void Project::SetAction(int nAction)
{
	bool Redraw = false;

	if (m_nCurAction == LC_ACTION_INSERT)
		Redraw = true;

	SystemUpdateAction(nAction, m_nCurAction);
	m_nCurAction = nAction;

	if ((m_nCurAction == LC_ACTION_MOVE) || (m_nCurAction == LC_ACTION_ROTATE) ||
	    (m_nCurAction == LC_ACTION_ROTATE_VIEW))
	{
		ActivateOverlay();

		if (m_OverlayActive)
			Redraw = true;
	}
	else
	{
		if (m_OverlayActive)
		{
			m_OverlayActive = false;
			Redraw = true;
		}
	}

	if (Redraw)
		UpdateAllViews();
}

// Remove unused groups
void Project::RemoveEmptyGroups()
{
	bool recurse = false;
	Group *g1, *g2;
	Piece* pPiece;
	int ref;

	for (g1 = m_pGroups; g1;)
	{
		ref = 0;

		for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			if (pPiece->GetGroup() == g1)
				ref++;

		for (g2 = m_pGroups; g2; g2 = g2->m_pNext)
			if (g2->m_pGroup == g1)
				ref++;

		if (ref < 2)
		{
			if (ref != 0)
			{
				for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
					if (pPiece->GetGroup() == g1)
						pPiece->SetGroup(g1->m_pGroup);

				for (g2 = m_pGroups; g2; g2 = g2->m_pNext)
					if (g2->m_pGroup == g1)
						g2->m_pGroup = g1->m_pGroup;
			}

			if (g1 == m_pGroups)
			{
				m_pGroups = g1->m_pNext;
				delete g1;
				g1 = m_pGroups;
			}
			else
			{
				for (g2 = m_pGroups; g2; g2 = g2->m_pNext)
					if (g2->m_pNext == g1)
					{
						g2->m_pNext = g1->m_pNext;
						break;
					}

				delete g1;
				g1 = g2->m_pNext;
			}

			recurse = true;
		}
		else
			g1 = g1->m_pNext;
	}

	if (recurse)
		RemoveEmptyGroups();
}

Group* Project::AddGroup (const char* name, Group* pParent, float x, float y, float z)
{
  Group *pNewGroup = new Group();

  if (name == NULL)
  {
    int i, max = 0;
    char str[65];

    for (Group *pGroup = m_pGroups; pGroup; pGroup = pGroup->m_pNext)
      if (strncmp (pGroup->m_strName, "Group #", 7) == 0)
        if (sscanf(pGroup->m_strName, "Group #%d", &i) == 1)
          if (i > max)
            max = i;
    sprintf (str, "Group #%.2d", max+1);

    strcpy (pNewGroup->m_strName, str);
  }
  else
    strcpy (pNewGroup->m_strName, name);

  pNewGroup->m_pNext = m_pGroups;
  m_pGroups = pNewGroup;

  pNewGroup->m_fCenter[0] = x;
  pNewGroup->m_fCenter[1] = y;
  pNewGroup->m_fCenter[2] = z;
  pNewGroup->m_pGroup = pParent;

  return pNewGroup;
}

void Project::SelectAndFocusNone(bool bFocusOnly)
{
  Piece* pPiece;
  Camera* pCamera;
  Light* pLight;

  for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
    pPiece->Select (false, bFocusOnly, false);

  for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
  {
    pCamera->Select (false, bFocusOnly, false);
    pCamera->GetTarget ()->Select (false, bFocusOnly, false);
  }

  for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
  {
    pLight->Select (false, bFocusOnly, false);
    if (pLight->GetTarget ())
      pLight->GetTarget ()->Select (false, bFocusOnly, false);
  }
//	AfxGetMainWnd()->PostMessage(WM_LC_UPDATE_INFO, NULL, OT_PIECE);
}

bool Project::GetSelectionCenter(Vector3& Center) const
{
	float bs[6] = { 10000, 10000, 10000, -10000, -10000, -10000 };
	bool Selected = false;

	for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
	{
		if (pPiece->IsSelected())
		{
			pPiece->CompareBoundingBox(bs);
			Selected = true;
		}
	}

	Center = Vector3((bs[0] + bs[3]) * 0.5f, (bs[1] + bs[4]) * 0.5f, (bs[2] + bs[5]) * 0.5f);

	return Selected;
}

Camera* Project::GetCamera(int i)
{
	Camera* pCamera;

	for (pCamera = m_pCameras; i-- > 0 && pCamera; pCamera = pCamera->m_pNext)
		;
	return pCamera;
}

void Project::GetActiveViewportMatrices(Matrix44& ModelView, Matrix44& Projection, int Viewport[4])
{
	Viewport[0] = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX);
	Viewport[1] = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY);
	Viewport[2] = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX);
	Viewport[3] = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY);

	float Aspect = (float)Viewport[2]/(float)Viewport[3];
	Camera* Cam = m_pViewCameras[m_nActiveViewport];

	// Build the matrices.
	ModelView.CreateLookAt(Cam->GetEyePosition(), Cam->GetTargetPosition(), Cam->GetUpVector());
	Projection.CreatePerspective(Cam->m_fovy, Aspect, Cam->m_zNear, Cam->m_zFar);
}

void Project::ConvertToUserUnits(Vector3& Value) const
{
	if ((m_nSnap & LC_DRAW_CM_UNITS) == 0)
		Value /= 0.08f;
}

void Project::ConvertFromUserUnits(Vector3& Value) const
{
	if ((m_nSnap & LC_DRAW_CM_UNITS) == 0)
		Value *= 0.08f;
}

bool Project::GetFocusPosition(Vector3& Position) const
{
	Piece* pPiece;
	Camera* pCamera;
	float* pos = &Position[0];

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		if (pPiece->IsFocused())
		{
			pPiece->GetPosition(pos);
			return true;
		}

	for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
	{
		if (pCamera->IsEyeFocused())
		{
			pCamera->GetEyePos(pos);
			return true;
		}

		if (pCamera->IsTargetFocused())
		{
			pCamera->GetTargetPos(pos);
			return true;
		}
	}

	// TODO: light

	pos[0] = pos[1] = pos[2] = 0.0f;

	return false;
}

// Returns the object that currently has focus.
Object* Project::GetFocusObject() const
{
	for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
	{
		if (pPiece->IsFocused())
			return pPiece;
	}

	for (Camera* pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
	{
		if (pCamera->IsEyeFocused() || pCamera->IsTargetFocused())
			return pCamera;
	}

	// TODO: light

	return NULL;
}

// Find a good starting position/orientation relative to an existing piece.
void Project::GetPieceInsertPosition(Piece* OffsetPiece, Vector3& Position, Vector4& Rotation)
{
	Vector3 Dist(0, 0, OffsetPiece->GetPieceInfo()->m_fDimensions[2] - m_pCurPiece->m_fDimensions[5]);
	SnapVector(Dist);

	float pos[3], rot[4];
	OffsetPiece->GetPosition(pos);
	OffsetPiece->GetRotation(rot);

	Matrix mat(rot, pos);
	mat.Translate(Dist[0], Dist[1], Dist[2]);
	mat.GetTranslation(pos);

	Position = Vector3(pos[0], pos[1], pos[2]);
	Rotation = Vector4(rot[0], rot[1], rot[2], rot[3]);
}

// Try to find a good starting position/orientation for a new piece.
void Project::GetPieceInsertPosition(int MouseX, int MouseY, Vector3& Position, Vector4& Rotation)
{
	// See if the mouse is over a piece.
	LC_CLICKLINE ClickLine;
	FindObjectFromPoint(MouseX, MouseY, &ClickLine, true);

	Piece* HitPiece = (Piece*)ClickLine.pClosest;
	if (HitPiece)
	{
		GetPieceInsertPosition(HitPiece, Position, Rotation);
		return;
	}

	// Try to hit the base grid.
	int Viewport[4] =
	{
		(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX),
		(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY),
		(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX),
		(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY)
	};

	float Aspect = (float)Viewport[2]/(float)Viewport[3];
	Camera* Cam = m_pViewCameras[m_nActiveViewport];

	// Build the matrices.
	Matrix44 ModelView, Projection;
	ModelView.CreateLookAt(Cam->GetEyePosition(), Cam->GetTargetPosition(), Cam->GetUpVector());
	Projection.CreatePerspective(Cam->m_fovy, Aspect, Cam->m_zNear, Cam->m_zFar);

	Vector3 ClickPoints[2] = { Vector3((float)m_nDownX, (float)m_nDownY, 0.0f), Vector3((float)m_nDownX, (float)m_nDownY, 1.0f) };
	UnprojectPoints(ClickPoints, 2, ModelView, Projection, Viewport);

	Vector3 Intersection;
	if (LinePlaneIntersection(Intersection, ClickPoints[0], ClickPoints[1], Vector4(0, 0, 1, 0)))
	{
		SnapVector(Intersection);
		Position = Intersection;
		Rotation = Vector4(0, 0, 1, 0);
		return;
	}

	// Couldn't find a good position, so just place the piece somewhere near the camera.
	Position = UnprojectPoint(Vector3((float)m_nDownX, (float)m_nDownY, 0.9f), ModelView, Projection, Viewport);
	Rotation = Vector4(0, 0, 1, 0);
}

void Project::FindObjectFromPoint(int x, int y, LC_CLICKLINE* pLine, bool PiecesOnly)
{
	GLdouble px, py, pz, rx, ry, rz;
	GLdouble modelMatrix[16], projMatrix[16];
	GLint viewport[4];
	Piece* pPiece;
	Camera* pCamera;
	Light* pLight;

	LoadViewportProjection(m_nActiveViewport);
	glGetDoublev(GL_MODELVIEW_MATRIX,modelMatrix);
	glGetDoublev(GL_PROJECTION_MATRIX,projMatrix);
	glGetIntegerv(GL_VIEWPORT,viewport);

	// Unproject the selected point against both the front and the back clipping plane
	gluUnProject(x, y, 0, modelMatrix, projMatrix, viewport, &px, &py, &pz);
	gluUnProject(x, y, 1, modelMatrix, projMatrix, viewport, &rx, &ry, &rz);

	pLine->a1 = (float)px;
	pLine->b1 = (float)py;
	pLine->c1 = (float)pz;
	pLine->a2 = (float)(rx-px);
	pLine->b2 = (float)(ry-py);
	pLine->c2 = (float)(rz-pz);
	pLine->mindist = FLT_MAX;
	pLine->pClosest = NULL;

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
			pPiece->MinIntersectDist(pLine);

	if (!PiecesOnly)
	{
		for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
			if (pCamera != m_pViewCameras[m_nActiveViewport])
				pCamera->MinIntersectDist(pLine);

		for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
			pLight->MinIntersectDist(pLine);
	}
}

void Project::FindObjectsInBox(float x1, float y1, float x2, float y2, PtrArray<Object>& Objects)
{
	int Viewport[4] =
	{
		(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX),
		(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY),
		(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX),
		(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY)
	};

	float Aspect = (float)Viewport[2]/(float)Viewport[3];
	Camera* Cam = m_pViewCameras[m_nActiveViewport];

	// Build the matrices.
	Matrix44 ModelView, Projection;
	ModelView.CreateLookAt(Cam->GetEyePosition(), Cam->GetTargetPosition(), Cam->GetUpVector());
	Projection.CreatePerspective(Cam->m_fovy, Aspect, Cam->m_zNear, Cam->m_zFar);

	// Find out the top-left and bottom-right corners in screen coordinates.
	float Left, Top, Bottom, Right;

	if (x1 < x2)
	{
		Left = x1;
		Right = x2;
	}
	else
	{
		Left = x2;
		Right = x1;
	}

	if (y1 > y2)
	{
		Top = y1;
		Bottom = y2;
	}
	else
	{
		Top = y2;
		Bottom = y1;
	}

	// Unproject 6 points to world space.
	Vector3 Corners[6] =
	{
		Vector3(Left, Top, 0), Vector3(Left, Bottom, 0), Vector3(Right, Bottom, 0),
		Vector3(Right, Top, 0), Vector3(Left, Top, 1), Vector3(Right, Bottom, 1)
	};

	UnprojectPoints(Corners, 6, ModelView, Projection, Viewport);

	// Build the box planes.
	Vector4 Planes[6];

	Planes[0] = Cross3(Corners[4] - Corners[0], Corners[1] - Corners[0]).Normalize(); // Left
	Planes[1] = Cross3(Corners[5] - Corners[2], Corners[3] - Corners[2]).Normalize(); // Right
	Planes[2] = Cross3(Corners[3] - Corners[0], Corners[4] - Corners[0]).Normalize(); // Top
	Planes[3] = Cross3(Corners[1] - Corners[2], Corners[5] - Corners[2]).Normalize(); // Bottom
	Planes[4] = Cross3(Corners[1] - Corners[0], Corners[3] - Corners[0]).Normalize(); // Front
	Planes[5] = Cross3(Corners[1] - Corners[2], Corners[3] - Corners[2]).Normalize(); // Back

	Planes[0][3] = -Dot3(Planes[0], Corners[0]);
	Planes[1][3] = -Dot3(Planes[1], Corners[5]);
	Planes[2][3] = -Dot3(Planes[2], Corners[0]);
	Planes[3][3] = -Dot3(Planes[3], Corners[5]);
	Planes[4][3] = -Dot3(Planes[4], Corners[0]);
	Planes[5][3] = -Dot3(Planes[5], Corners[5]);

	// Check if any objects are inside the volume.
	for (Piece* piece = m_pPieces; piece != NULL; piece = piece->m_pNext)
	{
		if (piece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
		{
			if (piece->IntersectsVolume(Planes, 6))
				Objects.Add(piece);
		}
	}

	// TODO: lights and cameras.
}

/////////////////////////////////////////////////////////////////////////////
// Mouse handling

// Returns true if point is not inside the current viewport.
bool Project::SetActiveViewport(int px, int py)
{
	float x, y, w, h;
	int vp;

	for (vp = 0; vp < viewports[m_nViewportMode].n; vp++)
	{
		x = viewports[m_nViewportMode].dim[vp][0] * (float)m_nViewX;
		y = viewports[m_nViewportMode].dim[vp][1] * (float)m_nViewY;
		w = viewports[m_nViewportMode].dim[vp][2] * (float)m_nViewX;
		h = viewports[m_nViewportMode].dim[vp][3] * (float)m_nViewY;

		if (px > x && px < x + w && py > y && py < y + h)
		{
			if (m_nActiveViewport != vp)
			{
				SystemUpdateCurrentCamera(m_pViewCameras[m_nActiveViewport], m_pViewCameras[vp], m_pCameras);
				m_nActiveViewport = vp;
				Render(false);
//				glDrawBuffer(GL_FRONT);
//				RenderViewports(true);
//				glDrawBuffer(GL_BACK);
				UpdateAllViews();

				return true;
			}
			else
				return false;
		}
	}

	// We shouldn't get here...
	m_nActiveViewport = 0;
	return true;
}

void Project::LoadViewportProjection(int Viewport)
{
	int x, y, w, h;
	float ratio;

	x = (int)(viewports[m_nViewportMode].dim[Viewport][0] * (float)m_nViewX);
	y = (int)(viewports[m_nViewportMode].dim[Viewport][1] * (float)m_nViewY);
	w = (int)(viewports[m_nViewportMode].dim[Viewport][2] * (float)m_nViewX);
	h = (int)(viewports[m_nViewportMode].dim[Viewport][3] * (float)m_nViewY);

	ratio = (float)w/h;
	glViewport(x, y, w, h);
	m_pViewCameras[Viewport]->LoadProjection(ratio);
}

// Returns true if the mouse was being tracked.
bool Project::StopTracking(bool bAccept)
{
	if (m_nTracking == LC_TRACK_NONE)
		return false;

	if ((m_nTracking == LC_TRACK_START_LEFT) || (m_nTracking == LC_TRACK_START_RIGHT))
	{
		if (m_pTrackFile)
		{
			delete m_pTrackFile;
			m_pTrackFile = NULL;
		}

		m_nTracking = LC_TRACK_NONE;
		SystemReleaseMouse();
		return false;
	}

	m_bTrackCancel = true;
	m_nTracking = LC_TRACK_NONE;
	SystemReleaseMouse();

	// Reset the mouse overlay.
	if (m_OverlayActive)
	{
		ActivateOverlay();
		UpdateAllViews();
	}

	if (bAccept)
	{
		switch (m_nCurAction)
		{
			case LC_ACTION_SELECT:
			{
				if (((float)m_nDownX != m_fTrack[0]) && ((float)m_nDownY != m_fTrack[1]))
				{
					// Find objects inside the rectangle.
					PtrArray<Object> Objects;
					FindObjectsInBox((float)m_nDownX, (float)m_nDownY, m_fTrack[0], m_fTrack[1], Objects);

					// Deselect old pieces.
					bool Control = Sys_KeyDown(KEY_CONTROL);
					SelectAndFocusNone(Control);

					// Select new pieces.
					for (int i = 0; i < Objects.GetSize(); i++)
					{
						if (Objects[i]->GetType() == LC_OBJECT_PIECE)
						{
							Group* pGroup = ((Piece*)Objects[i])->GetTopGroup();
							if (pGroup != NULL)
							{
								for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
									if ((pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation)) &&
											(pPiece->GetTopGroup() == pGroup))
										pPiece->Select (true, false, false);
							}
							else
								Objects[i]->Select(true, false, Control);
						}
						else
							Objects[i]->Select(true, false, Control);
					}
				}

				// Update screen and UI.
				UpdateSelection();
				UpdateAllViews();
				SystemUpdateFocus(NULL);

			} break;

			case LC_ACTION_MOVE:
			{
				SetModifiedFlag(true);
				CheckPoint("Moving");
			} break;

			case LC_ACTION_ROTATE:
			{
				SetModifiedFlag(true);
				CheckPoint("Rotating");
			} break;

			case LC_ACTION_CAMERA:
			{
				SystemUpdateCameraMenu(m_pCameras);
				SystemUpdateCurrentCamera(NULL, m_pViewCameras[m_nActiveViewport], m_pCameras);
				SetModifiedFlag(true);
				CheckPoint("Inserting");
			} break;

			case LC_ACTION_CURVE:
			{
				SetModifiedFlag(true);
				CheckPoint("Inserting");
			} break;

			case LC_ACTION_SPOTLIGHT:
			{
				SetModifiedFlag(true);
				CheckPoint("Inserting");
			} break;

			case LC_ACTION_ZOOM:
			case LC_ACTION_PAN:
			case LC_ACTION_ROTATE_VIEW:
			case LC_ACTION_ROLL:
			{
				// For some reason the scene doesn't get redrawn when changing a camera but it does
				// when moving things around, so manually get the full scene rendered again.
				if (m_nDetail & LC_DET_FAST)
					UpdateAllViews();
			} break;

			case LC_ACTION_ZOOM_REGION:
			{
				int Viewport[4] =
				{
					(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX),
					(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY),
					(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX),
					(int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY)
				};

				float Aspect = (float)Viewport[2]/(float)Viewport[3];
				Camera* Cam = m_pViewCameras[m_nActiveViewport];

				// Build the matrices.
				Matrix44 ModelView, Projection;
				ModelView.CreateLookAt(Cam->GetEyePosition(), Cam->GetTargetPosition(), Cam->GetUpVector());
				Projection.CreatePerspective(Cam->m_fovy, Aspect, Cam->m_zNear, Cam->m_zFar);

				// Find out the top-left and bottom-right corners in screen coordinates.
				float Left, Top, Bottom, Right;

				if (m_OverlayTrackStart[0] < m_nDownX)
				{
					Left = m_OverlayTrackStart[0];
					Right = (float)m_nDownX;
				}
				else
				{
					Left = (float)m_nDownX;
					Right = m_OverlayTrackStart[0];
				}

				if (m_OverlayTrackStart[1] > m_nDownY)
				{
					Top = m_OverlayTrackStart[1];
					Bottom = (float)m_nDownY;
				}
				else
				{
					Top = (float)m_nDownY;
					Bottom = m_OverlayTrackStart[1];
				}

				// Unproject screen points to world space.
				Vector3 Points[3] =
				{
					Vector3((Left + Right) / 2, (Top + Bottom) / 2, 0.9f),
					Vector3((float)Viewport[2] / 2.0f, (float)Viewport[3] / 2.0f, 0.9f),
					Vector3((float)Viewport[2] / 2.0f, (float)Viewport[3] / 2.0f, 0.1f),
				};

				UnprojectPoints(Points, 3, ModelView, Projection, Viewport);

				// Center camera.
				Vector3 Eye = Cam->GetEyePosition();
				Eye = Eye + (Points[0] - Points[1]);

				Vector3 Target = Cam->GetTargetPosition();
				Target = Target + (Points[0] - Points[1]);

				// Zoom in/out.
				float RatioX = (Right - Left) / Viewport[2];
				float RatioY = (Top - Bottom) / Viewport[3];
				float ZoomFactor = -max(RatioX, RatioY) + 0.75f;

				Vector3 Dir = Points[1] - Points[2];
				Eye = Eye + Dir * ZoomFactor;
				Target = Target + Dir * ZoomFactor;

				// Change the camera and redraw.
			  Cam->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, Eye, LC_CK_EYE);
			  Cam->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, Target, LC_CK_TARGET);
			  Cam->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);

				SystemUpdateFocus(NULL);
				UpdateAllViews();
			} break;

			case LC_ACTION_INSERT:
			case LC_ACTION_LIGHT:
			case LC_ACTION_ERASER:
			case LC_ACTION_PAINT:
				break;
		}
	}
	else if (m_pTrackFile != NULL)
	{
		if ((m_nCurAction == LC_ACTION_SELECT) || (m_nCurAction == LC_ACTION_ZOOM_REGION))
		{
			UpdateAllViews();
		}
		else
		{
			DeleteContents (true);
			FileLoad (m_pTrackFile, true, false);
			delete m_pTrackFile;
			m_pTrackFile = NULL;
		}
	}

	return true;
}

void Project::StartTracking(int mode)
{
	SystemCaptureMouse();
	m_nTracking = mode;

  if (m_pTrackFile != NULL)
    m_pTrackFile->SetLength (0);
  else
  	m_pTrackFile = new FileMem;

	FileSave(m_pTrackFile, true);
}

void Project::GetSnapIndex(int* SnapXY, int* SnapZ) const
{
	*SnapXY = (m_nMoveSnap & 0xff);
	*SnapZ = ((m_nMoveSnap >> 8) & 0xff);
}

void Project::GetSnapDistance(float* SnapXY, float* SnapZ) const
{
	const float SnapXYTable[] = { 0.01f, 0.04f, 0.2f, 0.32f, 0.4f, 0.8f, 1.6f, 2.4f, 3.2f, 6.4f };
	const float SnapZTable[] = { 0.01f, 0.04f, 0.2f, 0.32f, 0.4f, 0.8f, 0.96f, 1.92f, 3.84f, 7.68f };

	int SXY, SZ;
	GetSnapIndex(&SXY, &SZ);

	SXY = min(SXY, 9);
	SZ = min(SZ, 9);

	*SnapXY = SnapXYTable[SXY];
	*SnapZ = SnapZTable[SZ];
}

void Project::GetSnapDistanceText(char* SnapXY, char* SnapZ) const
{
	if (m_nSnap & LC_DRAW_CM_UNITS)
	{
		float xy, z;

		GetSnapDistance(&xy, &z);

		sprintf(SnapXY, "%.2f", xy);
		sprintf(SnapZ, "%.2f", z);
	}
	else
	{
		const char* SnapXYText[] = { "0", "1/20S", "1/4S", "1F", "1/2S", "1S", "2S", "3S", "4S", "8S" };
		const char* SnapZText[] = { "0", "1/20S", "1/4S", "1F", "1/2S", "1S", "1B", "2B", "4B", "8B" };

		int SXY, SZ;
		GetSnapIndex(&SXY, &SZ);

		SXY = min(SXY, 9);
		SZ = min(SZ, 9);

		strcpy(SnapXY, SnapXYText[SXY]);
		strcpy(SnapZ, SnapZText[SZ]);
	}
}

void Project::SnapVector(Vector3& Delta, Vector3& Leftover) const
{
	float SnapXY, SnapZ;
	GetSnapDistance(&SnapXY, &SnapZ);

	if (m_nSnap & LC_DRAW_SNAP_X)
	{
		int i = (int)(Delta[0] / SnapXY);
		Leftover[0] = Delta[0] - (SnapXY * i);

		if (Leftover[0] > SnapXY / 2)
		{
			Leftover[0] -= SnapXY;
			i++;
		}
		else if (Leftover[0] < -SnapXY / 2)
		{
			Leftover[0] += SnapXY;
			i--;
		}

		Delta[0] = SnapXY * i;
	}

	if (m_nSnap & LC_DRAW_SNAP_Y)
	{
		int i = (int)(Delta[1] / SnapXY);
		Leftover[1] = Delta[1] - (SnapXY * i);

		if (Leftover[1] > SnapXY / 2)
		{
			Leftover[1] -= SnapXY;
			i++;
		}
		else if (Leftover[1] < -SnapXY / 2)
		{
			Leftover[1] += SnapXY;
			i--;
		}

		Delta[1] = SnapXY * i;
	}

	if (m_nSnap & LC_DRAW_SNAP_Z)
	{
		int i = (int)(Delta[2] / SnapZ);
		Leftover[2] = Delta[2] - (SnapZ * i);

		if (Leftover[2] > SnapZ / 2)
		{
			Leftover[2] -= SnapZ;
			i++;
		}
		else if (Leftover[2] < -SnapZ / 2)
		{
			Leftover[2] += SnapZ;
			i--;
		}

		Delta[2] = SnapZ * i;
	}
}

void Project::SnapRotationVector(Vector3& Delta, Vector3& Leftover) const
{
	if (m_nSnap & LC_DRAW_SNAP_A)
	{
		int Snap[3];

		for (int i = 0; i < 3; i++)
		{
			Snap[i] = (int)(Delta[i] / (float)m_nAngleSnap);
		}

		Vector3 NewDelta((float)(m_nAngleSnap * Snap[0]), (float)(m_nAngleSnap * Snap[1]), (float)(m_nAngleSnap * Snap[2]));
		Leftover = Delta - NewDelta;
		Delta = NewDelta;
	}
}

bool Project::MoveSelectedObjects(Vector3& Move, Vector3& Remainder)
{
	// Don't move along locked directions.
	if (m_nSnap & LC_DRAW_LOCK_X)
		Move[0] = 0;

	if (m_nSnap & LC_DRAW_LOCK_Y)
		Move[1] = 0;

	if (m_nSnap & LC_DRAW_LOCK_Z)
		Move[2] = 0;

	// Snap.
	SnapVector(Move, Remainder);

	if (Move.LengthSquared() < 0.001f)
		return false;

	// Transform the translation if we're in relative mode.
	if ((m_nSnap & LC_DRAW_GLOBAL_SNAP) == 0)
	{
		Object* Focus = GetFocusObject();

		if ((Focus != NULL) && Focus->IsPiece())
		{
			float Rot[4];
			((Piece*)Focus)->GetRotation(Rot);

			Matrix33 RotMat;
			RotMat.CreateFromAxisAngle(Vector3(Rot[0], Rot[1], Rot[2]), Rot[3] * LC_DTOR);

			Move = Mul(Move, RotMat);
		}
	}

	if (Move.LengthSquared() < 0.001f)
		return false;

	Piece* pPiece;
	Camera* pCamera;
	Light* pLight;
	float x = Move[0], y = Move[1], z = Move[2];

	for (pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
		if (pCamera->IsSelected())
		{
			pCamera->Move(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, x, y, z);
			pCamera->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
		}

	for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
	  if (pLight->IsSelected())
	  {
	    pLight->Move (m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, x, y, z);
	    pLight->UpdatePosition (m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
	  }

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		if (pPiece->IsSelected())
		{
			pPiece->Move(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, x, y, z);
			pPiece->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
		}

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		if (pPiece->IsSelected())
			pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, false, true);

	// TODO: move group centers

	if (m_OverlayActive)
	{
		if (!GetFocusPosition(m_OverlayCenter))
			GetSelectionCenter(m_OverlayCenter);
	}

	return true;
}

bool Project::RotateSelectedObjects(Vector3& Delta, Vector3& Remainder)
{
	// Don't move along locked directions.
	if (m_nSnap & LC_DRAW_LOCK_X)
		Delta[0] = 0;

	if (m_nSnap & LC_DRAW_LOCK_Y)
		Delta[1] = 0;

	if (m_nSnap & LC_DRAW_LOCK_Z)
		Delta[2] = 0;

	// Snap.
	SnapRotationVector(Delta, Remainder);

	if (Delta.LengthSquared() < 0.001f)
		return false;

	float bs[6] = { 10000, 10000, 10000, -10000, -10000, -10000 };
	float pos[3], rot[4];
	int nSel = 0;
	Piece *pPiece, *pFocus = NULL;

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
	{
		if (pPiece->IsSelected())
		{
			if (pPiece->IsFocused())
				pFocus = pPiece;

			pPiece->CompareBoundingBox(bs);
			nSel++;
		}
	}

	if (pFocus != NULL)
	{
		pFocus->GetPosition(pos);
		bs[0] = bs[3] = pos[0];
		bs[1] = bs[4] = pos[1];
		bs[2] = bs[5] = pos[2];
	}

	Vector3 Center((bs[0]+bs[3])/2, (bs[1]+bs[4])/2, (bs[2]+bs[5])/2);

	// Create the rotation matrix.
	Quaternion Rotation(0, 0, 0, 1);
	Quaternion WorldToFocus, FocusToWorld;

	if (!(m_nSnap & LC_DRAW_LOCK_X) && (Delta[0] != 0.0f))
	{
		Quaternion q;
		q.CreateRotationX(Delta[0] * LC_DTOR);
		Rotation = Mul(q, Rotation);
	}

	if (!(m_nSnap & LC_DRAW_LOCK_Y) && (Delta[1] != 0.0f))
	{
		Quaternion q;
		q.CreateRotationY(Delta[1] * LC_DTOR);
		Rotation = Mul(q, Rotation);
	}

	if (!(m_nSnap & LC_DRAW_LOCK_Z) && (Delta[2] != 0.0f))
	{
		Quaternion q;
		q.CreateRotationZ(Delta[2] * LC_DTOR);
		Rotation = Mul(q, Rotation);
	}

	// Transform the rotation relative to the focused piece.
	if (m_nSnap & LC_DRAW_GLOBAL_SNAP)
		pFocus = NULL;

	if (pFocus != NULL)
	{
		float Rot[4];
		((Piece*)pFocus)->GetRotation(Rot);

		WorldToFocus.FromAxisAngle(Vector4(Rot[0], Rot[1], Rot[2], -Rot[3] * LC_DTOR));
		FocusToWorld.FromAxisAngle(Vector4(Rot[0], Rot[1], Rot[2], Rot[3] * LC_DTOR));

		Rotation = Mul(FocusToWorld, Rotation);
	}

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
	{
		if (!pPiece->IsSelected())
			continue;

		pPiece->GetPosition(pos);
		pPiece->GetRotation(rot);

		Vector4 NewRotation;

		if ((nSel == 1) && (pFocus == pPiece))
		{
			Quaternion LocalToWorld;
			LocalToWorld.FromAxisAngle(Vector4(rot[0], rot[1], rot[2], rot[3] * LC_DTOR));

			Quaternion NewLocalToWorld;

			if (pFocus != NULL)
			{
				Quaternion LocalToFocus = Mul(WorldToFocus, LocalToWorld);
				NewLocalToWorld = Mul(LocalToFocus, Rotation);
			}
			else
			{
				NewLocalToWorld = Mul(Rotation, LocalToWorld);
			}

			NewLocalToWorld.ToAxisAngle(NewRotation);
		}
		else
		{
			Vector3 Distance = Vector3(pos[0], pos[1], pos[2]) - Center;

			Quaternion LocalToWorld;
			LocalToWorld.FromAxisAngle(Vector4(rot[0], rot[1], rot[2], rot[3] * LC_DTOR));

			Quaternion NewLocalToWorld;

			if (pFocus != NULL)
			{
				Quaternion LocalToFocus = Mul(WorldToFocus, LocalToWorld);
				NewLocalToWorld = Mul(Rotation, LocalToFocus);

				Quaternion WorldToLocal;
				WorldToLocal.FromAxisAngle(Vector4(rot[0], rot[1], rot[2], -rot[3] * LC_DTOR));

				Distance = Mul(Distance, WorldToLocal);
				Distance = Mul(Distance, NewLocalToWorld);
			}
			else
			{
				NewLocalToWorld = Mul(Rotation, LocalToWorld);

				Distance = Mul(Distance, Rotation);
			}

			NewLocalToWorld.ToAxisAngle(NewRotation);

			pos[0] = Center[0] + Distance[0];
			pos[1] = Center[1] + Distance[1];
			pos[2] = Center[2] + Distance[2];

			pPiece->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, pos, LC_PK_POSITION);
		}

		rot[0] = NewRotation[0];
		rot[1] = NewRotation[1];
		rot[2] = NewRotation[2];
		rot[3] = NewRotation[3] * LC_RTOD;

		pPiece->ChangeKey(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, rot, LC_PK_ROTATION);
		pPiece->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);
	}

	for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
		if (pPiece->IsSelected())
			pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, false, true);

	if (m_OverlayActive)
	{
		if (!GetFocusPosition(m_OverlayCenter))
			GetSelectionCenter(m_OverlayCenter);
	}

	return true;
}

bool Project::OnKeyDown(char nKey, bool bControl, bool bShift)
{
	bool ret = false;

	if (IsDrawing())
		return false;

	// FIXME: Almost all of this should go through the keyboard shortcut system.
	switch (nKey)
	{
		case KEY_ESCAPE:
		{
			if (m_nTracking != LC_TRACK_NONE)
				StopTracking(false);
			ret = true;
		} break;

		case KEY_INSERT:
		{
			HandleCommand(LC_PIECE_INSERT, 0);
			ret = true;
		} break;

		case KEY_DELETE:
		{
			HandleCommand(LC_PIECE_DELETE, 0);
			ret = true;
		} break;

		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
		{
			if (bControl)
			{
				m_nCurClipboard = nKey - 0x30;
				SystemUpdatePaste(m_pClipboard[m_nCurClipboard] != NULL);
				ret = true;
			}
		} break;


		case KEY_PLUS: // case '+': case '=':
		{
			if (bShift)
				HandleCommand(LC_VIEW_ZOOM, -10);
			else
				HandleCommand(LC_VIEW_ZOOM, -1);

			ret = true;
		} break;
			
		case KEY_MINUS: // case '-': case '_':
		{
			if (bShift)
				HandleCommand(LC_VIEW_ZOOM, 10);
			else
				HandleCommand(LC_VIEW_ZOOM, 1);

			ret = true;
		} break;

		case KEY_TAB:
		{
			if (m_pPieces == NULL)
				break;

			Piece* pFocus = NULL, *pPiece;
			for (pFocus = m_pPieces; pFocus; pFocus = pFocus->m_pNext)
				if (pFocus->IsFocused())
					break;

			SelectAndFocusNone(false);

			if (pFocus == NULL)
			{
				if (bShift)
				{
					// Focus the last visible piece.
					for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
						if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
							pFocus = pPiece;
				}
				else
				{
					// Focus the first visible piece.
					for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
						if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
						{
							pFocus = pPiece;
							break;
						}
				}
			}
			else
			{
				if (bShift)
				{
					// Focus the previous visible piece.
					Piece* pBest = pPiece = pFocus;
					for (;;)
					{
						if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
							pBest = pPiece;

						if (pPiece->m_pNext != NULL)
						{
							if (pPiece->m_pNext == pFocus)
								break;
							else
								pPiece = pPiece->m_pNext;
						}
						else
						{
							if (pFocus == m_pPieces)
								break;
							else
								pPiece = m_pPieces;
						}
					}

					pFocus = pBest;
				}
				else
				{
					// Focus the next visible piece.
					pPiece = pFocus;
					for (;;)
					{
						if (pPiece != pFocus)
							if (pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation))
							{
								pFocus = pPiece;
								break;
							}

						if (pPiece->m_pNext != NULL)
						{
							if (pPiece->m_pNext == pFocus)
								break;
							else
								pPiece = pPiece->m_pNext;
						}
						else
						{
							if (pFocus == m_pPieces)
								break;
							else
								pPiece = m_pPieces;
						}
					}
				}
			}

			if (pFocus != NULL)
			{
        pFocus->Select (true, true, false);
        Group* pGroup = pFocus->GetTopGroup();
        if (pGroup != NULL)
        {
          for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
            if ((pPiece->IsVisible(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation)) &&
                (pPiece->GetTopGroup() == pGroup))
              pPiece->Select (true, false, false);
        }
			}

			UpdateSelection();
			UpdateAllViews();
			SystemUpdateFocus(pFocus);
			ret = true;
		} break;

		case KEY_UP:    case KEY_DOWN: case KEY_LEFT: 
		case KEY_RIGHT: case KEY_NEXT: case KEY_PRIOR:
//		if (AnyObjectSelected(FALSE))
		{
			Vector3 axis;
			if (bShift)
			{
				if (m_nSnap & LC_DRAW_SNAP_A)
					axis[0] = axis[1] = axis[2] = m_nAngleSnap;
				else
					axis[0] = axis[1] = axis[2] = 1;
			}
			else
			{
				float xy, z;
				GetSnapDistance(&xy, &z);

				axis[0] = axis[1] = xy;
				axis[2] = z;

				if (((m_nSnap & LC_DRAW_SNAP_X) == 0) || bControl)
					axis[0] = 0.01f;
				if (((m_nSnap & LC_DRAW_SNAP_Y) == 0) || bControl)
					axis[1] = 0.01f;
				if (((m_nSnap & LC_DRAW_SNAP_Z) == 0) || bControl)
					axis[2] = 0.01f;
			}

			if (m_nSnap & LC_DRAW_MOVEAXIS)
			{
				switch (nKey)
				{
					case KEY_UP: {
						axis[1] = axis[2] = 0; axis[0] = -axis[0];
					} break;
					case KEY_DOWN: {
						axis[1] = axis[2] = 0;
					} break;
					case KEY_LEFT: {
						axis[0] = axis[2] = 0; axis[1] = -axis[1];
					} break;
					case KEY_RIGHT: {
						axis[0] = axis[2] = 0;
					} break;
					case KEY_NEXT: {
						axis[0] = axis[1] = 0; axis[2] = -axis[2];
					} break;
					case KEY_PRIOR: {
						axis[0] = axis[1] = 0;
					} break;
				}
			}
			else
			{
        Camera *camera = m_pViewCameras[m_nActiveViewport];

        if (camera->IsSide ())
        {
          Matrix mat;

          mat.CreateLookat (camera->GetEyePos (), camera->GetTargetPos (), camera->GetUpVec ());
          mat.SetTranslation (0, 0, 0);
          mat.Invert ();

  				switch (nKey)
	  			{
		  			case KEY_UP:
			  			axis[0] = axis[2] = 0;
              break;

            case KEY_DOWN:
	  					axis[0] = axis[2] = 0; axis[1] = -axis[1];
              break;

            case KEY_LEFT:
				  		axis[0] = -axis[0]; axis[1] = axis[2] = 0;
              break;

            case KEY_RIGHT:
              axis[1] = axis[2] = 0;
              break;

            case KEY_NEXT:
              axis[0] = axis[1] = 0; axis[2] = -axis[2];
              break;

            case KEY_PRIOR:
              axis[0] = axis[1] = 0;
              break;
          }

          mat.TransformPoints (axis, 1);
        }
        else
        {

          // TODO: rewrite this

  				switch (nKey)
	  			{
		  			case KEY_UP: {
			  			axis[1] = axis[2] = 0; axis[0] = -axis[0];
				  	} break;
					  case KEY_DOWN: {
              axis[1] = axis[2] = 0;
            } break;
            case KEY_LEFT: {
              axis[0] = axis[2] = 0; axis[1] = -axis[1];
            } break;
            case KEY_RIGHT: {
              axis[0] = axis[2] = 0;
            } break;
            case KEY_NEXT: {
              axis[0] = axis[1] = 0; axis[2] = -axis[2];
            } break;
            case KEY_PRIOR: {
              axis[0] = axis[1] = 0;
            } break;
          }

  				GLdouble modelMatrix[16], projMatrix[16], p1[3], p2[3], p3[3];
	  			float ax, ay;
		  		GLint viewport[4];

          glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
          glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
          glGetIntegerv(GL_VIEWPORT, viewport);
          gluUnProject( 5, 5, 0.1, modelMatrix,projMatrix,viewport,&p1[0],&p1[1],&p1[2]);
          gluUnProject(10, 5, 0.1, modelMatrix,projMatrix,viewport,&p2[0],&p2[1],&p2[2]);
          gluUnProject( 5,10, 0.1, modelMatrix,projMatrix,viewport,&p3[0],&p3[1],&p3[2]);
				
          Vector vx((float)(p2[0] - p1[0]), (float)(p2[1] - p1[1]), 0);//p2[2] - p1[2] };
          Vector x(1, 0, 0);
          ax = vx.Angle(x);
				
          Vector vy((float)(p3[0] - p1[0]), (float)(p3[1] - p1[1]), 0);//p2[2] - p1[2] };
          Vector y(0, -1, 0);
          ay = vy.Angle(y);
				
          if (ax > 135)
            axis[0] = -axis[0];
				
          if (ay < 45)
            axis[1] = -axis[1];
				
          if (ax >= 45 && ax <= 135)
          {
            float tmp = axis[0];
            
            ax = vx.Angle(y);
            if (ax > 90)
            {
              axis[0] = -axis[1];
              axis[1] = tmp;
            }
            else
            {
              axis[0] = axis[1];
              axis[1] = -tmp;
            }
          }
        }
      }

			if (bShift)
			{
				Vector3 tmp;
				RotateSelectedObjects(axis, tmp);
			}
			else
			{
				Vector3 tmp;
				MoveSelectedObjects(axis, tmp);
			}

			UpdateOverlayScale();
			UpdateAllViews();
			SetModifiedFlag(true);
			CheckPoint((bShift) ? "Rotating" : "Moving");
			SystemUpdateFocus(NULL);
			ret = true;
		} break;
	}

	return ret;
}

void Project::BeginPieceDrop(PieceInfo* Info)
{
	StartTracking(LC_TRACK_LEFT);

	m_PreviousAction = m_nCurAction;
	m_PreviousPiece = m_pCurPiece;

	m_pCurPiece = Info;
	m_pCurPiece->AddRef();
	SetAction(LC_ACTION_INSERT);
}

void Project::OnLeftButtonDown(int x, int y, bool bControl, bool bShift)
{
	GLdouble modelMatrix[16], projMatrix[16], point[3];
	GLint viewport[4];

	if (IsDrawing())
		return;

	if (m_nTracking != LC_TRACK_NONE)
		if (StopTracking(false))
			return;

	if (SetActiveViewport(x, y))
		return;

	m_bTrackCancel = false;
	m_nDownX = x;
	m_nDownY = y;
	m_MouseTotalDelta = Vector3(0, 0, 0);
	m_MouseSnapLeftover = Vector3(0, 0, 0);

	LoadViewportProjection(m_nActiveViewport);
	glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
	glGetIntegerv(GL_VIEWPORT, viewport);

	gluUnProject(x, y, 0.9, modelMatrix, projMatrix, viewport, &point[0], &point[1], &point[2]);
	m_fTrack[0] = (float)point[0]; m_fTrack[1] = (float)point[1]; m_fTrack[2] = (float)point[2];

	switch (m_nCurAction)
	{
		case LC_ACTION_SELECT:
		case LC_ACTION_ERASER:
		case LC_ACTION_PAINT:
		{
			LC_CLICKLINE ClickLine;
			FindObjectFromPoint (x, y, &ClickLine);

			if (m_nCurAction == LC_ACTION_SELECT) 
			{
				if (ClickLine.pClosest != NULL)
				{
					switch (ClickLine.pClosest->GetType ())
					{
						case LC_OBJECT_PIECE:
						{
							Piece* pPiece = (Piece*)ClickLine.pClosest;
							Group* pGroup = pPiece->GetTopGroup();
							bool bFocus = pPiece->IsFocused ();

							SelectAndFocusNone (bControl);

							// if a piece has focus deselect it, otherwise set the focus
							pPiece->Select (!bFocus, !bFocus, false);

							if (pGroup != NULL)
								for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
									if (pPiece->GetTopGroup() == pGroup)
										pPiece->Select (!bFocus, false, false);
						} break;

						case LC_OBJECT_CAMERA:
						case LC_OBJECT_CAMERA_TARGET:
						case LC_OBJECT_LIGHT:
						case LC_OBJECT_LIGHT_TARGET:
						{
							SelectAndFocusNone (bControl);
							ClickLine.pClosest->Select (true, true, bControl);
						} break;
					}
				}
				else
					SelectAndFocusNone (bControl);

				UpdateSelection();
				UpdateAllViews();
				SystemUpdateFocus(ClickLine.pClosest);

				StartTracking(LC_TRACK_START_LEFT);
			}

			if ((m_nCurAction == LC_ACTION_ERASER) && (ClickLine.pClosest != NULL))
			{
				switch (ClickLine.pClosest->GetType ())
				{
					case LC_OBJECT_PIECE:
					{
						Piece* pPiece = (Piece*)ClickLine.pClosest;
						RemovePiece(pPiece);
						delete pPiece;
//						CalculateStep();
						RemoveEmptyGroups();
					} break;

					case LC_OBJECT_CAMERA:
					case LC_OBJECT_CAMERA_TARGET:
					{
						Camera* pCamera;
						if (ClickLine.pClosest->GetType () == LC_OBJECT_CAMERA)
							pCamera = (Camera*)ClickLine.pClosest;
						else
							pCamera = ((CameraTarget*)ClickLine.pClosest)->GetParent();
						bool bCanDelete = pCamera->IsUser();

						for (int i = 0; i < 4; i++)
							if (pCamera == m_pViewCameras[i])
								bCanDelete = false;

						if (bCanDelete)
						{
							Camera* pPrev;
							for (pPrev = m_pCameras; pPrev; pPrev = pPrev->m_pNext)
								if (pPrev->m_pNext == pCamera)
								{
									pPrev->m_pNext = pCamera->m_pNext;
									delete pCamera;
									SystemUpdateCameraMenu(m_pCameras);
									SystemUpdateCurrentCamera(NULL, m_pViewCameras[m_nActiveViewport], m_pCameras);
									break;
								}
						}
					} break;

					case LC_OBJECT_LIGHT:
					case LC_OBJECT_LIGHT_TARGET:
					{
//						pos = m_Lights.Find(pObject->m_pParent);
//						m_Lights.RemoveAt(pos);
//						delete pObject->m_pParent;
					} break;
				}

				UpdateSelection();
				UpdateAllViews();
				SetModifiedFlag(true);
				CheckPoint("Deleting");
//				AfxGetMainWnd()->PostMessage(WM_LC_UPDATE_INFO, NULL, OT_PIECE);
			}

			if ((m_nCurAction == LC_ACTION_PAINT) && (ClickLine.pClosest != NULL) && 
				(ClickLine.pClosest->GetType() == LC_OBJECT_PIECE))
			{
				Piece* pPiece = (Piece*)ClickLine.pClosest;

				if (pPiece->GetColor() != m_nCurColor)
				{
					bool bTrans = pPiece->IsTransparent();
					pPiece->SetColor(m_nCurColor);
					if (bTrans != pPiece->IsTransparent())
						pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, true, true);

					SetModifiedFlag(true);
					CheckPoint("Painting");
					SystemUpdateFocus(NULL);
					UpdateAllViews();
				}
			}
		} break;

		case LC_ACTION_INSERT:
		case LC_ACTION_LIGHT:
		{
			if (m_nCurAction == LC_ACTION_INSERT)
			{
				Vector3 Pos;
				Vector4 Rot;

				GetPieceInsertPosition(x, y, Pos, Rot);

				Piece* pPiece = new Piece(m_pCurPiece);
				pPiece->Initialize(Pos[0], Pos[1], Pos[2], m_nCurStep, m_nCurFrame, m_nCurColor);

				pPiece->ChangeKey(m_nCurStep, false, false, Rot, LC_PK_ROTATION);
				pPiece->ChangeKey(m_nCurFrame, true, false, Rot, LC_PK_ROTATION);
				pPiece->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);

				SelectAndFocusNone(false);
				pPiece->CreateName(m_pPieces);
				AddPiece(pPiece);
				pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, false, true);
				pPiece->Select (true, true, false);
				UpdateSelection();
				SystemPieceComboAdd(m_pCurPiece->m_strDescription);
				SystemUpdateFocus(pPiece);

				if (m_nSnap & LC_DRAW_MOVE)
					SetAction(LC_ACTION_MOVE);
			}
			else if (m_nCurAction == LC_ACTION_LIGHT)
			{
				GLint max;
				int count = 0;
				Light *pLight;

				glGetIntegerv (GL_MAX_LIGHTS, &max);
				for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
					count++;

				if (count == max)
					break;

				pLight = new Light (m_fTrack[0], m_fTrack[1], m_fTrack[2]);

				SelectAndFocusNone (false);

				pLight->CreateName(m_pLights);
				pLight->m_pNext = m_pLights;
				m_pLights = pLight;
				SystemUpdateFocus (pLight);
				pLight->Select (true, true, false);
				UpdateSelection ();
			}

//			AfxGetMainWnd()->PostMessage(WM_LC_UPDATE_INFO, (WPARAM)pNew, OT_PIECE);
			UpdateSelection();
			UpdateAllViews();
			SetModifiedFlag(true);
			CheckPoint("Inserting");
		} break;

    case LC_ACTION_SPOTLIGHT:
    {
      GLint max;
      int count = 0;
      Light *pLight;

      glGetIntegerv (GL_MAX_LIGHTS, &max);
      for (pLight = m_pLights; pLight; pLight = pLight->m_pNext)
        count++;

      if (count == max)
        break;

      double tmp[3];
      gluUnProject(x+1, y-1, 0.9, modelMatrix, projMatrix, viewport, &tmp[0], &tmp[1], &tmp[2]);
      SelectAndFocusNone(false);
      StartTracking(LC_TRACK_START_LEFT);
      pLight = new Light (m_fTrack[0], m_fTrack[1], m_fTrack[2], (float)tmp[0], (float)tmp[1], (float)tmp[2]);
      pLight->GetTarget ()->Select (true, true, false);
      pLight->m_pNext = m_pLights;
      m_pLights = pLight;
      UpdateSelection();
      UpdateAllViews();
      SystemUpdateFocus(pLight);
    } break;

    case LC_ACTION_CAMERA:
    {
      double tmp[3];
      gluUnProject(x+1, y-1, 0.9, modelMatrix, projMatrix, viewport, &tmp[0], &tmp[1], &tmp[2]);
      SelectAndFocusNone(false);
      StartTracking(LC_TRACK_START_LEFT);
      Camera* pCamera = new Camera(m_fTrack[0], m_fTrack[1], m_fTrack[2], (float)tmp[0], (float)tmp[1], (float)tmp[2], m_pCameras);
      pCamera->GetTarget ()->Select (true, true, false);
      UpdateSelection();
      UpdateAllViews();
      SystemUpdateFocus(pCamera);
    } break;

		case LC_ACTION_MOVE:
		{
			bool sel = false;

			for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				if (pPiece->IsSelected())
				{
					sel = true;
					break;
				}
			}

			if (!sel)
			{
				for (Camera* pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
				{
					if (pCamera->IsSelected())
					{
						sel = true;
						break;
					}
				}
			}

			if (!sel)
			{
				for (Light* pLight = m_pLights; pLight; pLight = pLight->m_pNext)
				{
					if (pLight->IsSelected())
					{
						sel = true;
						break;
					}
				}
			}

			if (sel)
			{
				StartTracking(LC_TRACK_START_LEFT);
				m_OverlayDelta = Vector3(0.0f, 0.0f, 0.0f);
				m_MouseSnapLeftover = Vector3(0.0f, 0.0f, 0.0f);
			}
		} break;

		case LC_ACTION_ROTATE:
		{
			Piece* pPiece;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
			{
				if (pPiece->IsSelected())
				{
					StartTracking(LC_TRACK_START_LEFT);
					m_OverlayDelta = Vector3(0.0f, 0.0f, 0.0f);
					break;
				}
			}
		} break;

		case LC_ACTION_ZOOM_REGION:
		{
			m_OverlayTrackStart[0] = (float)x;
			m_OverlayTrackStart[1] = (float)y;
			StartTracking(LC_TRACK_START_LEFT);
			ActivateOverlay();
		} break;

		case LC_ACTION_ZOOM:
		case LC_ACTION_ROLL:
		case LC_ACTION_PAN:
		case LC_ACTION_ROTATE_VIEW:
		{
			StartTracking(LC_TRACK_START_LEFT);
		} break;
	}
}

void Project::OnLeftButtonDoubleClick(int x, int y, bool bControl, bool bShift)
{
  GLdouble modelMatrix[16], projMatrix[16], point[3];
  GLint viewport[4];

  if (IsDrawing())
    return;

  if (SetActiveViewport(x, y))
    return;

  LoadViewportProjection(m_nActiveViewport);
  glGetDoublev (GL_MODELVIEW_MATRIX, modelMatrix);
  glGetDoublev (GL_PROJECTION_MATRIX, projMatrix);
  glGetIntegerv (GL_VIEWPORT, viewport);

  // why this is here ?
  gluUnProject(x, y, 0.9, modelMatrix, projMatrix, viewport, &point[0], &point[1], &point[2]);
  m_fTrack[0] = (float)point[0]; m_fTrack[1] = (float)point[1]; m_fTrack[2] = (float)point[2];

  LC_CLICKLINE ClickLine;
  FindObjectFromPoint (x, y, &ClickLine);

//  if (m_nCurAction == LC_ACTION_SELECT) 
  {
    SelectAndFocusNone(bControl);

    if (ClickLine.pClosest != NULL)
      switch (ClickLine.pClosest->GetType ())
      {
        case LC_OBJECT_PIECE:
        {
          Piece* pPiece = (Piece*)ClickLine.pClosest;
          pPiece->Select (true, true, false);
          Group* pGroup = pPiece->GetTopGroup();

          if (pGroup != NULL)
            for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
              if (pPiece->GetTopGroup() == pGroup)
                pPiece->Select (true, false, false);
        } break;

        case LC_OBJECT_CAMERA:
        case LC_OBJECT_CAMERA_TARGET:
        case LC_OBJECT_LIGHT:
        case LC_OBJECT_LIGHT_TARGET:
        {
          ClickLine.pClosest->Select (true, true, bControl);
        } break;
      }

    UpdateSelection();
    UpdateAllViews();
    SystemUpdateFocus(ClickLine.pClosest);
  }
}

void Project::OnLeftButtonUp(int x, int y, bool bControl, bool bShift)
{
	if (m_nTracking == LC_TRACK_LEFT)
	{
		// Dragging a new piece from the tree.
		if (m_nCurAction == LC_ACTION_INSERT)
		{
			int Viewport[4];
			Viewport[0] = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX);
			Viewport[1] = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY);
			Viewport[2] = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX);
			Viewport[3] = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY);

			if ((x > Viewport[0]) && (x < Viewport[2]) && (y > Viewport[1]) && (y < Viewport[3]))
			{
				Vector3 Pos;
				Vector4 Rot;

				GetPieceInsertPosition(x, y, Pos, Rot);

				Piece* pPiece = new Piece(m_pCurPiece);
				pPiece->Initialize(Pos[0], Pos[1], Pos[2], m_nCurStep, m_nCurFrame, m_nCurColor);

				pPiece->ChangeKey(m_nCurStep, false, false, Rot, LC_PK_ROTATION);
				pPiece->ChangeKey(m_nCurFrame, true, false, Rot, LC_PK_ROTATION);
				pPiece->UpdatePosition(m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation);

				SelectAndFocusNone(false);
				pPiece->CreateName(m_pPieces);
				AddPiece(pPiece);
				pPiece->CalculateConnections(m_pConnections, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, false, true);
				pPiece->Select (true, true, false);
				UpdateSelection();
				SystemPieceComboAdd(m_pCurPiece->m_strDescription);
				SystemUpdateFocus(pPiece);

				if (m_nSnap & LC_DRAW_MOVE)
					SetAction(LC_ACTION_MOVE);
				else
					SetAction(m_PreviousAction);
			}
			else
				SetAction(m_PreviousAction);

			m_pCurPiece->DeRef();
			m_pCurPiece = m_PreviousPiece;
			m_PreviousPiece = NULL;
		}
	}

  StopTracking(true);
}

void Project::OnRightButtonDown(int x, int y, bool bControl, bool bShift)
{
	GLdouble modelMatrix[16], projMatrix[16], point[3];
	GLint viewport[4];

	if (StopTracking(false))
		return;
	if (SetActiveViewport(x, y))
		return;

	m_nDownX = x;
	m_nDownY = y;
	m_bTrackCancel = false;

	LoadViewportProjection(m_nActiveViewport);
	glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
	glGetIntegerv(GL_VIEWPORT, viewport);

	gluUnProject(x, y, 0.9, modelMatrix, projMatrix, viewport, &point[0], &point[1], &point[2]);
	m_fTrack[0] = (float)point[0]; m_fTrack[1] = (float)point[1]; m_fTrack[2] = (float)point[2];

	switch (m_nCurAction)
	{
		case LC_ACTION_MOVE:
		{
			bool sel = false;

			for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
				{
					sel = true;
					break;
				}

			if (!sel)
			for (Camera* pCamera = m_pCameras; pCamera; pCamera = pCamera->m_pNext)
				if (pCamera->IsSelected())
				{
					sel = true;
					break;
				}

			if (!sel)
			for (Light* pLight = m_pLights; pLight; pLight = pLight->m_pNext)
				if (pLight->IsSelected())
				{
					sel = true;
					break;
				}

			if (sel)
      {
				StartTracking(LC_TRACK_START_RIGHT);
        m_fTrack[0] = m_fTrack[1] = m_fTrack[2] = 0.0f;
      }
		} break;

		case LC_ACTION_ROTATE:
		{
			Piece* pPiece;

			for (pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
				{
					StartTracking(LC_TRACK_START_RIGHT);
          m_fTrack[0] = m_fTrack[1] = m_fTrack[2] = 0.0f;
					break;
				}
		} break;
	}
}

void Project::OnRightButtonUp(int x, int y, bool bControl, bool bShift)
{
	if (!StopTracking(true) && !m_bTrackCancel)
		SystemDoPopupMenu(1, -1, -1);
	m_bTrackCancel = false;
}

void Project::OnMouseMove(int x, int y, bool bControl, bool bShift)
{
	if ((m_nTracking == LC_TRACK_NONE) && (m_nCurAction != LC_ACTION_INSERT))
	{
		if (m_OverlayActive)
		{
			MouseUpdateOverlays(x, y);
		}

		return;
	}

	if (m_nTracking == LC_TRACK_START_RIGHT)
		m_nTracking = LC_TRACK_RIGHT;

	if (m_nTracking == LC_TRACK_START_LEFT)
		m_nTracking = LC_TRACK_LEFT;

	if (IsDrawing())
		return;

	GLdouble modelMatrix[16], projMatrix[16], tmp[3];
	GLint viewport[4];
	float ptx, pty, ptz;

	LoadViewportProjection(m_nActiveViewport);
	glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
	glGetIntegerv(GL_VIEWPORT, viewport);

	gluUnProject(x, y, 0.9, modelMatrix, projMatrix, viewport, &tmp[0], &tmp[1], &tmp[2]);
	ptx = (float)tmp[0]; pty = (float)tmp[1]; ptz = (float)tmp[2];

	switch (m_nCurAction)
	{
		case LC_ACTION_SELECT:
		{
			int ptx = x, pty = y;

			if (ptx >= viewport[0] + viewport[2])
				ptx = viewport[0] + viewport[2] - 1;
			else if (ptx <= viewport[0])
				ptx = viewport[0] + 1;

			if (pty >= viewport[1] + viewport[3])
				pty = viewport[1] + viewport[3] - 1;
			else if (pty <= viewport[1])
				pty = viewport[1] + 1;

			m_fTrack[0] = (float)ptx;
			m_fTrack[1] = (float)pty;

			UpdateAllViews();
		} break;

		case LC_ACTION_INSERT:
		{
			if (m_nDownX != x || m_nDownY != y)
			{
				m_nDownX = x;
				m_nDownY = y;

				UpdateAllViews();
			}
		}	break;

		case LC_ACTION_SPOTLIGHT:
		{
			float mouse = 10.0f/(21 - m_nMouse);
			float delta[3] = { (ptx - m_fTrack[0])*mouse,
				(pty - m_fTrack[1])*mouse, (ptz - m_fTrack[2])*mouse };

			m_fTrack[0] = ptx;
			m_fTrack[1] = pty;
			m_fTrack[2] = ptz;
			
			Light* pLight = m_pLights;

			pLight->Move (1, m_bAnimation, false, delta[0], delta[1], delta[2]);
			pLight->UpdatePosition (1, m_bAnimation);

			SystemUpdateFocus(NULL);
			UpdateAllViews();
		} break;

		case LC_ACTION_CAMERA:
		{
			float mouse = 10.0f/(21 - m_nMouse);
			float delta[3] = { (ptx - m_fTrack[0])*mouse,
				(pty - m_fTrack[1])*mouse, (ptz - m_fTrack[2])*mouse };

			m_fTrack[0] = ptx;
			m_fTrack[1] = pty;
			m_fTrack[2] = ptz;
			
			Camera* pCamera = m_pCameras;
			while (pCamera->m_pNext != NULL)
				pCamera = pCamera->m_pNext;

			pCamera->Move (1, m_bAnimation, false, delta[0], delta[1], delta[2]);
			pCamera->UpdatePosition(1, m_bAnimation);

			SystemUpdateFocus(NULL);
			UpdateAllViews();
		} break;

		case LC_ACTION_MOVE:
		{
			// Check if the mouse moved since the last update.
			if ((x == m_nDownX) && (y == m_nDownY))
				break;

			Camera* Camera = m_pViewCameras[m_nActiveViewport];
			bool Redraw;

			if ((m_OverlayActive && (m_OverlayMode != LC_OVERLAY_XYZ)) || (!Camera->IsSide()))
			{
				Vector3 ScreenX = Cross3(Camera->GetTargetPosition() - Camera->GetEyePosition(), Camera->GetUpVector());
				Vector3 ScreenY = Camera->GetUpVector();
				ScreenX.Normalize();
				Vector3 Dir1, Dir2;
				bool SingleDir = true;

				int OverlayMode;

				if (m_OverlayActive && (m_OverlayMode != LC_OVERLAY_XYZ))
					OverlayMode = m_OverlayMode;
				else if (m_nTracking == LC_TRACK_LEFT)
					OverlayMode = LC_OVERLAY_XY;
				else
					OverlayMode = LC_OVERLAY_Z;

				switch (OverlayMode)
				{
				case LC_OVERLAY_X:
					Dir1 = Vector3(1, 0, 0);
					break;
				case LC_OVERLAY_Y:
					Dir1 = Vector3(0, 1, 0);
					break;
				case LC_OVERLAY_Z:
					Dir1 = Vector3(0, 0, 1);
					break;
				case LC_OVERLAY_XY:
					Dir1 = Vector3(1, 0, 0);
					Dir2 = Vector3(0, 1, 0);
					SingleDir = false;
					break;
				case LC_OVERLAY_XZ:
					Dir1 = Vector3(1, 0, 0);
					Dir2 = Vector3(0, 0, 1);
					SingleDir = false;
					break;
				case LC_OVERLAY_YZ:
					Dir1 = Vector3(0, 1, 0);
					Dir2 = Vector3(0, 0, 1);
					SingleDir = false;
					break;
				}

				// Transform the translation axis.
				Vector3 Axis1 = Dir1;
				Vector3 Axis2 = Dir2;

				if ((m_nSnap & LC_DRAW_GLOBAL_SNAP) == 0)
				{
					Object* Focus = GetFocusObject();

					if ((Focus != NULL) && Focus->IsPiece())
					{
						float Rot[4];
						((Piece*)Focus)->GetRotation(Rot);

						Matrix33 RotMat;
						RotMat.CreateFromAxisAngle(Vector3(Rot[0], Rot[1], Rot[2]), Rot[3] * LC_DTOR);

						Axis1 = Mul(Dir1, RotMat);
						Axis2 = Mul(Dir2, RotMat);
					}
				}

				// Find out what direction the mouse is going to move stuff.
				Vector3 MoveX, MoveY;

				if (SingleDir)
				{
					float dx1 = Dot3(ScreenX, Axis1);
					float dy1 = Dot3(ScreenY, Axis1);

					if (fabsf(dx1) > fabsf(dy1))
					{
						if (dx1 >= 0.0f)
							MoveX = Dir1;
						else
							MoveX = -Dir1;

						MoveY = Vector3(0, 0, 0);
					}
					else
					{
						MoveX = Vector3(0, 0, 0);

						if (dy1 > 0.0f)
							MoveY = Dir1;
						else
							MoveY = -Dir1;
					}
				}
				else
				{
					float dx1 = Dot3(ScreenX, Axis1);
					float dy1 = Dot3(ScreenY, Axis1);
					float dx2 = Dot3(ScreenX, Axis2);
					float dy2 = Dot3(ScreenY, Axis2);

					if (fabsf(dx1) > fabsf(dx2))
					{
						if (dx1 >= 0.0f)
							MoveX = Dir1;
						else
							MoveX = -Dir1;

						if (dy2 >= 0.0f)
							MoveY = Dir2;
						else
							MoveY = -Dir2;
					}
					else
					{
						if (dx2 >= 0.0f)
							MoveX = Dir2;
						else
							MoveX = -Dir2;

						if (dy1 > 0.0f)
							MoveY = Dir1;
						else
							MoveY = -Dir1;
					}
				}

				MoveX *= (float)(x - m_nDownX) * 0.25f / (21 - m_nMouse);
				MoveY *= (float)(y - m_nDownY) * 0.25f / (21 - m_nMouse);

				m_nDownX = x;
				m_nDownY = y;

				Vector3 Delta = MoveX + MoveY + m_MouseSnapLeftover;
				Redraw = MoveSelectedObjects(Delta, m_MouseSnapLeftover);
				m_MouseTotalDelta += Delta;
			}
			else
			{
				// 3D movement.
				Vector3 ScreenZ = (Camera->GetTargetPosition() - Camera->GetEyePosition()).Normalize();
				Vector3 ScreenX = Cross3(ScreenZ, Camera->GetUpVector());
				Vector3 ScreenY = Camera->GetUpVector();

				Vector3 TotalMove;

				if (m_nTracking == LC_TRACK_LEFT)
				{
					Vector3 MoveX, MoveY;

					MoveX = ScreenX * (float)(x - m_nDownX) * 0.25f / (float)(21 - m_nMouse);
					MoveY = ScreenY * (float)(y - m_nDownY) * 0.25f / (float)(21 - m_nMouse);

					TotalMove = MoveX + MoveY + m_MouseSnapLeftover;
				}
				else
				{
					Vector3 MoveZ;

					MoveZ = ScreenZ * (float)(y - m_nDownY) * 0.25f / (float)(21 - m_nMouse);

					TotalMove = MoveZ + m_MouseSnapLeftover;
				}

				m_nDownX = x;
				m_nDownY = y;

				Redraw = MoveSelectedObjects(TotalMove, m_MouseSnapLeftover);
			}

			SystemUpdateFocus(NULL);
			if (Redraw)
				UpdateAllViews();
		} break;
		
		case LC_ACTION_ROTATE:
		{
			Camera* Camera = m_pViewCameras[m_nActiveViewport];
			bool Redraw;

			if ((m_OverlayActive && (m_OverlayMode != LC_OVERLAY_XYZ)) || (!Camera->IsSide()))
			{
				Vector3 ScreenX = Cross3(Camera->GetTargetPosition() - Camera->GetEyePosition(), Camera->GetUpVector());
				Vector3 ScreenY = Camera->GetUpVector();
				ScreenX.Normalize();
				Vector3 Dir1, Dir2;
				bool SingleDir = true;

				int OverlayMode;

				if (m_OverlayActive && (m_OverlayMode != LC_OVERLAY_XYZ))
					OverlayMode = m_OverlayMode;
				else if (m_nTracking == LC_TRACK_LEFT)
					OverlayMode = LC_OVERLAY_XY;
				else
					OverlayMode = LC_OVERLAY_Z;

				switch (OverlayMode)
				{
				case LC_OVERLAY_X:
					Dir1 = Vector3(1, 0, 0);
					break;
				case LC_OVERLAY_Y:
					Dir1 = Vector3(0, 1, 0);
					break;
				case LC_OVERLAY_Z:
					Dir1 = Vector3(0, 0, 1);
					break;
				case LC_OVERLAY_XY:
					Dir1 = Vector3(1, 0, 0);
					Dir2 = Vector3(0, 1, 0);
					SingleDir = false;
					break;
				case LC_OVERLAY_XZ:
					Dir1 = Vector3(1, 0, 0);
					Dir2 = Vector3(0, 0, 1);
					SingleDir = false;
					break;
				case LC_OVERLAY_YZ:
					Dir1 = Vector3(0, 1, 0);
					Dir2 = Vector3(0, 0, 1);
					SingleDir = false;
					break;
				}

				// Find out what direction the mouse is going to move stuff.
				Vector3 MoveX, MoveY;

				if (SingleDir)
				{
					float dx1 = Dot3(ScreenX, Dir1);
					float dy1 = Dot3(ScreenY, Dir1);

					if (fabsf(dx1) > fabsf(dy1))
					{
						if (dx1 >= 0.0f)
							MoveX = Dir1;
						else
							MoveX = -Dir1;

						MoveY = Vector3(0, 0, 0);
					}
					else
					{
						MoveX = Vector3(0, 0, 0);

						if (dy1 > 0.0f)
							MoveY = Dir1;
						else
							MoveY = -Dir1;
					}
				}
				else
				{
					float dx1 = Dot3(ScreenX, Dir1);
					float dy1 = Dot3(ScreenY, Dir1);
					float dx2 = Dot3(ScreenX, Dir2);
					float dy2 = Dot3(ScreenY, Dir2);

					if (fabsf(dx1) > fabsf(dx2))
					{
						if (dx1 >= 0.0f)
							MoveX = Dir1;
						else
							MoveX = -Dir1;

						if (dy2 >= 0.0f)
							MoveY = Dir2;
						else
							MoveY = -Dir2;
					}
					else
					{
						if (dx2 >= 0.0f)
							MoveX = Dir2;
						else
							MoveX = -Dir2;

						if (dy1 > 0.0f)
							MoveY = Dir1;
						else
							MoveY = -Dir1;
					}
				}

				MoveX *= (float)(x - m_nDownX) * 36.0f / (21 - m_nMouse);
				MoveY *= (float)(y - m_nDownY) * 36.0f / (21 - m_nMouse);

				m_nDownX = x;
				m_nDownY = y;

				Vector3 Delta = MoveX + MoveY + m_MouseSnapLeftover;
				Redraw = RotateSelectedObjects(Delta, m_MouseSnapLeftover);
				m_MouseTotalDelta += Delta;
			}
			else
			{
				// 3D movement.
				Vector3 ScreenZ = (Camera->GetTargetPosition() - Camera->GetEyePosition()).Normalize();
				Vector3 ScreenX = Cross3(ScreenZ, Camera->GetUpVector());
				Vector3 ScreenY = Camera->GetUpVector();

				Vector3 Delta;

				if (m_nTracking == LC_TRACK_LEFT)
				{
					Vector3 MoveX, MoveY;

					MoveX = ScreenX * (float)(x - m_nDownX) * 36.0f / (float)(21 - m_nMouse);
					MoveY = ScreenY * (float)(y - m_nDownY) * 36.0f / (float)(21 - m_nMouse);

					Delta = MoveX + MoveY + m_MouseSnapLeftover;
				}
				else
				{
					Vector3 MoveZ;

					MoveZ = ScreenZ * (float)(y - m_nDownY) * 36.0f / (float)(21 - m_nMouse);

					Delta = MoveZ + m_MouseSnapLeftover;
				}

				m_nDownX = x;
				m_nDownY = y;

				Redraw = RotateSelectedObjects(Delta, m_MouseSnapLeftover);
				m_MouseTotalDelta += Delta;
			}

			SystemUpdateFocus(NULL);
			if (Redraw)
				UpdateAllViews();
		} break;

		case LC_ACTION_ZOOM:
		{
			if (m_nDownY == y)
				break;

			m_pViewCameras[m_nActiveViewport]->DoZoom(y - m_nDownY, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys);
			m_nDownY = y;
			SystemUpdateFocus(NULL);
			UpdateAllViews();
		} break;

		case LC_ACTION_ZOOM_REGION:
		{
			if ((m_nDownY == y) && (m_nDownX == x))
				break;

			m_nDownX = x;
			m_nDownY = y;
			UpdateAllViews();
		} break;

		case LC_ACTION_PAN:
		{
			if ((m_nDownY == y) && (m_nDownX == x))
				break;

			m_pViewCameras[m_nActiveViewport]->DoPan(x - m_nDownX, y - m_nDownY, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys);
			m_nDownX = x;
			m_nDownY = y;
			SystemUpdateFocus(NULL);
			UpdateAllViews();
		} break;
		
		case LC_ACTION_ROTATE_VIEW:
		{
			if ((m_nDownY == y) && (m_nDownX == x))
				break;

			// We can't rotate the side cameras.
			if (m_pViewCameras[m_nActiveViewport]->IsSide())
			{
				float eye[3], target[3], up[3];
				m_pViewCameras[m_nActiveViewport]->GetEyePos(eye);
				m_pViewCameras[m_nActiveViewport]->GetTargetPos(target);
				m_pViewCameras[m_nActiveViewport]->GetUpVec(up);
				Camera* pCamera = new Camera(eye, target, up, m_pCameras);

				m_pViewCameras[m_nActiveViewport] = pCamera;
				SystemUpdateCameraMenu(m_pCameras);
				SystemUpdateCurrentCamera(NULL, pCamera, m_pCameras);
			}

			float bs[6] = { 10000, 10000, 10000, -10000, -10000, -10000 };
			for (Piece* pPiece = m_pPieces; pPiece; pPiece = pPiece->m_pNext)
				if (pPiece->IsSelected())
					pPiece->CompareBoundingBox(bs);
			bs[0] = (bs[0]+bs[3])/2;
			bs[1] = (bs[1]+bs[4])/2;
			bs[2] = (bs[2]+bs[5])/2;

			switch (m_OverlayMode)
			{
				case LC_OVERLAY_XYZ:
					m_pViewCameras[m_nActiveViewport]->DoRotate(x - m_nDownX, y - m_nDownY, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, bs);
					break;

				case LC_OVERLAY_X:
					m_pViewCameras[m_nActiveViewport]->DoRotate(x - m_nDownX, 0, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, bs);
					break;

				case LC_OVERLAY_Y:
					m_pViewCameras[m_nActiveViewport]->DoRotate(0, y - m_nDownY, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys, bs);
					break;

				case LC_OVERLAY_Z:
					m_pViewCameras[m_nActiveViewport]->DoRoll(x - m_nDownX, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys);
					break;
			}

			m_nDownX = x;
			m_nDownY = y;
			SystemUpdateFocus(NULL);
			UpdateAllViews();
		} break;
		
		case LC_ACTION_ROLL:
		{
			if (m_nDownX == x)
				break;

			m_pViewCameras[m_nActiveViewport]->DoRoll(x - m_nDownX, m_nMouse, m_bAnimation ? m_nCurFrame : m_nCurStep, m_bAnimation, m_bAddKeys);
			m_nDownX = x;
			SystemUpdateFocus(NULL);
			UpdateAllViews();
		} break;
		/*
    case LC_ACTION_CURVE:
    {
      float mouse = 10.0f/(21 - m_nMouse);
      float dx = (ptx - m_fTrack[0])*mouse;
      float dy = (pty - m_fTrack[1])*mouse;
      float dz = (ptz - m_fTrack[2])*mouse;
      Object *pObj = NULL;
      Curve *pCurve;

      m_fTrack[0] = ptx;
      m_fTrack[1] = pty;
      m_fTrack[2] = ptz;

      for (pObj = m_pObjects; pObj != NULL; pObj = pObj->m_pNext)
	if (pObj->IsSelected ())
	  break;

      if (pObj == NULL)
	break;
      pCurve = (Curve*)pObj;

      pCurve->Move (1, m_bAnimation, false, dx, dy, dz);
      pCurve->UpdatePosition(1, m_bAnimation);

      SystemUpdateFocus(NULL);
      UpdateAllViews();
    } break;
                */
	}
}

// Check if the mouse is over a different area of the overlay and redraw it.
void Project::MouseUpdateOverlays(int x, int y)
{
	const float OverlayScale = m_OverlayScale[m_nActiveViewport];

	if (m_nCurAction == LC_ACTION_MOVE)
	{
		const float OverlayMoveArrowSize = 1.5f;

		Matrix44 ModelView, Projection;
		int Viewport[4];

		GetActiveViewportMatrices(ModelView, Projection, Viewport);

		// Array of points for the arrow edges.
		Vector3 Points[4] =
		{
			Vector3(m_OverlayCenter[0], m_OverlayCenter[1], m_OverlayCenter[2]),
			Vector3(OverlayMoveArrowSize * OverlayScale, 0, 0),
			Vector3(0, OverlayMoveArrowSize * OverlayScale, 0),
			Vector3(0, 0, OverlayMoveArrowSize * OverlayScale),
		};

		// Find the rotation from the focused piece if relative snap is enabled.
		if ((m_nSnap & LC_DRAW_GLOBAL_SNAP) == 0)
		{
			Object* Focus = GetFocusObject();

			if ((Focus != NULL) && Focus->IsPiece())
			{
				float Rot[4];
				((Piece*)Focus)->GetRotation(Rot);

				Matrix33 RotMat;
				RotMat.CreateFromAxisAngle(Vector3(Rot[0], Rot[1], Rot[2]), Rot[3] * LC_DTOR);

				for (int i = 1; i < 4; i++)
					Points[i] = Mul(Points[i], RotMat);
			}
		}

		int i, Mode = -1;
		Vector3 Pt((float)x, (float)y, 0);

		for (i = 1; i < 4; i++)
			Points[i] += Points[0];

		ProjectPoints(Points, 4, ModelView, Projection, Viewport);

		// Check if the mouse is over an arrow.
		for (i = 1; i < 4; i++)
		{
			Vector3 Line = Points[i] - Points[0];
			Vector3 Vec = Pt - Points[0];

			float u = Dot3(Vec, Line) / Line.LengthSquared();

			// Point is outside the line segment.
			if (u < 0.0f || u > 1.0f)
				continue;

			// Closest point in the line segment to the mouse.
			Vector3 Closest = Points[0] + u * Line;

			if ((Closest - Pt).LengthSquared() < 100.0f)
			{
				// If we already know the mouse is close to another axis, select a plane.
				if (Mode != -1)
				{
					if (Mode == LC_OVERLAY_X)
					{
						if (i == 2)
						{
							Mode = LC_OVERLAY_XY;
						}
						else
						{
							Mode = LC_OVERLAY_XZ;
						}
					}
					else
					{
						Mode = LC_OVERLAY_YZ;
					}

					break;
				}
				else
				{
					Mode = LC_OVERLAY_X + i - 1;
				}
			}
		}

		if (Mode == -1)
		{
			Mode = LC_OVERLAY_XYZ;
		}

		if (Mode != m_OverlayMode)
		{
			m_OverlayMode = Mode;
			UpdateAllViews();
		}
	}
	else if (m_nCurAction == LC_ACTION_ROTATE)
	{
		const float OverlayRotateRadius = 2.0f;

		// Calculate the distance from the mouse pointer to the center of the sphere.
		GLdouble px, py, pz, rx, ry, rz;
		GLdouble ModelMatrix[16], ProjMatrix[16];
		GLint Viewport[4];

		LoadViewportProjection(m_nActiveViewport);
		glGetDoublev(GL_MODELVIEW_MATRIX, ModelMatrix);
		glGetDoublev(GL_PROJECTION_MATRIX, ProjMatrix);
		glGetIntegerv(GL_VIEWPORT, Viewport);

		// Unproject the mouse point against both the front and the back clipping planes.
		gluUnProject(x, y, 0, ModelMatrix, ProjMatrix, Viewport, &px, &py, &pz);
		gluUnProject(x, y, 1, ModelMatrix, ProjMatrix, Viewport, &rx, &ry, &rz);

		Vector3 SegStart((float)rx, (float)ry, (float)rz);
		Vector3 SegEnd((float)px, (float)py, (float)pz);
		Vector3 Center(m_OverlayCenter[0], m_OverlayCenter[1], m_OverlayCenter[2]);

		Vector3 Line = SegEnd - SegStart;
		Vector3 Vec = Center - SegStart;

		float u = Dot3(Vec, Line) / Line.LengthSquared();

		// Closest point in the line to the mouse.
		Vector3 Closest = SegStart + u * Line;

		int Mode = -1;
		float Distance = (Closest - Center).Length();
		const float Epsilon = 0.25f * OverlayScale;

		if (Distance > (OverlayRotateRadius * OverlayScale + Epsilon))
		{
			Mode = LC_OVERLAY_XYZ;
		}
		else if (Distance < (OverlayRotateRadius * OverlayScale + Epsilon))
		{
			// 3D rotation unless we're over one of the axis circles.
			Mode = LC_OVERLAY_XYZ;

			// Point P on a line defined by two points P1 and P2 is described by P = P1 + u (P2 - P1)
			// A sphere centered at P3 with radius r is described by (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2 
			// Substituting the equation of the line into the sphere gives a quadratic equation where:
			// a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2 
			// b = 2[ (x2 - x1) (x1 - x3) + (y2 - y1) (y1 - y3) + (z2 - z1) (z1 - z3) ] 
			// c = x32 + y32 + z32 + x12 + y12 + z12 - 2[x3 x1 + y3 y1 + z3 z1] - r2 
			// The solutions to this quadratic are described by: (-b +- sqrt(b^2 - 4 a c) / 2 a
			// The exact behavior is determined by b^2 - 4 a c:
			// If this is less than 0 then the line does not intersect the sphere. 
			// If it equals 0 then the line is a tangent to the sphere intersecting it at one point
			// If it is greater then 0 the line intersects the sphere at two points. 

			float x1 = (float)px, y1 = (float)py, z1 = (float)pz;
			float x2 = (float)rx, y2 = (float)ry, z2 = (float)rz;
			float x3 = m_OverlayCenter[0], y3 = m_OverlayCenter[1], z3 = m_OverlayCenter[2];
			float r = OverlayRotateRadius * OverlayScale;

			// TODO: rewrite using vectors.
			float a = (x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1) + (z2 - z1)*(z2 - z1);
			float b = 2 * ((x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3));
			float c = x3*x3 + y3*y3 + z3*z3 + x1*x1 + y1*y1 + z1*z1 - 2*(x3*x1 + y3*y1 + z3*z1) - r*r;
			float f = b * b - 4 * a * c;

			if (f >= 0.0f)
			{
				Camera* Cam = m_pViewCameras[m_nActiveViewport];
				Vector3 ViewDir = Cam->GetTargetPosition() - Cam->GetEyePosition();

				float u1 = (-b + sqrtf(f)) / (2*a);
				float u2 = (-b - sqrtf(f)) / (2*a);

				Vector3 Intersections[2] =
				{
					Vector3(x1 + u1*(x2-x1), y1 + u1*(y2-y1), z1 + u1*(z2-z1)),
					Vector3(x1 + u2*(x2-x1), y1 + u2*(y2-y1), z1 + u2*(z2-z1))
				};

				for (int i = 0; i < 2; i++)
				{
					Vector3 Dist = Intersections[i] - Center;

					if (Dot3(ViewDir, Dist) > 0.0f)
						continue;

					// Find the rotation from the focused piece if relative snap is enabled.
					if ((m_nSnap & LC_DRAW_GLOBAL_SNAP) == 0)
					{
						Object* Focus = GetFocusObject();

						if ((Focus != NULL) && Focus->IsPiece())
						{
							float Rot[4];
							((Piece*)Focus)->GetRotation(Rot);

							Matrix33 RotMat;
							RotMat.CreateFromAxisAngle(Vector3(Rot[0], Rot[1], Rot[2]), -Rot[3] * LC_DTOR);

							Dist = Mul(Dist, RotMat);
						}
					}

					// Check if we're close enough to one of the axis.
					Dist.Normalize();

					float dx = fabsf(Dist[0]);
					float dy = fabsf(Dist[1]);
					float dz = fabsf(Dist[2]);

					if (dx < dy)
					{
						if (dx < dz)
						{
							if (dx < Epsilon)
								Mode = LC_OVERLAY_X;
						}
						else
						{
							if (dz < Epsilon)
								Mode = LC_OVERLAY_Z;
						}
					}
					else
					{
						if (dy < dz)
						{
							if (dy < Epsilon)
								Mode = LC_OVERLAY_Y;
						}
						else
						{
							if (dz < Epsilon)
								Mode = LC_OVERLAY_Z;
						}
					}

					if (Mode != LC_OVERLAY_XYZ)
					{
						switch (Mode)
						{
						case LC_OVERLAY_X:
							Dist[0] = 0.0f;
							break;
						case LC_OVERLAY_Y:
							Dist[1] = 0.0f;
							break;
						case LC_OVERLAY_Z:
							Dist[2] = 0.0f;
							break;
						}

						Dist *= r;

						m_OverlayTrackStart = Center + Dist;

						break;
					}
				}
			}
		}

		if (Mode != m_OverlayMode)
		{
			m_OverlayMode = Mode;
			UpdateAllViews();
		}
	}
	else if (m_nCurAction == LC_ACTION_ROTATE_VIEW)
	{
		int vx, vy, vw, vh;
		vx = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][0] * (float)m_nViewX);
		vy = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][1] * (float)m_nViewY);
		vw = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][2] * (float)m_nViewX);
		vh = (int)(viewports[m_nViewportMode].dim[m_nActiveViewport][3] * (float)m_nViewY);

		int cx = vx + vw / 2;
		int cy = vy + vh / 2;

		float d = sqrtf((float)((cx - x) * (cx - x) + (cy - y) * (cy - y)));
		float r = min(vw, vh) * 0.35f;

		const float SquareSize = max(8.0f, (vw+vh)/200);

		if ((d < r + SquareSize) && (d > r - SquareSize))
		{
			if ((cx - x < SquareSize) && (cx - x > -SquareSize))
				m_OverlayMode = LC_OVERLAY_Y;

			if ((cy - y < SquareSize) && (cy - y > -SquareSize))
				m_OverlayMode = LC_OVERLAY_X;
		}
		else
		{
			if (d < r)
				m_OverlayMode = LC_OVERLAY_XYZ;
			else
				m_OverlayMode = LC_OVERLAY_Z;
		}
	}
}

void Project::ActivateOverlay()
{
	if ((m_nCurAction == LC_ACTION_MOVE) || (m_nCurAction == LC_ACTION_ROTATE))
	{
		if (GetFocusPosition(m_OverlayCenter))
			m_OverlayActive = true;
		else if (GetSelectionCenter(m_OverlayCenter))
			m_OverlayActive = true;
		else
			m_OverlayActive = false;
	}
	else if ((m_nCurAction == LC_ACTION_ZOOM_REGION) && (m_nTracking == LC_TRACK_START_LEFT))
		m_OverlayActive = true;
	else if (m_nCurAction == LC_ACTION_ROTATE_VIEW)
		m_OverlayActive = true;
	else
		m_OverlayActive = false;

	if (m_OverlayActive)
	{
		m_OverlayMode = LC_OVERLAY_XYZ;
		UpdateOverlayScale();
	}
}

void Project::UpdateOverlayScale()
{
	if (m_OverlayActive)
	{
		GLdouble ScreenX, ScreenY, ScreenZ, PointX, PointY, PointZ;
		GLdouble ModelMatrix[16], ProjMatrix[16];
		GLint Viewport[4];

		for (int i = 0; i < viewports[m_nViewportMode].n; i++)
		{
			LoadViewportProjection(i);
			glGetDoublev(GL_MODELVIEW_MATRIX, ModelMatrix);
			glGetDoublev(GL_PROJECTION_MATRIX, ProjMatrix);
			glGetIntegerv(GL_VIEWPORT, Viewport);

			// Calculate the scaling factor by projecting the center to the front plane then
			// projecting a point close to it back.
			gluProject(m_OverlayCenter[0], m_OverlayCenter[1], m_OverlayCenter[2], ModelMatrix, ProjMatrix, Viewport, &ScreenX, &ScreenY, &ScreenZ);
			gluUnProject(ScreenX + 10.0f, ScreenY, ScreenZ, ModelMatrix, ProjMatrix, Viewport, &PointX, &PointY, &PointZ);

			Vector3 Dist((float)PointX - m_OverlayCenter[0], (float)PointY - m_OverlayCenter[1], (float)PointZ - m_OverlayCenter[2]);
			m_OverlayScale[i] = Dist.Length() * 5.0f;
		}
	}
}
