//
// Tools_Texturing_Texture.cpp
//
// Created by Kevin Lea - 05/10/2001 17:12:48
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#include "Texturing/Tools_Texturing_Texture.h"

#include "Core/Asura_Material_System.h"
#include "Core/Asura_MaterialResponse_System.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_UserInput.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_Cursor.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Interface.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Mesh/Asura_Mesh.h"
#include "Rendering/Asura_Module.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_RenderStates.h"
#include "Rendering/Asura_Viewport.h"

#include "Tools_Overlays.h"
#include "Texturing/Tools_Mode_Texturing.h"
#include "Texturing/Tools_Multiple_Node.h"
#include "Texturing/Tools_PickTexture.h"
#include "Texturing/Tools_Texture_Management.h"

/////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////////////

Asura_Vector_2					Tools_UV_Editor::MouseModeOverlayPosition = Asura_Vector_2(3.0f,30.0f);
u_int							Tools_UV_Editor::s_uMouseModeOverlayMinimised = 0;
int Tools_UV_Editor::LeftButtonMode = 0;
int Tools_UV_Editor::AxisRestrict = 0;


Tools_UV_Editor::Tools_UV_Editor()
{
	UsedArea_UV.left = 0;
	UsedArea_UV.top = 0;
	UsedArea_UV.right = 1;
	UsedArea_UV.bottom = 1;

	ShownArea_UV.left = 0;
	ShownArea_UV.top = 0;
	ShownArea_UV.right = 1;
	ShownArea_UV.bottom = 1;

	ZoomScale = 0;
	m_fScale = 256.0f;

	ClearMaterialAndTexture();

	m_iTextureChannel = 0;

	NumNodes = 4;
	Nodes = new Asura_Vector_2[NumNodes];
	m_pxInitialNodes = new Asura_Vector_2[NumNodes];
	BackupNodes = NULL;
	Nodes[0].x = 0;
	Nodes[0].y = 0;
	Nodes[1].x = 1;
	Nodes[1].y = 0;
	Nodes[2].x = 1;
	Nodes[2].y = 1;
	Nodes[3].x = 0;
	Nodes[3].y = 1;


	LeftButtonMode = NodeLBM_SelectAndMove;
	AxisRestrict = NodeEdit_NoRestriction;

	CurrentNodeIndex = 0;
	NodesMoved = false;
	NodeUndo.TheTexture = this;

	DragMode = 0;

	m_bClearSelectionAfterDrag = false;

	m_iToolsTextureIndex = Asura_Texture_Management::AddTexture("\\tools\\texturingtoolbits.dds");
	Asura_Texture_Management::LoadTextures();	
	m_eTranslucency = ASURA_TRANSLUCENCY_OFF;
}


Tools_UV_Editor::~Tools_UV_Editor()
{
	delete [] Nodes;
	delete [] m_pxInitialNodes;
	delete [] BackupNodes;
	Nodes = 0;
	m_pxInitialNodes = 0;
	BackupNodes = 0;
}

void Tools_UV_Editor::CreateMouseModeOverlay(void)
{
	//strings for the 2 list boxes
	static char* LeftButtonModeStrings[] =
	{
		"Select/Move",
		"Selection",
		"Movement",
		"Scale",
		"Rotate",
		"Zoom (Z)",
		"Edit Shown Area (Space)",
	};

	static char* AxisRestrictStrings[] =
	{
		"Both Axes",
		"X Only",
		"Y Only",
	};

	//Dummy offset variables for the list boxes declared here as static , 
	//since they should always be 0 , and I don't
	//care about them anywhere else
	static int LeftButtonModeOffset = 0;
	static int AxisRestrictOffset = 0;

	Asura_Overlay* newOverlay;
	newOverlay = new Asura_Overlay("Mouse Mode",false,&MouseModeOverlayPosition,&s_uMouseModeOverlayMinimised);
	
	Asura_Dialog* newDialog;
	Asura_Vector_2 offset = Asura_Vector_2(1.0f,3.0f);
			
	newDialog = new Asura_List_Dialog(offset,LeftButtonModeStrings,&LeftButtonModeOffset,&LeftButtonMode,7,7,NULL);
	newOverlay->AddDialog(newDialog);
	offset.y+=9.0f;

	newDialog = new Asura_List_Dialog(offset,AxisRestrictStrings,&AxisRestrictOffset,&AxisRestrict,3,3,NULL);
	newOverlay->AddDialog(newDialog);
	
	newOverlay->m_uID = TOOLS_OVERLAY_TEXTURING_MOUSEMODE;

	Asura_Overlay_System::SubmitNewOverlay(newOverlay,false);
}

void Tools_UV_Editor::CloseMouseModeOverlay()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_TEXTURING_MOUSEMODE);

}



void Tools_UV_Editor::NodesToCorners()
{
	Asura_Assert(NumNodes == 4,"Use of this doesn't make a whole lot of sense with more than 4 nodes");
	if(Tools_Mode_Texturing::TexturingMode == TexturingMode_NodeEdit)
	{
		//log for undo
		PrepareNodeUndo();
		NodesMoved = true;
	}


	//find the node closest to being in the top-left
	int tl_index = 0;
	float min_dist = Nodes[0].Magnitude();
	for(int i=1;i<4;i++)
	{
		float dist = Nodes[i].Magnitude();
		if(dist<min_dist)
		{
			tl_index = i;
			min_dist = dist;
		}
	}

	//determine whether to go clockwise/anticlockwise by looking at which
	//of the next/previous nodes is furthest right
	if(Nodes[(tl_index+1)%4].x>Nodes[(tl_index+3)%4].x)
	{
		Nodes[tl_index].x = UsedArea_UV.left;
		Nodes[tl_index].y = UsedArea_UV.top;
		Nodes[(tl_index+1)%4].x = UsedArea_UV.right;
		Nodes[(tl_index+1)%4].y = UsedArea_UV.top;
		Nodes[(tl_index+2)%4].x = UsedArea_UV.right;
		Nodes[(tl_index+2)%4].y = UsedArea_UV.bottom;
		Nodes[(tl_index+3)%4].x = UsedArea_UV.left;
		Nodes[(tl_index+3)%4].y = UsedArea_UV.bottom;
	}
	else
	{
		Nodes[tl_index].x = UsedArea_UV.left;
		Nodes[tl_index].y = UsedArea_UV.top;
		Nodes[(tl_index+3)%4].x = UsedArea_UV.right;
		Nodes[(tl_index+3)%4].y = UsedArea_UV.top;
		Nodes[(tl_index+2)%4].x = UsedArea_UV.right;
		Nodes[(tl_index+2)%4].y = UsedArea_UV.bottom;
		Nodes[(tl_index+1)%4].x = UsedArea_UV.left;
		Nodes[(tl_index+1)%4].y = UsedArea_UV.bottom;
	}


}

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
void Tools_UV_Editor::SetMaterialResponse(int iChannel, Asura_Hash_ID uMaterialResponseHashID)
{
	m_iMaterialID = ASURA_MATERIAL_NONE;
	m_iTextureID = ASURA_TEXTURE_NONE;

	m_auMaterialResponseHashID[iChannel] = uMaterialResponseHashID;
}
#endif 

void Tools_UV_Editor::SetMaterialID(int iMaterialID)
{
	ClearMaterialAndTexture();
	m_iMaterialID = iMaterialID;
}

void Tools_UV_Editor::SetTextureID(int iTextureID)
{
	ClearMaterialAndTexture();
	m_iTextureID = iTextureID;
}

void Tools_UV_Editor::ClearMaterialAndTexture()
{
	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	m_auMaterialResponseHashID[0] = ASURA_HASH_ID_UNSET;
	m_auMaterialResponseHashID[1] = ASURA_HASH_ID_UNSET;
	#endif
	m_iMaterialID = ASURA_MATERIAL_NONE;
	m_iTextureID = ASURA_TEXTURE_NONE;

}


void Tools_UV_Editor::Render()
{
	RenderTexture();
	RenderLines();
	RenderNodes();
	RenderSelectionBox(); 

}

void Tools_UV_Editor::RenderTexture()
{
	//first draw the texture
	//get the width and height of the area to be drawn
	float width = (ShownArea_UV.right-ShownArea_UV.left)*GetWidthScale();
	float height = (ShownArea_UV.bottom-ShownArea_UV.top)*GetHeightScale();

	//determine x/y coords...
	//subtracting 0.5 to avoid bilinear filtering blurring
	//and another 2 for good measure...
	float x = Asura_Viewport::GetWidth()-width-2.5f;
	float y = Asura_Viewport::GetHeight()-height-2.5f;

	Asura_Render::Overlay.SetRectXY(x,y,width,height);
	Asura_Render::Overlay.SetRectUV(ShownArea_UV.left,ShownArea_UV.top,ShownArea_UV.right-ShownArea_UV.left,ShownArea_UV.bottom-ShownArea_UV.top);
	Asura_Render::Overlay.TranslucencyMode = m_eTranslucency;

	Asura_Render::Overlay.TextureIndex = GetTextureToDisplay();

	Asura_Render::Overlay.Colour=0xffffffff;
	Asura_Render::Overlay.Render();

	//force this to be drawn now , so the lines don't get covered
	Asura_Render::FlushDisplay();
}

void Tools_UV_Editor::RenderLine(const Asura_Vector_2& xUVStart,const Asura_Vector_2& xUVEnd,u_int uColour)
{
#define CLIP_LINE_BETWEEN_NODES(SIGN,MEMBER,val) \
{\
	if(xStart.MEMBER SIGN val || xEnd.MEMBER SIGN val)\
	{\
		if(xStart.MEMBER SIGN val && xEnd.MEMBER SIGN val) return;\
		Asura_Vector_2 *v1,*v2;\
		if(xStart.MEMBER SIGN xEnd.MEMBER)\
		{\
			v1 = &xStart;\
			v2 = &xEnd;\
		}\
		else\
		{\
			v1 = &xEnd;\
			v2 = &xStart;\
		}\
\
\
		float numerator = val - v1->MEMBER;\
		float denominator = v2->MEMBER - v1->MEMBER;\
		if(denominator == 0) return;\
		float scale = numerator/denominator;\
\
		v1->x += ((v2->x-v1->x)*scale);\
		v1->y += ((v2->y-v1->y)*scale);\
\
	}\
}

	Asura_Vector_2 xStart = xUVStart;
	Asura_Vector_2 xEnd = xUVEnd;

	//clip the line to the shown area of texture
	CLIP_LINE_BETWEEN_NODES(<,x,ShownArea_UV.left)
	CLIP_LINE_BETWEEN_NODES(>,x,ShownArea_UV.right)
	CLIP_LINE_BETWEEN_NODES(<,y,ShownArea_UV.top)
	CLIP_LINE_BETWEEN_NODES(>,y,ShownArea_UV.bottom)

	//convert to screen coords
	xStart = GetScreenPosition(xStart);
	xEnd = GetScreenPosition(xEnd);

	Asura_Render::Render2DLine(xStart,xEnd,uColour);

	#undef CLIP_LINE_BETWEEN_NODES
}

void Tools_UV_Editor::RenderLines()
{
	//determine the colour for the lines
	float fBright = Asura_Maths::Sine( Asura_Timers::GetLocalUnscaledTime() * 2.0f );
	int iBright = Asura_Maths::FloatToInt( 255.0f * fBright * fBright );
	unsigned int uColour = 0xff000000 + (iBright) + (iBright << 8) + (iBright << 16);
	
	//now draw the lines connecting the nodes
	for( int i = 0; i < NumNodes; i++ )
	{
		int j = 0;
		int iIndex;
		while( (iIndex = Texturing_Multiple_Nodes::GetConnectedNode(i,j++)) >=0)
		{
			if( iIndex < i ) continue;

			RenderLine( Nodes[i], Nodes[iIndex], uColour );
		}
	}

	Asura_Render::FlushDisplay();

}

void Tools_UV_Editor::RenderNodes()
{
	for(int i=0;i<NumNodes;i++)
	{
		//don't draw the node if it is outside the shown area
		if(Nodes[i].x<ShownArea_UV.left) continue;
		if(Nodes[i].x>ShownArea_UV.right) continue;
		if(Nodes[i].y<ShownArea_UV.top) continue;
		if(Nodes[i].y>ShownArea_UV.bottom) continue;
		
		Asura_Vector_2 centre = GetScreenPosition(Nodes[i]);
		centre.x = (float)(int)centre.x;
		centre.y = (float)(int)centre.y;
		Asura_Render::Overlay.SetRectXY(centre.x-3.5f,centre.y-3.5f,7,7);
		//select appropriate part of the texture , depending on whether it is being dragged
		if(SelectedNodeList.contains(i) ||
		   (!SelectedNodeList.size() && CurrentNodeIndex == i && LeftButtonMode == NodeLBM_SelectAndMove))
		{
			Asura_Render::Overlay.SetRectUV(9/32.0f,0/32.0f,7/32.0f,7/32.0f);
		}
		else
		{
			Asura_Render::Overlay.SetRectUV(0/32.0f,0/32.0f,7/32.0f,7/32.0f);
		}
		
		
		Asura_Render::Overlay.TextureIndex = m_iToolsTextureIndex;
		Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;

		Asura_Render::Overlay.Render();
	}

	Asura_Render::FlushDisplay();
}

void Tools_UV_Editor::RenderSelectionBox()
{
	//determine the colour for the lines
	float fBright = Asura_Maths::Sine( Asura_Timers::GetLocalUnscaledTime() * 2.0f );
	int iBright = Asura_Maths::FloatToInt( 255.0f * fBright * fBright );
	unsigned int uColour = 0xff000000 + (iBright) + (iBright << 8) + (iBright << 16);
	
	//if doing a rectangle selection , need to draw the rectangle
	if(DragMode & TexturingDragMode_RectSelection)
	{
		Asura_Vector_2 xStart, xEnd;
		xEnd = DragInitial;
	
		xStart = xEnd;
		xEnd.x = DragPrevious.x;
		Asura_Render::Render2DLine( xStart, xEnd, uColour );

		xStart = xEnd;
		xEnd.y = DragPrevious.y;
		Asura_Render::Render2DLine( xStart, xEnd, uColour );

		xStart = xEnd;
		xEnd.x = DragInitial.x;
		Asura_Render::Render2DLine( xStart, xEnd, uColour );

		xStart = xEnd;
		xEnd.y = DragInitial.y;
		Asura_Render::Render2DLine( xStart, xEnd, uColour );
	}
}




Asura_Vector_2 Tools_UV_Editor::GetScreenPosition(const Asura_Vector_2& uv_pos)
{
	Asura_Vector_2 pos;

	//get position relative to bottom right corner of shown area
	pos.x = uv_pos.x - ShownArea_UV.right;
	pos.y = uv_pos.y - ShownArea_UV.bottom;

	//scale up into pixel coordinates
	pos.x *= GetWidthScale();
	pos.y *= GetHeightScale();

	//and make relative to the bottom right of the screen
	pos.x += Asura_Viewport::GetWidth()-2;
	pos.y += Asura_Viewport::GetHeight()-2;

	return pos;

}

Float_Rectangle Tools_UV_Editor::GetScreenPosition(const Float_Rectangle& rect)
{
	//convert uv rectangle to a screen position
	Asura_Vector_2 top_left(rect.left,rect.top);
	Asura_Vector_2 bottom_right(rect.right,rect.bottom);

	top_left = GetScreenPosition(top_left);
	bottom_right = GetScreenPosition(bottom_right);

	Float_Rectangle screen_rect;
	screen_rect.left = top_left.x;
	screen_rect.top = top_left.y;
	screen_rect.right = bottom_right.x;
	screen_rect.bottom = bottom_right.y;

	return screen_rect;
}

Asura_Vector_2 Tools_UV_Editor::GetUVFromScreenPosition(const Asura_Vector_2& screen)
{
	Asura_Vector_2 pos;

	//get position relative to bottom right corner of the screen
	pos.x = screen.x - (Asura_Viewport::GetWidth()-2);
	pos.y = screen.y - (Asura_Viewport::GetHeight()-2);
	
	//scale down into uv coordinates
	pos.x /= GetWidthScale();
	pos.y /= GetHeightScale();
	
	//and make relative to the bottom right of the texture
	pos.x += ShownArea_UV.right;
	pos.y += ShownArea_UV.bottom;


	return pos;
	
}


void Tools_UV_Editor::CopyFromPolygon(PolygonID* polygon,int iChannel)
{
	Asura_Assert(NumNodes==4,"Shouldn't be calling this while in multiple nodes mode");
	//grab the uv coordinates and texturing from another polygon

	//get the mesh data
	Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(polygon->ModuleIndex);
	Asura_Assert(mesh->TextureDataPtr,"Texture data hasn't been allocated for the mesh");

	//get the polygon
	Asura_Mesh_Polygon* poly = &mesh->PolygonDataPtr[polygon->PolyIndex];
	//get the texture info
	Asura_Mesh_TextureInfo* tinfo = &mesh->TextureDataPtr[polygon->PolyIndex];

	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		const bool bNoMaterial = (tinfo->m_auMaterialResponseHashID[0] == ASURA_HASH_ID_UNSET);
	#else
		const bool bNoMaterial = (tinfo->m_iMaterialID == ASURA_MATERIAL_NONE);
	#endif

	if( bNoMaterial )
	{
		//polygon not textured , so just set some default coordinates
		Nodes[0].x = UsedArea_UV.left;
		Nodes[0].y = UsedArea_UV.top;
		Nodes[1].x = UsedArea_UV.right;
		Nodes[1].y = UsedArea_UV.top;
		Nodes[2].x = UsedArea_UV.right;
		Nodes[2].y = UsedArea_UV.bottom;
		Nodes[3].x = UsedArea_UV.left;
		Nodes[3].y = UsedArea_UV.bottom;

	}
	else
	{

		for(int i=0;i<4;i++)
		{
			Nodes[i].x = tinfo->U[iChannel][i];
			Nodes[i].y = tinfo->V[iChannel][i];
		}
		if(!poly->IsQuad())
		{
			Nodes[3] = Nodes[2];
		}
	}
	FitUsedAreaToNodes();
}


void Tools_UV_Editor::HandleMouse()
{
	//each frame's actions get counted as a seperate undo , except during drag operations
	//in which the whole operation is counted as one undo
	if(!DragMode)
	{
		NodeUndo.MarkUndoStart();
	}
	
	//get the current mouse position
	Asura_Vector_2 MousePos;
	int mx,my;
	Asura_Cursor::GetScreenSpaceCoords(mx,my);

	MousePos.x = (float)mx;
	MousePos.y = (float)my;
	
	if(!(DragMode & TexturingDragMode_RightClickModes))
	{
		if(Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON1) && !Asura_Interface::UsingMenusOrOverlays())
		{
			//left button is down... just been pressed?
			if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1))
			{
				//starting a drag action then
				LeftButtonPressed(MousePos);

			}
			else
			{
				//continuing a drag action
				LeftButtonHeld(MousePos);
			}
			//update 'previous' frame mouse position
			DragPrevious = MousePos;

		}
		else
		{
			//button not currently down , if DragMode is nonzero then it has
			//just been released
			if(DragMode & TexturingDragMode_LeftClickModes)
			{
				LeftButtonReleased(MousePos);
			}
			DragMode = 0;
		}
	}
	if(!(DragMode & TexturingDragMode_LeftClickModes))
	{
		if(Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON2) && !Asura_Interface::UsingMenusOrOverlays())
		{
			//right button is down... just been pressed?
			if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON2))
			{
				//starting a drag action then
				RightButtonPressed(MousePos);
				DragInitial = MousePos;
			}
			else
			{
				//continuing a drag action
				RightButtonHeld(MousePos);
			}
			//update 'previous' frame mouse position
			DragPrevious = MousePos;

		}
		else
		{
			//button not currently down , if DragMode is nonzero then it has
			//just been released
			if(DragMode & TexturingDragMode_RightClickModes)
			{
				RightButtonReleased(MousePos);
			}
			DragMode = 0;
		}
	}

}


void Tools_UV_Editor::LeftButtonPressed(Asura_Vector_2& MousePos)
{
	int i;
	//get the area of the texture on screen
	Float_Rectangle screen_rect = GetScreenPosition(ShownArea_UV);

	Asura_Assert(DragMode == 0,"DragMode should have been reset");

	int ButtonMode = LeftButtonMode;
	//check for keypresses overiding the button mode
	if(Asura_UserInput::KeyPressed('z'))
	{
		ButtonMode = NodeLBM_Zoom;
	}
	else if(Asura_UserInput::KeyPressed(ASURA_KEYID_SPACE))
	{
		ButtonMode = NodeLBM_EditShownArea;
	}

	//update the initial node positions
	for(int i=0;i<NumNodes;i++)
	{
		m_pxInitialNodes[i] = Nodes[i];
	}

	if(ButtonMode == NodeLBM_EditShownArea)
	{
		//if the mouse is near the top and/or left of the texture,the flags are set  to show that the texture is being resized
		if(screen_rect.left-3<=MousePos.x && MousePos.x<=screen_rect.left+2)
		{
			DragMode |= TexturingDragMode_Horizontal;
		}
		if(screen_rect.top-3<=MousePos.y && MousePos.y<=screen_rect.top+2)
		{
			DragMode |= TexturingDragMode_Vertical;
		}

		//if the mouse is within the texture , but ot too close to the sides, scroll the texture
		if(!DragMode)
		{
			if(screen_rect.left<=MousePos.x && screen_rect.top<=MousePos.y)
			{
				DragMode |= TexturingDragMode_ScrollTexture;
			}
		}

	
	}
	else if(ButtonMode == NodeLBM_SelectAndMove)
	{
		//clear the node selection before starting
		while(SelectedNodeList.size()) SelectedNodeList.delete_first_entry();
		
		//First we see if we will drag just one node...
		//if the shift key is pressed move the current node to the mouse
		if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT) &&
		   MousePos.x>=screen_rect.left && MousePos.x<=screen_rect.right && MousePos.y>=screen_rect.top && MousePos.y<=screen_rect.bottom)
		{
			//store the locations for undo
			PrepareNodeUndo();

			DragMode |= TexturingDragMode_Nodes;
			//add the currentnode to the listof nodes being moved
			if(!SelectedNodeList.contains(CurrentNodeIndex))SelectedNodeList.add_entry(CurrentNodeIndex);

			//update the nodes position
			Nodes[CurrentNodeIndex] = GetUVFromScreenPosition(MousePos);

			//set Nodesmoved , so that the polygons get updated
			NodesMoved = true;
		}
		else
		{
			//See if the mouse is near to a node.
			//If it is then we willl move that node.
			for(i=0;i<NumNodes;i++)
			{
				Asura_Vector_2 pos = GetScreenPosition(Nodes[i]);
				//if the mouse is near a node,that node is made current and the flag is set to show that it is being dragged.
				if(pos.x-4.0f<=MousePos.x &&
				   pos.x+4.0f>=MousePos.x &&	
				   pos.y-4.0f<=MousePos.y &&
				   pos.y+4.0f>=MousePos.y)
				{
					//store the locations for undo
					PrepareNodeUndo();
					DragMode |= TexturingDragMode_Nodes;
					CurrentNodeIndex=i;
					if(!SelectedNodeList.contains(i))SelectedNodeList.add_entry(i);

					break;

				}
			}

			//If we're not dragging a single node , then we need to check to see if we should
			//drag two nodes connected by a line			
			if(!DragMode)
			{
				
				//check each line to see if the mouse is close enough
				for(i=0;i<NumNodes;i++)
				{
					int j = 0;
					int index;
					while( (index = Texturing_Multiple_Nodes::GetConnectedNode(i,j++)) >=0)
					{
						if(index<i) continue;
			
						//get the screen positions of the 2 nodes
						Asura_Vector_2 pos1 = GetScreenPosition(Nodes[i]);
						Asura_Vector_2 pos2 = GetScreenPosition(Nodes[index]);
						if(pos1 == pos2) continue;
				
						//Calculate dot product of first point to the mouse,and second point to the mouse
						//if this value is negative then the mouse is somewhere between the two points.
						if((MousePos-pos1)*(MousePos-pos2)<0)
						{
							//get a normalised vector perpendicular to the line
							Asura_Vector_2 perp;
							perp.x = pos2.y-pos1.y;
							perp.y = pos1.x-pos2.x;
							perp.Normalise();

							//take the dot product with the line from the first point to the mouse , to get
							//the distance of the mouse from the line
							float distance = perp * (MousePos-pos1);
							if(-5.0f<distance && distance < 5.0f)
							{
								//store the locations for undo
								PrepareNodeUndo();
								DragMode |= TexturingDragMode_Nodes;
								if(!SelectedNodeList.contains(i))SelectedNodeList.add_entry(i);
								if(!SelectedNodeList.contains(index))SelectedNodeList.add_entry(index);
							}
						}
					}
				}
			}

			//If we still haven't found some nodes to drag , then we may drag all of them
			if(!DragMode)
			{
				bool drag_all = false;
				//drag all the nodes if the mouse is over the texture
				if( MousePos.x>=screen_rect.left && MousePos.x<=screen_rect.right && MousePos.y>=screen_rect.top && MousePos.y<=screen_rect.bottom)
				{
					drag_all = true;
				}

				if(drag_all)
				{
					for(i=0;i<NumNodes;i++)
					{
						if(!SelectedNodeList.contains(i))SelectedNodeList.add_entry(i);
					}
					//store the locations for undo
					PrepareNodeUndo();
					DragMode |= TexturingDragMode_Nodes;
				}
			
			}

		}
	}
	else if(ButtonMode == NodeLBM_Selection)
	{
		//start the selection rectangle
		DragMode |= TexturingDragMode_RectSelection;
	}
	else if(ButtonMode == NodeLBM_Scale)
	{
		SelectAllNodesForDragIfNeeded();
	
		//prepare undo
		PrepareNodeUndo();
		//and initialise the scaling
		PrepareRotationOrScale(true);
		DragMode |= TexturingDragMode_ScaleSelected;
	}
	else if(ButtonMode == NodeLBM_Rotate)
	{
		SelectAllNodesForDragIfNeeded();
		//prepare undo
		PrepareNodeUndo();
		//and initialise the rotation
		PrepareRotationOrScale(true);
		DragMode |= TexturingDragMode_RotateSelected;
	}
	else if(ButtonMode == NodeLBM_Move)
	{
		SelectAllNodesForDragIfNeeded();

		//start a node drag operation
		if(SelectedNodeList.size())
		{
			//store the locations for undo
			PrepareNodeUndo();
			DragMode |= TexturingDragMode_Nodes;
		}
	}
	else if(ButtonMode == NodeLBM_Zoom)
	{
		//start zooming into the texture
		DragMode |= TexturingDragMode_Zoom;
	}

	DragInitial = MousePos;
	DragInitial_UV = GetUVFromScreenPosition(MousePos);
	if(DragInitial_UV.x<ShownArea_UV.left || DragInitial_UV.y<ShownArea_UV.top)
	{
		//mouse was outside texture... just set initial uv to centre of shown area
		DragInitial_UV.x = (ShownArea_UV.left+ShownArea_UV.right)/2.0f;
		DragInitial_UV.y = (ShownArea_UV.top+ShownArea_UV.bottom)/2.0f;
	}

}

void Tools_UV_Editor::LeftButtonHeld(Asura_Vector_2& MousePos)
{
	if(MousePos == DragPrevious) return;

	int iAxisRestrict = AxisRestrict;
	if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT) && iAxisRestrict == NodeEdit_NoRestriction)
	{
		//shift held down.... restict axis based on which is furthest from the initial position
		Asura_Vector_2 xDiff = MousePos - DragInitial;
		if(Asura_Maths::Abs(xDiff.x)>=Asura_Maths::Abs(xDiff.y))
			iAxisRestrict = NodeEdit_XOnly;
		else
			iAxisRestrict = NodeEdit_YOnly;

	}

	//holding alt snaps to nearest texel , where appropriate
	bool bSnapToTexel = Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL);

				
	//determine how much the mouse has moved in terms of uv coordinates
	Asura_Vector_2 uv_shift = GetUVFromScreenPosition(MousePos)-GetUVFromScreenPosition(DragPrevious);
	Asura_Vector_2 uv_shift_total = GetUVFromScreenPosition(MousePos)-GetUVFromScreenPosition(DragInitial);


	if(DragMode & TexturingDragMode_Nodes)
	{
		if(SelectedNodeList.size() == 1)
		{
			//moving just one node ,so move it to mouse
			uv_shift_total = GetUVFromScreenPosition(MousePos) - m_pxInitialNodes[SelectedNodeList.first_entry()];
		}
		
		if(iAxisRestrict == NodeEdit_XOnly)
		{
			uv_shift_total.y = 0;
		}
		else if(iAxisRestrict == NodeEdit_YOnly)
		{
			uv_shift_total.x = 0;
		}

		for(int i=0;i<NumNodes;i++)
		{
			Nodes[i] = m_pxInitialNodes[i];
		}
		
		for(LIF<int> dlif(&SelectedNodeList);!dlif.done();dlif.next())
		{
			int node=dlif();
			
			//move the node according to the distance the mouse moved
			Nodes[node] += uv_shift_total;
	   		
	   		//make sure that the node stays on the texture.
	   		//xcorr and ycorr are the amoounts by which the node has overshot the edge
			float xcorr = 0.0f;
			float ycorr = 0.0f;
	   		
	   		if(uv_shift_total.x>0 && Nodes[node].x > UsedArea_UV.right)
			{
				xcorr = min(Nodes[node].x - UsedArea_UV.right,uv_shift_total.x);
			}
	   		else if(uv_shift_total.x<0 && Nodes[node].x < UsedArea_UV.left)
			{
				xcorr = max(Nodes[node].x - UsedArea_UV.left,uv_shift_total.x);
			}
	   		if(uv_shift_total.y>0 && Nodes[node].y > UsedArea_UV.bottom)
			{
				ycorr = min(Nodes[node].y - UsedArea_UV.bottom,uv_shift_total.y);
			}
	   		else if(uv_shift_total.y<0 && Nodes[node].y < UsedArea_UV.top)
			{
				ycorr = max(Nodes[node].y - UsedArea_UV.top,uv_shift_total.y);
			}
			
			//Now do the adjustment on all nodes currently being moved
			if (xcorr || ycorr)
			{
				for(LIF<int> dlif2(&SelectedNodeList);!dlif2.done();dlif2.next())
				{
					Nodes[dlif2()].x-=xcorr;
					Nodes[dlif2()].y-=ycorr;
				}
			}
			if(bSnapToTexel)
			{
				for(LIF<int> dlif2(&SelectedNodeList);!dlif2.done();dlif2.next())
				{
					SnapNodeToNearestTexel(dlif2());
				}
			}


		}
		//flag the nodes as having moved
		NodesMoved=1;
	
	}

	#if 1
	if(DragMode & TexturingDragMode_Horizontal)
	{
		//adjust the right hand side of the shown area
		ShownArea_UV.right -= uv_shift.x;
	}
	if(DragMode & TexturingDragMode_Vertical)
	{
		//adjust the bottom of the shown area
		ShownArea_UV.bottom -= uv_shift.y;
	}
	if(DragMode & TexturingDragMode_ScrollTexture)
	{
		//scrolling texture , move all the sides
		ShownArea_UV.left -= uv_shift.x;	
		ShownArea_UV.right -= uv_shift.x;	
		ShownArea_UV.top -= uv_shift.y;	
		ShownArea_UV.bottom -= uv_shift.y;	
	}

	if(DragMode & (TexturingDragMode_Horizontal|TexturingDragMode_Vertical|TexturingDragMode_ScrollTexture))
	{
		//make sure the shown area is within acceptable bounds

		//get the new shown width , clamping to used width , and making sure its not too small
		float shown_width = max(ShownArea_UV.right-ShownArea_UV.left,0.01f);
		shown_width = min(shown_width,UsedArea_UV.right-UsedArea_UV.left);

		//make sure left hand side isn't out of bounds
		if(ShownArea_UV.left<UsedArea_UV.left)
		{
			ShownArea_UV.left =	UsedArea_UV.left;
		}
		//get righthand side from left side
		ShownArea_UV.right = ShownArea_UV.left + shown_width;
		//if right side is out of bounds , shift back ,and recalculate the left side from that
		if(ShownArea_UV.right>UsedArea_UV.right)
		{
			ShownArea_UV.right=UsedArea_UV.right;
			ShownArea_UV.left=UsedArea_UV.right-shown_width;
		}
		
		//do the same thing for the top/bottom
		float shown_height = max(ShownArea_UV.bottom-ShownArea_UV.top,0.01f);
		shown_height = min(shown_height,UsedArea_UV.bottom-UsedArea_UV.top);

		if(ShownArea_UV.top<UsedArea_UV.top)
		{
			ShownArea_UV.top = UsedArea_UV.top;
		}
		ShownArea_UV.bottom = ShownArea_UV.top + shown_height;
		if(ShownArea_UV.bottom>UsedArea_UV.bottom)
		{
			ShownArea_UV.bottom=UsedArea_UV.bottom;
			ShownArea_UV.top=UsedArea_UV.bottom-shown_height;
		}

	}
	#endif

	if(DragMode & TexturingDragMode_RotateSelected)
	{
		Asura_Vector_2 initial_offset = DragInitial - GetScreenPosition(DragCentre);
		Asura_Vector_2 current_offset = MousePos - GetScreenPosition(DragCentre);
		float initial_dist = initial_offset.Magnitude();
		float current_dist = current_offset.Magnitude();
		
		if(initial_dist>1.0f && current_dist >1.0f)
		{
			//nodes are rotated through an angle equal to the angle that the
			//mouse travels around the centre.the following calculations work
			//out the angle.

			//normalise the offsets
			initial_offset.Normalise();
			current_offset.Normalise();

			Asura_Vector_2 current_offset_perp;
			current_offset_perp.x = current_offset.y;
			current_offset_perp.y = -current_offset.x;
			
			Asura_Angle angle = Asura_Maths::ArcSine(initial_offset*current_offset_perp);
			if(initial_offset*current_offset<0.0f) angle = Asura_Maths::Pi-angle;
			RotateNodes(DragCentre,angle,true);

			if(bSnapToTexel) SnapNodesToNearestTexel();

			
			//flag the nodes as having moved
			NodesMoved=1;
			
		}
	}

	if(DragMode & TexturingDragMode_ScaleSelected)
	{
		Asura_Vector_2 initial_offset = DragInitial - GetScreenPosition(DragCentre);
		Asura_Vector_2 current_offset = MousePos - GetScreenPosition(DragCentre);

		if(iAxisRestrict == NodeEdit_XOnly)
			initial_offset.y = current_offset.y = 0;
		if(iAxisRestrict == NodeEdit_YOnly)
			initial_offset.x = current_offset.x = 0;
		
	
		float initial_dist = initial_offset.Magnitude();
		float current_dist = current_offset.Magnitude();
		
		if(initial_dist>1.0f && current_dist >1.0f)
		{
			//polygon size changed according to movement of mouse relative to centre 
			ScaleNodes(DragCentre,current_dist/initial_dist,true,iAxisRestrict);
		
			if(bSnapToTexel) SnapNodesToNearestTexel();

			//flag the nodes as having moved
			NodesMoved=1;
		}
	}

	if(DragMode & TexturingDragMode_Zoom)
	{
		//amount to zoom is detmeined by the motion of the mouse up and down the screen
		int NewScaleChange = (int)(((DragInitial.y - MousePos.y)/Asura_Viewport::GetHeight())*25.0f);
		int OldScaleChange = (int)(((DragInitial.y - DragPrevious.y)/Asura_Viewport::GetHeight())*25.0f);

		if(NewScaleChange != OldScaleChange)
		{
			ZoomScale += NewScaleChange - OldScaleChange;
			ZoomScale = min(255,ZoomScale);
			ZoomScale = max(-255,ZoomScale);

			float scale = (ZoomScale>=0) ? ZoomScale+1.0f : -1.0f/(ZoomScale-1.0f);
			m_fScale = 256.0f*scale;

			DetermineSuitableShownArea(&DragInitial_UV);
		}
	}

}

void Tools_UV_Editor::LeftButtonReleased(Asura_Vector_2& MousePos)
{

	if(DragMode & TexturingDragMode_RectSelection)
	{
		bool deselecting = Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT);
		
		//finished drawing rectangle selection
		//if rect is small just select/deselect a nearby node

		if((MousePos-DragInitial).Magnitude()<=2.0f)
		{
			int near_node = -1;
			float near_dist = 5.0f;
			for(int i=0;i<NumNodes;i++)
			{
				Asura_Vector_2 pos = GetScreenPosition(Nodes[i]);
				
				float dist = (pos-MousePos).Magnitude();
				if(dist<near_dist)
				{
					near_node = i;
					near_dist = dist;
				}
			}
			if(near_node!=-1)
			{
				if(deselecting)
				{
					//deselect
					if(SelectedNodeList.contains(near_node))
					{
						SelectedNodeList.delete_entry(near_node);
					}
				}
				else
				{
					//select
					if(!SelectedNodeList.contains(near_node))
					{
						SelectedNodeList.add_entry(near_node);
					}
				}
			}
		}
		else
		{
			//make a bounding rect from the mouse positions
			Asura_Bounding_Rectangle bounds;
			bounds.Reset();
			bounds.Set(MousePos);
			bounds.Set(DragInitial);
			
			for(int i=0;i<NumNodes;i++)
			{
				Asura_Vector_2 pos = GetScreenPosition(Nodes[i]);
				
				if(bounds.Contains(pos))
				{
					if(deselecting)
					{
						if(SelectedNodeList.contains(i))
						{
							SelectedNodeList.delete_entry(i);
						}
					}
					else
					{
						if(!SelectedNodeList.contains(i))
						{
							SelectedNodeList.add_entry(i);
						}
					}

				}
			}
		}
	}
	
	
	if(LeftButtonMode == NodeLBM_SelectAndMove || m_bClearSelectionAfterDrag)
	{
		//clear the list of nodes being dragged
		while(SelectedNodeList.size())
		{
			SelectedNodeList.delete_first_entry();
		}
		m_bClearSelectionAfterDrag = false;

	}
}

void Tools_UV_Editor::RightButtonPressed(Asura_Vector_2& MousePos)
{
	//get the area of the texture on screen
	Float_Rectangle screen_rect = GetScreenPosition(ShownArea_UV);
	
	
	//make sure mouse is within texture.
	if(MousePos.x>=screen_rect.left && MousePos.x<=screen_rect.right && MousePos.y>=screen_rect.top && MousePos.y<=screen_rect.bottom)
	{
		
		//store the locations for undo
		PrepareNodeUndo();
		DragMode |= TexturingDragMode_RotateResize;

		//calculate the centre of the nodes
		//and fill in the unclamped nodes at the same time
		PrepareRotationOrScale(false);
		
		//if the mouse is too close to the centre the nodes won't be rotated.
		Asura_Vector_2 diff = MousePos - GetScreenPosition(DragCentre);
		if(MousePos.x>DragCentre.x-1 && MousePos.x<DragCentre.x+1 &&
		   MousePos.y>DragCentre.y-1 && MousePos.y<DragCentre.y+1)
		{
			DragMode = 0;//abort
		}
	}
}



void Tools_UV_Editor::RightButtonHeld(Asura_Vector_2& MousePos)
{

	if(DragMode & TexturingDragMode_RotateResize)
	{
		Asura_Vector_2 initial_offset = DragInitial - GetScreenPosition(DragCentre);
		Asura_Vector_2 current_offset = MousePos - GetScreenPosition(DragCentre);
		float initial_dist = initial_offset.Magnitude();
		float current_dist = current_offset.Magnitude();
		
		if(initial_dist>1.0f && current_dist >1.0f)
		{
			if(!Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
			{
				//nodes are rotated through an angle equal to the angle that the
				//mouse travels around the centre.the following calculations work
				//out the angle.

				//normalise the offsets
				initial_offset.Normalise();
				current_offset.Normalise();

				Asura_Vector_2 current_offset_perp;
				current_offset_perp.x = current_offset.y;
				current_offset_perp.y = -current_offset.x;
				
				Asura_Angle angle = Asura_Maths::ArcSine(initial_offset*current_offset_perp);
				if(initial_offset*current_offset<0.0f) angle = Asura_Maths::Pi-angle;
				RotateNodes(DragCentre,angle,false);
				

				//flag the nodes as having moved
				NodesMoved=1;
			}
			else
			{
				//polygon size changed according to movement of mouse relative to centre 
				ScaleNodes(DragCentre,current_dist/initial_dist,false,AxisRestrict);
				
				//flag the nodes as having moved
				NodesMoved=1;
			}
		}
	}
}

void Tools_UV_Editor::RightButtonReleased(Asura_Vector_2& /*MousePos*/)
{
	//nothing to do at the moment
}

void Tools_UV_Editor::EnsureNodesAreStillOkayForTextureWrap()
{
	Asura_Assert(NumNodes==4,"Shouldn't be calling this while in multiple nodes mode");
	if(!SelectedNodeList.size()) return;

	//First we need to look at which nodes have been moved , and adjust the remaining ones
	//as needed in order to keep a rectangular formation

	if(SelectedNodeList.size()==2)
	{
		//2 nodes in a line have been moved
		//first work out the node at the start of the line
		int node;
		for(node=0;node<4;node++)
		{
			if (SelectedNodeList.contains(node)) break;
		}
		if(SelectedNodeList.contains(0) && SelectedNodeList.contains(3)) node=3;

		//get the line joining the 2 nodes
		Asura_Vector_2 line = Nodes[(node+1)%4]-Nodes[node];
		if(line.Magnitude()<0.00001f) return;//nodes are getting too close... 
		//and normalise
		line.Normalise();

		//get one of the other sides ,that needs to be perpendicular
		Asura_Vector_2 line2 = Nodes[(node+2)%4]-Nodes[(node+1)%4];

		//get the dot product to see how much adjustment needs to be done
		float dot = line * line2;
		line *= dot;
		line2 -= line;

		//recalculate the other 2 nodes using this perpendicular line
		Nodes[(node+2)%4] = Nodes[(node+1)%4] + line2;
		Nodes[(node+3)%4] = Nodes[node] + line2;

		
	}
	else if(SelectedNodeList.size()==1)
	{
		//single node moved
		int node = SelectedNodeList.first_entry();

		//get the normalised side for one of the sides not containing this node
		Asura_Vector_2 line = Nodes[(node+3)%4] - Nodes[(node+2)%4];
		if(line.Magnitude()<0.00001f) return;//nodes are getting too close... 
		line.Normalise();
		//get the line that should be perpendicular to it
		Asura_Vector_2 line2 = Nodes[node] - Nodes[(node+3)%4];

		//find out how much it is off
		float dot = line*line2;
		line *= dot;
		
		//adjust the nodes in some way...can't think what this is all about at the moment
		Nodes[(node+3)%4] += line;
		Nodes[(node+1)%4] = Nodes[node] + Nodes[(node+2)%4] - Nodes[(node+3)%4];
	}

	//now we need to make sure the nodes haven't drifted off the used area of the texture
	//as a result of the adjustments

	Asura_Vector_2 centre = Nodes[0];
	float scale = 1.0f; //amount by which we need to scale the nodes to get them to fit
	//get the centre of the nodes
	for(int i=1;i<4;i++)
	{
		centre += Nodes[i];
	}
	centre/=4;


	//check each node to see if it is off the texture. if it is then work out how much
	//we need to scale down the nodes to get them to fit
	int i;
	for(i=0;i<4;i++)
	{
		if(Nodes[i].x<UsedArea_UV.left)
		{
			float temp_scale = (UsedArea_UV.left-centre.x)/(Nodes[i].x-centre.x);
			if(temp_scale<0) temp_scale=-temp_scale;
			scale=min(scale,temp_scale);
		}
		if(Nodes[i].x>UsedArea_UV.right)
		{
			float temp_scale = (UsedArea_UV.right-centre.x)/(Nodes[i].x-centre.x);
			if(temp_scale<0) temp_scale=-temp_scale;
			scale=min(scale,temp_scale);
		}
		if(Nodes[i].y<UsedArea_UV.top)
		{
			float temp_scale = (UsedArea_UV.top-centre.y)/(Nodes[i].y-centre.y);
			if(temp_scale<0) temp_scale=-temp_scale;
			scale=min(scale,temp_scale);
		}
		if(Nodes[i].y>UsedArea_UV.bottom)
		{
			float temp_scale = (UsedArea_UV.bottom-centre.y)/(Nodes[i].y-centre.y);
			if(temp_scale<0) temp_scale=-temp_scale;
			scale=min(scale,temp_scale);
		}
	}

	if(scale<1)
	{
		//go ahead and rescale the nodes
		for(i=0;i<4;i++)
		{
			Nodes[i]*=scale;
		}
	}	
	
}

void Tools_UV_Editor::InitaliseNodesForTextureWrap()
{
	Asura_Assert(NumNodes==4,"Shouldn't be calling this while in multiple nodes mode");
	bool ok = true;

	//first make sure that the nodes are in a rectangular formation
	//(compare pairs of opposite sides)
	//first pair
	Asura_Vector_2 side1 = Nodes[1]-Nodes[0];
	Asura_Vector_2 side2 = Nodes[2]-Nodes[3];
	if((side2-side1).Magnitude()>0.001f) ok = false;

	//second pair
	Asura_Vector_2 side3 = Nodes[2]-Nodes[1];
	Asura_Vector_2 side4 = Nodes[3]-Nodes[0];
	if((side4-side3).Magnitude()>0.001f) ok = false;

	//secondly check for nodes covering a too small area
	if(side1.Magnitude()<0.001f || side3.Magnitude()<0.001f) ok =false;

	if(!ok)
	{
		//move the nodes to the corners of the used area
		Nodes[0].x = UsedArea_UV.left;
		Nodes[0].y = UsedArea_UV.top;
		Nodes[1].x = UsedArea_UV.right;
		Nodes[1].y = UsedArea_UV.top;
		Nodes[2].x = UsedArea_UV.right;
		Nodes[2].y = UsedArea_UV.bottom;
		Nodes[3].x = UsedArea_UV.left;
		Nodes[3].y = UsedArea_UV.bottom;
	}
}


void Tools_UV_Editor::PrepareRotationOrScale(bool selected_only)
{
	//get the centre of the (selected nodes)
	DragCentre.x = 0;
	DragCentre.y = 0;

	for(int i=0;i<NumNodes;i++)
	{
		m_pxInitialNodes[i] = Nodes[i];
	}
	
	if(selected_only)
	{
		for(LIF<int> slif(&SelectedNodeList);!slif.done();slif.next())
		{
			DragCentre += Nodes[slif()];
		}
		DragCentre /= (float)max(1,SelectedNodeList.size());
	}
	else
	{
		for(int i=0;i<NumNodes;i++)
		{
			DragCentre += Nodes[i];
		}
		DragCentre /= (float)NumNodes;
	}
	
	
}

void Tools_UV_Editor::RotateNodes(const Asura_Vector_2& centre,Asura_Angle angle,bool selected_only)
{
	for (int i=0;i<NumNodes;i++)
	{
		if(selected_only && !SelectedNodeList.contains(i)) continue;

		//get position relative to the centre
		Asura_Vector_2 offset = m_pxInitialNodes[i] - centre;
		
		//rotate the nodes
		Nodes[i].x = offset.x*Asura_Maths::Cosine(angle) - offset.y*Asura_Maths::Sine(angle);
		Nodes[i].y = offset.x*Asura_Maths::Sine(angle) + offset.y*Asura_Maths::Cosine(angle);
	
		//and add the centre back on
		Nodes[i] += centre;
		
	}	
	//make sure the nodes are all on the used area of the texture
	ClampNodesToUsedArea(false);
}

void Tools_UV_Editor::ScaleNodes(const Asura_Vector_2& centre,float scale,bool selected_only,int iAxisRestrict)
{
	for (int i=0;i<NumNodes;i++)
	{
		if(selected_only && !SelectedNodeList.contains(i)) continue;
		//relocate relative to the centre
		Nodes[i] = m_pxInitialNodes[i]-centre;
		//scale
		if(iAxisRestrict != NodeEdit_YOnly)
			Nodes[i].x *= scale;
		if(iAxisRestrict != NodeEdit_XOnly)
			Nodes[i].y *= scale;
		//and add the centre back on
		Nodes[i] += centre;
	}	
	//make sure the nodes are all on the used area of the texture	
	ClampNodesToUsedArea(false);
}

void Tools_UV_Editor::ShiftAndScaleNodes(float fUOldOrigin,float fVOldOrigin,float fUNewOrigin,float fVNewOrigin,float fUScale,float fVScale)
{
	if(Tools_Mode_Texturing::TexturingMode == TexturingMode_NodeEdit)
	{
		//log for undo
		PrepareNodeUndo();
		NodesMoved = true;
	}

	for(int i=0;i<NumNodes;i++)
	{
		Nodes[i].x -= fUOldOrigin;
		Nodes[i].x *= fUScale;
		Nodes[i].x += fUNewOrigin;

		Nodes[i].y -= fVOldOrigin;
		Nodes[i].y *= fVScale;
		Nodes[i].y += fVNewOrigin;

	}

}

void Tools_UV_Editor::ClampNodesToUsedArea(bool log_undo /*= true*/)
{
	if(log_undo)
	{
		PrepareNodeUndo();
	}
	
	for(int i=0;i<NumNodes;i++)
	{
		if(Nodes[i].x>UsedArea_UV.right)
		{
			Nodes[i].x=UsedArea_UV.right;
		}
		else if(Nodes[i].x<UsedArea_UV.left)
		{
			Nodes[i].x=UsedArea_UV.left;
		}
		
		if(Nodes[i].y>UsedArea_UV.bottom)
		{
			Nodes[i].y=UsedArea_UV.bottom;
		}
		else if(Nodes[i].y<UsedArea_UV.top)
		{
			Nodes[i].y=UsedArea_UV.top;
		}
	}
}

void Tools_UV_Editor::ExpandUsedAreaToContainNodes()
{
	//log for undo
	NodeUndo.Log_UsedAreaChange(UsedArea_UV);
	
	//get the bound rectangle for the nodes
	Asura_Bounding_Rectangle bounds;
	bounds.Reset();

	for(int i=0;i<NumNodes;i++)
	{
		bounds.Set(Nodes[i]);
	}

	//expand the used area to contain the nodes
	Float_Rectangle rect = UsedArea_UV;
	if(bounds.MaxX>rect.right)
	{
		rect.right = (float)(int)bounds.MaxX;
		if(bounds.MaxX>rect.right) rect.right+=1.0f;
	}
	if(bounds.MinX>rect.left)
	{
		rect.left = (float)(int)bounds.MinX;
		if(bounds.MinX<rect.left) rect.left-=1.0f;
	}
	if(bounds.MaxY>rect.bottom)
	{
		rect.bottom = (float)(int)bounds.MaxY;
		if(bounds.MaxY>rect.bottom) rect.bottom+=1.0f;
	}
	if(bounds.MinY>rect.top)
	{
		rect.top = (float)(int)bounds.MinY;
		if(bounds.MinY<rect.top) rect.top-=1.0f;
	}

	//set the new area
	UpdateUsedTextureArea(rect);
}

void Tools_UV_Editor::FitUsedAreaToNodes()
{
	//log for undo
	NodeUndo.Log_UsedAreaChange(UsedArea_UV);
	
	//get the bound rectangle for the nodes
	Asura_Bounding_Rectangle bounds;
	bounds.Reset();

	for(int i=0;i<NumNodes;i++)
	{
		bounds.Set(Nodes[i]);
	}

	Float_Rectangle rect = UsedArea_UV;
	
	//set right and bottom extents so that they just contain the nodes
	//leave left and top at 0 , unless we have to make them lower to contain the nodes

	
	rect.top = (float)(int)bounds.MinY;
	if(bounds.MinY<rect.top) rect.top-=1.0f;
	
	rect.left = (float)(int)bounds.MinX;
	if(bounds.MinX<rect.left) rect.left-=1.0f;

	
	rect.bottom = (float)(int)bounds.MaxY;
	if(bounds.MaxY>rect.bottom) rect.bottom+=1.0f;
	
	rect.right = (float)(int)bounds.MaxX;
	if(bounds.MaxX>rect.right) rect.right+=1.0f;

	//make sure there is at least 1 tile of texture horizontally and vertically
	if(rect.left==rect.right) rect.right += 1.0f;
	if(rect.top==rect.bottom) rect.bottom += 1.0f;

	//set the new area
	UpdateUsedTextureArea(rect);
}

void Tools_UV_Editor::DetermineSuitableShownArea(Asura_Vector_2* DesiredCentre_UV /*= 0*/)
{
	/*
	Adjust the shown area of the texture to cope with any changes to
	make sure that it is within the used area , and is using a suitable amount 
	of the screen.
	*/

	//first clamp the shown area to the used area
	ShownArea_UV.left = max(ShownArea_UV.left,UsedArea_UV.left);
	ShownArea_UV.right = min(ShownArea_UV.right,UsedArea_UV.right);
	ShownArea_UV.top = max(ShownArea_UV.top,UsedArea_UV.top);
	ShownArea_UV.bottom = min(ShownArea_UV.bottom,UsedArea_UV.bottom);

	//now determine the desired width and height in screen coordinates
	//want at least one copy of the texture if possible
	float desired_width = GetWidthScale();
	float desired_height = GetHeightScale();
	float maximum_width;
	float maximum_height;

	//arbitrary number time...
	//try for about 50% of the height of the screen , but go no higher than 66%
	float desired = Asura_Viewport::GetHeight()*0.5f;
	maximum_height = Asura_Viewport::GetHeight()*0.66f;
	maximum_width = maximum_height;

	if(desired_width<desired)
	{
		desired_width = desired;
	}
	else if(desired_width>maximum_width)
	{
		desired_width = maximum_width;
	}
	if(desired_height<desired)
	{
		desired_height = desired;
	}
	else if(desired_height>maximum_height)
	{
		desired_height = maximum_height;
	}

	//scale down the width and height from pixels to uvs
	desired_width /= GetWidthScale();
	desired_height /= GetHeightScale();
	maximum_width /= GetWidthScale();
	maximum_height /= GetHeightScale();

	
	//get the current used width and height shown
	float current_width = ShownArea_UV.right - ShownArea_UV.left;
	float current_height = ShownArea_UV.bottom - ShownArea_UV.top;
	float new_width = current_width;
	float new_height = current_height;

	//if less than the desire values , increase if posssible
	if(new_width<desired_width)
	{
		new_width = min(desired_width,UsedArea_UV.right-UsedArea_UV.left);
	}
	if(new_height<desired_height)
	{
		new_height = min(desired_height,UsedArea_UV.bottom-UsedArea_UV.top);
	}

	//clamp to the maximum being allowed
	new_width = min(new_width,maximum_width);
	new_height = min(new_height,maximum_height);

	if(DesiredCentre_UV)
	{
		//start choosing the shown area so that it is centred on the value specified
		ShownArea_UV.left = DesiredCentre_UV->x - new_width/2;
		ShownArea_UV.right = DesiredCentre_UV->x + new_width/2;
		ShownArea_UV.top = DesiredCentre_UV->y - new_height/2;
		ShownArea_UV.bottom = DesiredCentre_UV->y + new_height/2;
	}
	
	//finally need to adjust the used area to use the new width and height
	if(new_width>current_width || DesiredCentre_UV)
	{
		ShownArea_UV.left=min(ShownArea_UV.right,UsedArea_UV.right) - new_width;
		if(ShownArea_UV.left<UsedArea_UV.left)
		{
			ShownArea_UV.left = UsedArea_UV.left;
		}
	}
	ShownArea_UV.right = ShownArea_UV.left + new_width;

	if(new_height>current_height|| DesiredCentre_UV)
	{
		ShownArea_UV.top=min(ShownArea_UV.bottom,UsedArea_UV.bottom) - new_height;
		if(ShownArea_UV.top<UsedArea_UV.top)
		{
			ShownArea_UV.top = UsedArea_UV.top;
		}
	}
	ShownArea_UV.bottom = ShownArea_UV.top + new_height;
	

	
}

void Tools_UV_Editor::UpdateUsedTextureArea(const Float_Rectangle& rect)
{
	//set the new area
	UsedArea_UV = rect;
	DetermineSuitableShownArea();

	//also need to update the TextureRepeats to reflect the new used area
	Tools_Mode_Texturing::TextureRepeatsOrigin.x = (float)(int)UsedArea_UV.left;
	Tools_Mode_Texturing::TextureRepeatsOrigin.y = (float)(int)UsedArea_UV.top;
	Tools_Mode_Texturing::TextureRepeats.x = max(1.0f,(int)(UsedArea_UV.right - UsedArea_UV.left));
	Tools_Mode_Texturing::TextureRepeats.y = max(1.0f,(int)(UsedArea_UV.bottom - UsedArea_UV.top));
	Tools_Mode_Texturing::TextureRepeatsChanged = false;

}

void Tools_UV_Editor::PrepareNodeUndo()
{
	if(Tools_Mode_Texturing::TexturingMode != TexturingMode_NodeEdit)
	{
		//only bother in node edit mode
		return;
	}

	for(int i=0;i<NumNodes;i++)
	{
		NodeUndo.Log_NodeMove(i,Nodes[i]);
	}
}

void Tools_UV_Editor::SetNumNodes(int new_num_nodes)
{
	if(BackupNodes)
	{
		//ought to be exiting multiple node mode.. so num_nodes should be 4
		Asura_Assert(new_num_nodes == 4,"");
		NumNodes = new_num_nodes;
	
		delete [] Nodes;
		Nodes = BackupNodes;
		BackupNodes  = NULL;
	}
	else
	{
		//ought to be entering multiple node mode
		Asura_Assert(NumNodes == 4,"");
		NumNodes = new_num_nodes;

		BackupNodes = Nodes;
		Nodes = new Asura_Vector_2[NumNodes];

	}

	//allocate new lot of initial nodes as we;;
	delete [] m_pxInitialNodes;
	m_pxInitialNodes = new Asura_Vector_2[NumNodes];

	//clear out the selected nodes
	while(SelectedNodeList.size())
	{
		SelectedNodeList.delete_first_entry();
	}

}

void Tools_UV_Editor::SelectAllNodesForDragIfNeeded()
{
	//if nothing is selected , select everything for the duration of the current operation
	if(!SelectedNodeList.size())
	{
		for(int i=0;i<NumNodes;i++)
		{
			SelectedNodeList.add_entry(i);
		}
		m_bClearSelectionAfterDrag = true;
	}
}

void Tools_UV_Editor::SnapCursorToNearestNode()
{
	//get the current cursor position
	Asura_Vector_2 xMousePos;
	int mx,my;
	Asura_Cursor::GetScreenSpaceCoords(mx,my);

	xMousePos.x = (float)mx;
	xMousePos.y = (float)my;

	Float_Rectangle xScreenRect = GetScreenPosition(ShownArea_UV);
	
	//find the nearest node
	float fLowDistance = Asura_Maths::LargeFloat;
	Asura_Vector_2 xNearest;
	for(int i=0;i<NumNodes;i++)
	{
		Asura_Vector_2 xPos = GetScreenPosition(Nodes[i]);
		//only pay attention to nodes that are in the visible area
		if(xScreenRect.left<=xPos.x && xScreenRect.right>=xPos.x &&
		   xScreenRect.top<=xPos.y && xScreenRect.bottom>=xPos.y)
		{
			float fDist = (xPos - xMousePos).Magnitude();
			if(fDist<fLowDistance)
			{
				fLowDistance = fDist;
				xNearest = xPos;
			}
		}
	}

	//set the cursor position
	if(fLowDistance!=Asura_Maths::LargeFloat)
	{
		Asura_Cursor::SetScreenSpaceCoords((int)xNearest.x,(int)xNearest.y);
	}
	
}

struct NodeGrid
{
	int iIndex;
	int iX;
	int iY;
	float fWeight;
	bool bDone;
};

static int NodeGridSort(const void *cmp1, const void *cmp2)
{
    NodeGrid* pxNode1 = *(NodeGrid**)cmp1;
	NodeGrid* pxNode2 = *(NodeGrid**)cmp2;
	
	if (pxNode1->fWeight > pxNode2->fWeight)
        return -1;
    if (pxNode1->fWeight < pxNode2->fWeight)
		return  1;

    return 0;
}


void Tools_UV_Editor::EvenlyDistributeUVs()
{

	if(Tools_Mode_Texturing::TexturingMode == TexturingMode_NodeEdit)
	{
		//log for undo
		PrepareNodeUndo();
		NodesMoved = true;
	}

	//initialise data for working out best grid positions for nodes
	NodeGrid* pxNodeGrid = new NodeGrid[NumNodes];
	NodeGrid** ppxSortedNodes = new NodeGrid*[NumNodes];
	for(int i=0;i<NumNodes;i++)
	{
		pxNodeGrid[i].iIndex = i;
		pxNodeGrid[i].iX = 0;
		pxNodeGrid[i].iY = 0;
		pxNodeGrid[i].fWeight = 0;
		pxNodeGrid[i].bDone = false;
		ppxSortedNodes[i] = &pxNodeGrid[i];
	}

	for(int i=0;i<NumNodes;i++)
	{
		//Update node sorting , so we deal with the best node next
		qsort(ppxSortedNodes+i,NumNodes-i,sizeof(NodeGrid*),NodeGridSort);

		Asura_Assert(!ppxSortedNodes[i]->bDone,"");
		int iNodeIndex = ppxSortedNodes[i]->iIndex;
		ppxSortedNodes[i]->bDone = false;

		//check all the connections from this node
		for(int j=0;;j++)
		{
			int iConnectedNode = Texturing_Multiple_Nodes::GetConnectedNode(iNodeIndex,j);
			if(iConnectedNode<0) break;
			if(pxNodeGrid[iConnectedNode].bDone) continue;

			//get direction of connection
			Asura_Vector_2 xLinkVector;
			xLinkVector = Nodes[iConnectedNode] - Nodes[iNodeIndex];
			if(xLinkVector.Magnitude()<0.001f) continue;
			xLinkVector.Normalise();

			int iX,iY;
			//which grid point is that closest to?
			
			if(Asura_Maths::Abs(xLinkVector.y)>Asura_Maths::Abs(xLinkVector.x))
			{
				iX = 0;
				iY = xLinkVector.y>0 ? 1 : -1;
			}
			else
			{
				iX = xLinkVector.x>0 ? 1 : -1;
				iY = 0;
			}
						
			Asura_Vector_2 xDiff = xLinkVector - Asura_Vector_2((float)iX,(float)iY);
			float fWeight = 1.0f - xDiff.Magnitude();

			//is this a better match for grid point than our current one?
			if(fWeight>pxNodeGrid[iConnectedNode].fWeight)
			{
				pxNodeGrid[iConnectedNode].fWeight = fWeight;
				pxNodeGrid[iConnectedNode].iX = pxNodeGrid[iNodeIndex].iX + iX;
				pxNodeGrid[iConnectedNode].iY = pxNodeGrid[iNodeIndex].iY + iY;
			}
			
		}
	}

	//get the min and max grid positions
	int iMinX = 0;
	int iMinY = 0;
	int iMaxX = 0;
	int iMaxY = 0;

	for(int i=0;i<NumNodes;i++)
	{
		if(iMinX > pxNodeGrid[i].iX) iMinX = pxNodeGrid[i].iX;
		if(iMaxX < pxNodeGrid[i].iX) iMaxX = pxNodeGrid[i].iX;
		if(iMinY > pxNodeGrid[i].iY) iMinY = pxNodeGrid[i].iY;
		if(iMaxY < pxNodeGrid[i].iY) iMaxY = pxNodeGrid[i].iY;
	}

	int iGridWidth = iMaxX-iMinX;
	if(!iGridWidth) iGridWidth = 1;
	
	int iGridHeight = iMaxY-iMinY;
	if(!iGridHeight) iGridHeight = 1;

	float fUVWidth = UsedArea_UV.right - UsedArea_UV.left;
	float fUVHeight = UsedArea_UV.bottom - UsedArea_UV.top;



	//now calculate all the UV coordinates for these positions
	for(int i=0;i<NumNodes;i++)
	{
		float fXLerp = (pxNodeGrid[i].iX - iMinX) / (float) iGridWidth;
		Nodes[i].x = UsedArea_UV.left + fUVWidth*fXLerp;

		float fYLerp = (pxNodeGrid[i].iY - iMinY) / (float) iGridHeight;
		Nodes[i].y = UsedArea_UV.top + fUVHeight*fYLerp;

	}

	delete [] pxNodeGrid;
	delete [] ppxSortedNodes;
}

float Tools_UV_Editor::GetWidthScale()
{
	float fScale = m_fScale;

	int iTextureID = GetTextureToDisplay();

	if(iTextureID != ASURA_TEXTURE_NONE)
	{
		int iWidth,iHeight;
		if(Tools_Texture_Management::GetTextureSize(iTextureID,iWidth,iHeight))
		{
			Asura_Assert(iHeight && iWidth,"expecting non-zero texture size");
			if(iWidth<iHeight)
			{
				fScale *= iWidth/(float)iHeight;
			}
		}
	}
	return fScale;
}

float Tools_UV_Editor::GetHeightScale()
{
	//set the height scale so as to keep the proportions of the current texture
	float fScale = m_fScale;

	int iTextureID = GetTextureToDisplay();

	if(iTextureID != ASURA_TEXTURE_NONE)
	{
		int iWidth,iHeight;
		if(Tools_Texture_Management::GetTextureSize(iTextureID,iWidth,iHeight))
		{
			Asura_Assert(iHeight && iWidth,"expecting non-zero texture size");
			if(iWidth>iHeight)
			{
				fScale *= iHeight/(float)iWidth;
			}
		}
	}
	return fScale;
}

void Tools_UV_Editor::SnapNodesToNearestTexel()
{
	for(int i=0;i<NumNodes;i++)
	{
		SnapNodeToNearestTexel(i);
	}

	NodesMoved = true;
}

void Tools_UV_Editor::SnapNodeToNearestTexel(int iIndex)
{
	//get the texture width and height
	int iTextureID = GetTextureToDisplay();

	if(iTextureID == ASURA_TEXTURE_NONE) return;

	int iWidth,iHeight;
	if(!Tools_Texture_Management::GetTextureSize(iTextureID,iWidth,iHeight)) return;
	if(!iWidth || !iHeight) return;

	//and move the node to nearest texel boundary
	Nodes[iIndex].x = Asura_Maths::FloatToInt_Near(Nodes[iIndex].x*iWidth)/(float)iWidth;
	Nodes[iIndex].y = Asura_Maths::FloatToInt_Near(Nodes[iIndex].y*iHeight)/(float)iHeight;
		
}

int Tools_UV_Editor::GetTextureToDisplay()
{
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	const Asura_MaterialResponse* const pxMaterial = Asura_MaterialResponse_System::GetMaterialByHashID( m_auMaterialResponseHashID[m_iTextureChannel] );
	// Make sure we don't get the default material
	if( m_auMaterialResponseHashID[m_iTextureChannel] != ASURA_HASH_ID_UNSET && pxMaterial )
	{
		return pxMaterial->GetTexture( Asura_MaterialResponse::ALBEDO_TEXTURE );
	}
#endif
	if(m_iMaterialID != ASURA_MATERIAL_NONE)
	{
		return Asura_Material_System::GetTextureID(m_iMaterialID,m_iTextureChannel);
	}

	return m_iTextureID;
}

////////////////////////////////////////////////////////////////////////////////////////
/*--------------**
** NodeEditUndo **
**--------------*/

/////////////////////////////////////////////////
//Undo node move
void NodeEditUndo::Log_NodeMove(int index,const Asura_Vector_2& position)
{
	RootUndoData** undo = AllocateNextUndoData();
	if(!undo) return;
	
	*undo = new NodeEditUndoData_NodeMoved(TheTexture,index,position);
}

NodeEditUndoData_NodeMoved::NodeEditUndoData_NodeMoved(Tools_UV_Editor* pxTextureEdit,int _index,const Asura_Vector_2& _position)
{
	m_pxTextureEdit = pxTextureEdit;
	index = _index;
	position = _position;
}

RootUndoData* NodeEditUndoData_NodeMoved::ProcessUndoData()
{
	//store the current position
	Asura_Vector_2 pos = m_pxTextureEdit->Nodes[index];

	//update the node's position
	m_pxTextureEdit->Nodes[index] = position;

	//flag the nodes as having moved
	m_pxTextureEdit->NodesMoved = true;
	
	//convert the undo data
	position = pos;

	return this;
}
/////////////////////////////////////////////////
//Undo used area change
void NodeEditUndo::Log_UsedAreaChange(const Float_Rectangle& rect)
{
	RootUndoData** undo = AllocateNextUndoData();
	if(!undo) return;
	
	*undo = new NodeEditUndoData_UsedAreaChange(TheTexture,rect);
}

NodeEditUndoData_UsedAreaChange::NodeEditUndoData_UsedAreaChange(Tools_UV_Editor* pxTextureEdit,const Float_Rectangle& _rect)
{
	m_pxTextureEdit = pxTextureEdit;
	rect = _rect;
}

RootUndoData* NodeEditUndoData_UsedAreaChange::ProcessUndoData()
{
	//store the current rectabgle
	Float_Rectangle temp = m_pxTextureEdit->UsedArea_UV;

	//set the new area
	m_pxTextureEdit->UpdateUsedTextureArea(rect);

	//convert the undo data
	rect = temp;
	return this;
}



void NodeEditUndo::SetNumNodes(int nodes)
{
	if(NumNodes != nodes)
	{
		//old undo data invalid
		FlushUndoBuffer();
	}
	NumNodes = nodes;
}

/////////////////////////////////////////////////
//Undo texture change
void NodeEditUndo::Log_TextureChange(int index)
{
//	RootUndoData** undo = AllocateNextUndoData();
//	if(!undo) return;
	
//	*undo = new NodeEditUndoData_TextureChange(TheTexture,index);
}

NodeEditUndoData_TextureChange::NodeEditUndoData_TextureChange(Tools_UV_Editor* pxTextureEdit,int _index)
{
//	m_pxTextureEdit = pxTextureEdit;
//	index = _index;
}

RootUndoData* NodeEditUndoData_TextureChange::ProcessUndoData()
{
	//store the current texture
//	int temp = m_pxTextureEdit->m_iMaterialID;

	//update the current texture
//	m_pxTextureEdit->ChangeMaterial(index);

	//convert the undo data
//	index = temp;

	return this;
}





