/**********************************************************************
 *<
	FILE: unwrap.cpp

	DESCRIPTION: A UVW map modifier unwraps the UVWs onto the image

	CREATED BY: Rolf Berteig

	HISTORY: 12/31/96

 *>	Copyright (c) 1996, All Rights Reserved.
 **********************************************************************/

#include "mods.h"
#include "iparamm.h"
#include "meshadj.h"
#include "sctex.h"
#include "gport.h"

#define UNWRAP_NAME		GetString(IDS_RB_UNWRAPMOD)
#define UNWRAP_CLASSID	Class_ID(0x02df2e3a,0x72ba4e1f)

class UnwrapMod;

static HCURSOR selCur   = NULL;
static HCURSOR moveCur  = NULL;
static HCURSOR rotCur   = NULL;
static HCURSOR scaleCur = NULL;


class SelectMode : public MouseCallBack {
	public:
		UnwrapMod *mod;
		BOOL region, toggle, subtract;
		IPoint2 om, lm;
		SelectMode(UnwrapMod *m) {mod=m;}
		int proc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
		virtual int subproc(HWND hWnd, int msg, int point, int flags, IPoint2 m)=0;
		virtual HCURSOR GetXFormCur()=0;
	};

class MoveMode : public SelectMode {
	public:				
		MoveMode(UnwrapMod *m) : SelectMode(m) {}
		int subproc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
		HCURSOR GetXFormCur() {return moveCur;}
	};
class RotateMode : public SelectMode {
	public:				
		RotateMode(UnwrapMod *m) : SelectMode(m) {}
		int subproc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
		HCURSOR GetXFormCur() {return rotCur;}
	};
class ScaleMode : public SelectMode {
	public:				
		ScaleMode(UnwrapMod *m) : SelectMode(m) {}
		int subproc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
		HCURSOR GetXFormCur() {return scaleCur;}
	};
class PanMode : public MouseCallBack {
	public:
		UnwrapMod *mod;
		IPoint2 om;
		float oxscroll, oyscroll;
		PanMode(UnwrapMod *m) {mod=m;}
		int proc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
	};
class ZoomMode : public MouseCallBack {
	public:
		UnwrapMod *mod;
		IPoint2 om;
		float ozoom;
		float oxscroll, oyscroll;
		ZoomMode(UnwrapMod *m) {mod=m;}
		int proc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
	};
class ZoomRegMode : public MouseCallBack {
	public:
		UnwrapMod *mod;
		IPoint2 om, lm;		
		ZoomRegMode(UnwrapMod *m) {mod=m;}
		int proc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
	};

class RightMouseMode : public MouseCallBack {
	public:
		UnwrapMod *mod;		
		RightMouseMode(UnwrapMod *m) {mod=m;}
		int proc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
	};



class UnwrapMod : public Modifier, TimeChangeCallback {	
	public:	
		Tab<Point3> tvert;
		Tab<TVFace> tvFace;
		Tab<Control*> cont;		
		BitArray vsel;
		float zoom, aspect, xscroll, yscroll;
		Texmap *map;
		UBYTE *image;
		int iw, ih, uvw, move;
		BOOL showMap, update;
		COLORREF lineColor, selColor;
		int rendW, rendH;
		int channel;

		static HWND hParams, hWnd, hView;
		static IObjParam  *ip;
		static UnwrapMod *editMod;
		static ICustToolbar *iTool;
		static ICustButton *iMove, *iRot, *iScale, *iPan, *iZoom, *iPickMap;
		static ICustButton *iZoomReg, *iZoomExt, *iUVW, *iProp, *iShowMap, *iUpdate;
		static ISpinnerControl *iU, *iV, *iW;
		static MouseManager mouseMan;
		static IOffScreenBuf *iBuf;
		static int mode;
		static MoveMode *moveMode;
		static RotateMode *rotMode;
		static ScaleMode *scaleMode;
		static PanMode *panMode;
		static ZoomMode *zoomMode;
		static ZoomRegMode *zoomRegMode;
		static RightMouseMode *rightMode;
		static BOOL viewValid, typeInsValid;

		UnwrapMod();

		// From Animatable
		void DeleteThis() { delete this; }
		void GetClassName(TSTR& s) {s=UNWRAP_NAME;}
		virtual Class_ID ClassID() {return UNWRAP_CLASSID;}
		void BeginEditParams(IObjParam  *ip, ULONG flags,Animatable *prev);
		void EndEditParams(IObjParam *ip,ULONG flags,Animatable *next);		
		TCHAR *GetObjectName() { return UNWRAP_NAME; }
		CreateMouseCallBack* GetCreateMouseCallBack() {return NULL;} 
		BOOL AssignController(Animatable *control,int subAnim);

		ChannelMask ChannelsUsed()  {return PART_GEOM|PART_TOPO|PART_SELECT|PART_SUBSEL_TYPE|TEXMAP_CHANNEL|PART_VERTCOLOR;}
		ChannelMask ChannelsChanged() {return TEXMAP_CHANNEL|PART_VERTCOLOR; }
		Class_ID InputType() {return triObjectClassID;}
		void ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node);
		Interval LocalValidity(TimeValue t);		

		int NumRefs() {return cont.Count()+1;}
		RefTargetHandle GetReference(int i);
		void SetReference(int i, RefTargetHandle rtarg);

		int NumSubs() {return cont.Count();}
		Animatable* SubAnim(int i);
		TSTR SubAnimName(int i);
		
		RefTargetHandle Clone(RemapDir& remap = NoRemap());
		RefResult NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message);
		IOResult Save(ISave *isave);
		IOResult Load(ILoad *iload);

		// From TimeChangeCallback
		void TimeChanged(TimeValue t) {InvalidateView();}

		// Local methods
		void EditMapping(HWND hWnd);
		void SynchWithMesh(Mesh &mesh);

		// Floater win methods
		void SetupDlg(HWND hWnd);
		void SizeDlg();
		void DestroyDlg();
		void PaintView();		
		void RegisterClasses();
		Point2 UVWToScreen(Point3 pt, float xzoom, float yzoom,int w,int h);
		void ComputeZooms(HWND hWnd, float &xzoom, float &yzoom,int &width,int &height);
		void SetMode(int m);
		void InvalidateView();
		BOOL HitTest(Rect rect,Tab<int> &hits,BOOL selOnly);
		void Select(Tab<int> &hits,BOOL toggle,BOOL subtract,BOOL all);
		void ClearSelect();
		void HoldPoints();
		void HoldSelection();
		void MovePoints(Point2 pt);
		void RotatePoints(float ang);
		void ScalePoints(float scale);
		void PickMap();
		void SetupImage();
		void GetUVWIndices(int &i1, int &i2);
		void PropDialog();
		void PlugControllers();
		Point3 GetPoint(TimeValue t,int i);
		void ZoomExtents();
		void Reset();
		void SetupChannelButtons();
		void SetupTypeins();
		void InvalidateTypeins();
		void TypeInChanged(int which);
	};

class UnwrapClassDesc:public ClassDesc {
	public:
	int 			IsPublic() {return 1;}
	void *			Create(BOOL loading = FALSE) {return new UnwrapMod;}
	const TCHAR *	ClassName() {return UNWRAP_NAME;}
	SClass_ID		SuperClassID() {return OSM_CLASS_ID;}
	Class_ID		ClassID() {return UNWRAP_CLASSID;}
	const TCHAR* 	Category() {return GetString(IDS_RB_DEFSURFACE);}
	};

static UnwrapClassDesc unwrapDesc;
ClassDesc* GetUnwrapModDesc() {return &unwrapDesc;}

#define ID_TOOL_MOVE	0x0100
#define ID_TOOL_ROTATE	0x0110
#define ID_TOOL_SCALE	0x0120
#define ID_TOOL_PAN		0x0130
#define ID_TOOL_ZOOM    0x0140
#define ID_TOOL_PICKMAP	0x0160
#define ID_TOOL_ZOOMREG 0x0170
#define ID_TOOL_UVW		0x0200
#define ID_TOOL_PROP	0x0210 
#define ID_TOOL_SHOWMAP	0x0220
#define ID_TOOL_UPDATE	0x0230
#define ID_TOOL_ZOOMEXT	0x0240

HWND            UnwrapMod::hParams = NULL;
HWND            UnwrapMod::hWnd = NULL;
HWND            UnwrapMod::hView = NULL;
IObjParam      *UnwrapMod::ip = NULL;
ICustToolbar   *UnwrapMod::iTool = NULL;
ICustButton    *UnwrapMod::iMove = NULL;
ICustButton    *UnwrapMod::iRot = NULL;
ICustButton    *UnwrapMod::iScale = NULL;
ICustButton    *UnwrapMod::iPan = NULL;
ICustButton    *UnwrapMod::iZoom = NULL;
ICustButton    *UnwrapMod::iPickMap = NULL;
ICustButton    *UnwrapMod::iUpdate = NULL;
ISpinnerControl *UnwrapMod::iU = NULL;
ISpinnerControl *UnwrapMod::iV = NULL;
ISpinnerControl *UnwrapMod::iW = NULL;
MouseManager    UnwrapMod::mouseMan;
IOffScreenBuf  *UnwrapMod::iBuf = NULL;
int             UnwrapMod::mode = ID_TOOL_MOVE;
MoveMode       *UnwrapMod::moveMode = NULL;
RotateMode     *UnwrapMod::rotMode = NULL;
ScaleMode      *UnwrapMod::scaleMode = NULL;
PanMode        *UnwrapMod::panMode = NULL;
ZoomMode       *UnwrapMod::zoomMode = NULL;
ZoomRegMode    *UnwrapMod::zoomRegMode = NULL;
RightMouseMode *UnwrapMod::rightMode = NULL;
BOOL            UnwrapMod::viewValid = FALSE;
BOOL            UnwrapMod::typeInsValid = FALSE;
UnwrapMod      *UnwrapMod::editMod = NULL;
ICustButton    *UnwrapMod::iZoomReg = NULL;
ICustButton    *UnwrapMod::iZoomExt = NULL;
ICustButton    *UnwrapMod::iUVW = NULL;
ICustButton    *UnwrapMod::iProp = NULL;
ICustButton    *UnwrapMod::iShowMap = NULL;

static BOOL CALLBACK UnwrapFloaterDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

class TVertRestore : public RestoreObj {
	public:
		UnwrapMod *mod;
		Tab<Point3> undo, redo;

		TVertRestore(UnwrapMod *m) {
			mod = m;
			undo = mod->tvert;
			}
		void Restore(int isUndo) {
			if (isUndo) {
				redo = mod->tvert;
				}
			mod->tvert = undo;
			mod->NotifyDependents(FOREVER,TEXMAP_CHANNEL,REFMSG_CHANGE);
			if (mod->editMod==mod && mod->hView) mod->InvalidateView();
			}
		void Redo() {
			mod->tvert = redo;
			mod->NotifyDependents(FOREVER,TEXMAP_CHANNEL,REFMSG_CHANGE);
			if (mod->editMod==mod && mod->hView) mod->InvalidateView();
			}
		void EndHold() {mod->ClearAFlag(A_HELD);}
		TSTR Description() {return TSTR(_T("Move UVWs"));}
	};

class TSelRestore : public RestoreObj {
	public:
		UnwrapMod *mod;
		BitArray undo, redo;

		TSelRestore(UnwrapMod *m) {
			mod = m;
			undo = mod->vsel;
			}
		void Restore(int isUndo) {
			if (isUndo) {
				redo = mod->vsel;
				}
			mod->vsel = undo;
			if (mod->editMod==mod && mod->hView) mod->InvalidateView();
			}
		void Redo() {
			mod->vsel = redo;
			if (mod->editMod==mod && mod->hView) mod->InvalidateView();
			}
		void EndHold() {mod->ClearAFlag(A_HELD);}
		TSTR Description() {return TSTR(_T("Select UVWs"));}
	};

class ResetRestore : public RestoreObj {
	public:
		UnwrapMod *mod;
		Tab<Point3> utvert, rtvert;
		Tab<TVFace> utvFace, rtvFace;
		Tab<Control*> ucont, rcont;
		BitArray uvsel, rvsel;
		int uchan, rchan;

		ResetRestore(UnwrapMod *m) {
			mod     = m;
			utvert  = mod->tvert;
			utvFace = mod->tvFace;
			ucont   = mod->cont;
			uvsel   = mod->vsel;
			uchan   = mod->channel;
			}
		void Restore(int isUndo) {
			if (isUndo) {
				rtvert  = mod->tvert;
				rtvFace = mod->tvFace;
				rcont   = mod->cont;
				rvsel   = mod->vsel;
				rchan   = mod->channel;
				}
			mod->tvert   = utvert;
			mod->tvFace  = utvFace;
			mod->cont   = ucont;
			mod->vsel    = uvsel;
			mod->channel = uchan;
			if (mod->editMod==mod && mod->hView) mod->InvalidateView();
			mod->SetupChannelButtons();
			}
		void Redo() {
			mod->tvert   = rtvert;
			mod->tvFace  = rtvFace;
			mod->cont   = rcont;
			mod->vsel    = rvsel;
			mod->channel = rchan;
			if (mod->editMod==mod && mod->hView) mod->InvalidateView();
			mod->SetupChannelButtons();
			}		
		TSTR Description() {return TSTR(_T("Reset Unwrap mod"));}
	};


//--- UnwrapMod methods -----------------------------------------------

UnwrapMod::UnwrapMod()
	{
	move    = 0;
	zoom    = 0.9f;
	aspect	= 1.0f;
	xscroll = 0.0f;
	yscroll = 0.0f;
	map = NULL;
	image = NULL;
	iw = ih = 0;
	uvw = 0;
	showMap = TRUE;	
	update = FALSE;
	lineColor = RGB(255,255,255);
	selColor  = RGB(255,0,0);
	rendW = 256;
	rendH = 256;
	channel = 0;
	}

static BOOL CALLBACK UnwrapRollupWndProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	UnwrapMod *mod = (UnwrapMod*)GetWindowLong(hWnd,GWL_USERDATA);
	
	switch (msg) {
		case WM_INITDIALOG:
			mod = (UnwrapMod*)lParam;
			SetWindowLong(hWnd,GWL_USERDATA,lParam);
			mod->hParams = hWnd;
			mod->SetupChannelButtons();
			break;

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_MAP_CHAN1:
				case IDC_MAP_CHAN2: {
					TSTR buf1 = GetString(IDS_RB_SHOULDRESET);
					TSTR buf2 = GetString(IDS_RB_UNWRAPMOD);
					int res = MessageBox(hWnd,buf1,buf2,MB_YESNO|MB_ICONQUESTION);
					theHold.Begin();
					if (res==IDYES) mod->Reset();
					mod->channel = IsDlgButtonChecked(hWnd,IDC_MAP_CHAN2);
					theHold.Accept(GetString(IDS_RB_SETCHANNEL));					
					break;
					}

				case IDC_UNWRAP_RESET: {
					TSTR buf1 = GetString(IDS_RB_RESETUNWRAPUVWS);
					TSTR buf2 = GetString(IDS_RB_UNWRAPMOD);
					if (IDYES==MessageBox(hWnd,buf1,buf2,MB_YESNO|MB_ICONQUESTION)) {
						theHold.Begin();
						mod->Reset();
						theHold.Accept(GetString(IDS_RB_RESETUVWS));
						mod->ip->RedrawViews(mod->ip->GetTime());
						}
					break;
					}

				case IDC_UNWRAP_EDIT:
					mod->EditMapping(hWnd);
					break;
				}
			break;

		default:
			return FALSE;
		}
	return TRUE;
	}

void UnwrapMod::SetupChannelButtons()
	{
	if (hParams && editMod==this) {		
		CheckDlgButton(hParams,IDC_MAP_CHAN2,channel);
		CheckDlgButton(hParams,IDC_MAP_CHAN1,!channel);
		}
	}

void UnwrapMod::Reset()
	{
	if (theHold.Holding()) theHold.Put(new ResetRestore(this));
	for (int i=0; i<cont.Count(); i++) DeleteReference(i+1);
	tvert.Resize(0);
	tvFace.Resize(0);
	cont.Resize(0);
	vsel.SetSize(0);
	NotifyDependents(FOREVER,0,REFMSG_CONTROLREF_CHANGE,TREE_VIEW_CLASS_ID,FALSE);	
	NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
	}

BOOL UnwrapMod::AssignController(Animatable *control,int subAnim)
	{
	ReplaceReference(subAnim+1,(RefTargetHandle)control);	
	NotifyDependents(FOREVER,0,REFMSG_CONTROLREF_CHANGE,TREE_VIEW_CLASS_ID,FALSE);	
	NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
	return TRUE;
	}

void UnwrapMod::BeginEditParams(
		IObjParam  *ip, ULONG flags,Animatable *prev)
	{
	selCur   = ip->GetSysCursor(SYSCUR_SELECT);
	moveCur	 = ip->GetSysCursor(SYSCUR_MOVE);
	rotCur   = ip->GetSysCursor(SYSCUR_ROTATE);
	scaleCur = ip->GetSysCursor(SYSCUR_USCALE);

	this->ip = ip;
	editMod  = this;
	hParams  = ip->AddRollupPage( 
		hInstance, 
		MAKEINTRESOURCE(IDD_UNWRAP_PARAMS),
		UnwrapRollupWndProc,
		GetString(IDS_RB_PARAMETERS),
		(LPARAM)this);	
	ip->RegisterTimeChangeCallback(this);
	}

void UnwrapMod::EndEditParams(
		IObjParam *ip,ULONG flags,Animatable *next)
	{	
	ip->UnRegisterTimeChangeCallback(this);
	if (hParams) ip->DeleteRollupPage(hParams);
	hParams  = NULL;	
	if (hWnd) DestroyWindow(hWnd);
	this->ip = NULL;
	editMod  = NULL;	
	}


void UnwrapMod::ModifyObject(
		TimeValue t, ModContext &mc, ObjectState *os, INode *node)
	{
	if (!os->obj->IsSubClassOf(triObjectClassID)) return;
	TriObject *tobj = (TriObject*)os->obj;
	SynchWithMesh(tobj->mesh);	

	// Apply our mapping
	Mesh &mesh = tobj->mesh;
	
	TVFace *tvFaceM = mesh.tvFace;
	Point3 *tVertsM = mesh.tVerts;
	if (channel) {
		tvFaceM = mesh.vcFace;
		tVertsM = mesh.vertCol;
		}

	if (mesh.selLevel!=MESH_FACE) {
		// Just copy the whole works in
		assert(tvFace.Count()==mesh.getNumFaces());
		if (channel) {
			mesh.setNumVCFaces(tvFace.Count());
			mesh.setNumVertCol(tvert.Count());
			tvFaceM = mesh.vcFace;
			tVertsM = mesh.vertCol;
		} else {
			mesh.setNumTVFaces(tvFace.Count());
			mesh.setNumTVerts(tvert.Count());
			tvFaceM = mesh.tvFace;
			tVertsM = mesh.tVerts;
			}
		for (int i=0; i<tvFace.Count(); i++) 
			tvFaceM[i] = tvFace[i];
		for (    i=0; i<tvert.Count(); i++) 
			tVertsM[i] = GetPoint(t,i);
	} else {
		if (!tvFaceM) {
			// Create tvfaces and init to 0
			if (channel) {
				mesh.setNumVCFaces(mesh.getNumFaces());
				tvFaceM = mesh.vcFace;
			} else {
				mesh.setNumTVFaces(mesh.getNumFaces());
				tvFaceM = mesh.tvFace;
				}
			for (int i=0; i<mesh.getNumFaces(); i++)
				for (int j=0; j<3; j++) tvFaceM[i].t[j] = 0;			
			}
		int otvc = channel ? mesh.getNumVertCol() : mesh.getNumTVerts();		
		if (channel) {
			mesh.setNumVertCol(otvc+tvert.Count(),TRUE);
			tVertsM = mesh.vertCol;
		} else {
			mesh.setNumTVerts(otvc+tvert.Count(),TRUE);
			tVertsM = mesh.tVerts;
			}
		int ix=0;
		// Copy new tvfaces on top of selected faces.
		for (int i=0; i<mesh.getNumFaces(); i++) {
			if (mesh.faceSel[i]) {
				tvFaceM[i] = tvFace[ix++];
				for (int j=0; j<3; j++) 
					tvFaceM[i].t[j] += otvc;
				}
			}

		// Append new tverts
		int numV = channel ? mesh.getNumVertCol() : mesh.getNumTVerts();
		for (i=otvc; i<numV; i++) {
			tVertsM[i] = GetPoint(t,i-otvc);
			}

		// Note: tverts that were only used by selected faces 
		// have now been isolated and could be removed.
		}
		
	os->obj->UpdateValidity(TEXMAP_CHAN_NUM,LocalValidity(t));	
	}

Interval UnwrapMod::LocalValidity(TimeValue t)
	{
	Interval iv = FOREVER;
	for (int i=0; i<cont.Count(); i++) {
		if (cont[i]) {
			cont[i]->GetValue(t,&tvert[i],iv);
			}
		}
	return iv;
	}

RefTargetHandle UnwrapMod::GetReference(int i)
	{
	if (i==0) return map;
	return cont[i-1];
	}

void UnwrapMod::SetReference(int i, RefTargetHandle rtarg)
	{
	if (i==0) map = (Texmap*)rtarg;
	else cont[i-1] = (Control*)rtarg;
	}

Animatable* UnwrapMod::SubAnim(int i)
	{
	return cont[i];
	}

TSTR UnwrapMod::SubAnimName(int i)
	{
	TSTR buf;
	buf.printf(_T("Point %d"),i+1);
	return buf;
	}


RefTargetHandle UnwrapMod::Clone(RemapDir& remap)
	{
	UnwrapMod *mod = new UnwrapMod;
	mod->tvert     = tvert;
	mod->tvFace    = tvFace;
	mod->cont      = cont;
	mod->vsel      = vsel;
	mod->zoom      = zoom;
	mod->aspect    = aspect;
	mod->xscroll   = xscroll;
	mod->yscroll   = yscroll;
	mod->uvw       = uvw;
	mod->showMap   = showMap;
	mod->update    = update;
	mod->lineColor = lineColor;
	mod->selColor  =	selColor;
	mod->rendW     = rendW;
	mod->rendH     = rendH;
	mod->ReplaceReference(0,remap.CloneRef(map));
	for (int i=0; i<cont.Count(); i++) {
		mod->cont[i] = NULL;		
		if (cont[i]) mod->ReplaceReference(i+1,remap.CloneRef(cont[i]));
		}
	return mod;
	}

RefResult UnwrapMod::NotifyRefChanged(
		Interval changeInt, 
		RefTargetHandle hTarget, 
		PartID& partID, 
		RefMessage message)
	{
	if (hTarget==map) return REF_STOP;
	switch (message) {
		case REFMSG_CHANGE:
			if (editMod==this && hView) {
				InvalidateView();
				}
			break;
		}
	return REF_SUCCEED;
	}

#define VERTCOUNT_CHUNK	0x0100
#define VERTS_CHUNK		0x0110
#define VERTSEL_CHUNK	0x0120
#define ZOOM_CHUNK		0x0130
#define ASPECT_CHUNK	0x0140
#define XSCROLL_CHUNK	0x0150
#define YSCROLL_CHUNK	0x0160
#define IWIDTH_CHUNK	0x0170
#define IHEIGHT_CHUNK	0x0180
#define SHOWMAP_CHUNK	0x0190
#define UPDATE_CHUNK	0x0200
#define LINECOLOR_CHUNK	0x0210
#define SELCOLOR_CHUNK	0x0220
#define FACECOUNT_CHUNK	0x0230
#define FACE_CHUNK		0x0240
#define UVW_CHUNK		0x0250

IOResult UnwrapMod::Save(ISave *isave)
	{
	ULONG nb;
	Modifier::Save(isave);
	int vct = tvert.Count(), fct = tvFace.Count();

	isave->BeginChunk(VERTCOUNT_CHUNK);
	isave->Write(&vct, sizeof(vct), &nb);
	isave->EndChunk();

	if (vct) {
		isave->BeginChunk(VERTS_CHUNK);
		isave->Write(tvert.Addr(0), sizeof(Point3)*vct, &nb);
		isave->EndChunk();
		}
	
	isave->BeginChunk(FACECOUNT_CHUNK);
	isave->Write(&fct, sizeof(fct), &nb);
	isave->EndChunk();

	if (fct) {
		isave->BeginChunk(FACE_CHUNK);
		isave->Write(tvFace.Addr(0), sizeof(TVFace)*fct, &nb);
		isave->EndChunk();
		}

	isave->BeginChunk(VERTSEL_CHUNK);
	vsel.Save(isave);
	isave->EndChunk();

	isave->BeginChunk(ZOOM_CHUNK);
	isave->Write(&zoom, sizeof(zoom), &nb);
	isave->EndChunk();

	isave->BeginChunk(ASPECT_CHUNK);
	isave->Write(&aspect, sizeof(aspect), &nb);
	isave->EndChunk();

	isave->BeginChunk(XSCROLL_CHUNK);
	isave->Write(&xscroll, sizeof(xscroll), &nb);
	isave->EndChunk();

	isave->BeginChunk(YSCROLL_CHUNK);
	isave->Write(&yscroll, sizeof(yscroll), &nb);
	isave->EndChunk();

	isave->BeginChunk(IWIDTH_CHUNK);
	isave->Write(&rendW, sizeof(rendW), &nb);
	isave->EndChunk();

	isave->BeginChunk(IHEIGHT_CHUNK);
	isave->Write(&rendH, sizeof(rendH), &nb);
	isave->EndChunk();

	isave->BeginChunk(UVW_CHUNK);
	isave->Write(&uvw, sizeof(uvw), &nb);
	isave->EndChunk();

	isave->BeginChunk(SHOWMAP_CHUNK);
	isave->Write(&showMap, sizeof(showMap), &nb);
	isave->EndChunk();

	isave->BeginChunk(UPDATE_CHUNK);
	isave->Write(&update, sizeof(update), &nb);
	isave->EndChunk();

	isave->BeginChunk(LINECOLOR_CHUNK);
	isave->Write(&lineColor, sizeof(lineColor), &nb);
	isave->EndChunk();

	isave->BeginChunk(SELCOLOR_CHUNK);
	isave->Write(&selColor, sizeof(selColor), &nb);
	isave->EndChunk();
	
	return IO_OK;
	}

IOResult UnwrapMod::Load(ILoad *iload)
	{
	IOResult res;
	ULONG nb;
	Modifier::Load(iload);
	int ct, i;

	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case VERTCOUNT_CHUNK:
				iload->Read(&ct, sizeof(ct), &nb);
				tvert.SetCount(ct);
				cont.SetCount(ct);
				vsel.SetSize(ct);
				for (i=0; i<ct; i++) cont[i] = NULL;				
				break;
			case FACECOUNT_CHUNK:
				iload->Read(&ct, sizeof(ct), &nb);
				tvFace.SetCount(ct);
				break;
			case FACE_CHUNK:
				iload->Read(tvFace.Addr(0), sizeof(TVFace)*tvFace.Count(), &nb);
				break;
			case VERTS_CHUNK:
				iload->Read(tvert.Addr(0), sizeof(Point3)*tvert.Count(), &nb);
				break;
			case VERTSEL_CHUNK:
				vsel.Load(iload);
				break;
			case ZOOM_CHUNK:
				iload->Read(&zoom, sizeof(zoom), &nb);
				break;
			case ASPECT_CHUNK:
				iload->Read(&aspect, sizeof(aspect), &nb);
				break;
			case XSCROLL_CHUNK:
				iload->Read(&xscroll, sizeof(xscroll), &nb);
				break;
			case YSCROLL_CHUNK:
				iload->Read(&yscroll, sizeof(yscroll), &nb);
				break;
			case IWIDTH_CHUNK:
				iload->Read(&rendW, sizeof(rendW), &nb);
				break;
			case IHEIGHT_CHUNK:
				iload->Read(&rendH, sizeof(rendH), &nb);
				break;
			case SHOWMAP_CHUNK:
				iload->Read(&showMap, sizeof(showMap), &nb);
				break;
			case UPDATE_CHUNK:
				iload->Read(&update, sizeof(update), &nb);
				break;
			case LINECOLOR_CHUNK:
				iload->Read(&lineColor, sizeof(lineColor), &nb);
				break;
			case SELCOLOR_CHUNK:
				iload->Read(&selColor, sizeof(selColor), &nb);
				break;			
			case UVW_CHUNK:
				iload->Read(&uvw, sizeof(uvw), &nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}

	return IO_OK;
	}

void UnwrapMod::EditMapping(HWND hWnd)
	{
	RegisterClasses();
	if (!this->hWnd) {
		CreateDialogParam(
			hInstance,
			MAKEINTRESOURCE(IDD_UNWRAP_FLOATER),
			hWnd,
			UnwrapFloaterDlgProc,
			(LPARAM)this);
	} else {
		SetForegroundWindow(this->hWnd);
		}
	}

void UnwrapMod::SynchWithMesh(Mesh &mesh)
	{
	int ct=0;
	if (mesh.selLevel==MESH_FACE) {
		for (int i=0; i<mesh.getNumFaces(); i++) {
			if (mesh.faceSel[i]) ct++;
			}
	} else {
		ct = mesh.getNumFaces();
		}
	if (ct != tvFace.Count()) {
		DeleteAllRefsFromMe();
		tvert.Resize(0);
		cont.Resize(0);
		tvFace.SetCount(ct);
		
		TVFace *tvFaceM = mesh.tvFace;
		Point3 *tVertsM = mesh.tVerts;
		int numTV = channel ? mesh.getNumVertCol() : mesh.getNumTVerts();
		if (channel) {
			tvFaceM = mesh.vcFace;
			tVertsM = mesh.vertCol;
			}

		if (mesh.selLevel==MESH_FACE) {
			// Mark tverts that are used by selected faces
			BitArray used;
			if (tvFaceM) used.SetSize(numTV);
			else used.SetSize(mesh.getNumVerts());
			for (int i=0; i<mesh.getNumFaces(); i++) {
				if (mesh.faceSel[i]) {
					if (tvFaceM) {
						for (int j=0; j<3; j++) 
							used.Set(tvFaceM[i].t[j]);
					} else {
						for (int j=0; j<3; j++) 
							used.Set(mesh.faces[i].v[j]);
						}
					}
				}

			// Now build a vmap
			Tab<DWORD> vmap;
			vmap.SetCount(used.GetSize());
			int ix=0;
			for (i=0; i<used.GetSize(); i++) {
				if (used[i]) vmap[i] = ix++;
				else vmap[i] = UNDEFINED;				
				}

			// Copy in tverts
			tvert.SetCount(ix);
			cont.SetCount(ix);
			vsel.SetSize(ix);
			ix = 0;
			Box3 box = mesh.getBoundingBox();
			for (i=0; i<used.GetSize(); i++) {
				if (used[i]) {
					cont[ix] = NULL;
					if (tvFaceM) tvert[ix++] = tVertsM[i];
					else {
						// Do a planar mapping if there are no tverts
						tvert[ix].x = mesh.verts[i].x/box.Width().x + 0.5f;
						tvert[ix].y = mesh.verts[i].y/box.Width().y + 0.5f;
						tvert[ix].z = mesh.verts[i].z/box.Width().z + 0.5f;
						ix++;
						}
					}
				}

			// Copy in face and remap indices		
			ix = 0;
			for (i=0; i<mesh.getNumFaces(); i++) {
				if (mesh.faceSel[i]) {
					if (tvFaceM) tvFace[ix] = tvFaceM[i];
					else {
						for (int j=0; j<3; j++) 
							tvFace[ix].t[j] = mesh.faces[i].v[j];
						}

					for (int j=0; j<3; j++) {
						tvFace[ix].t[j] = vmap[tvFace[ix].t[j]];
						}
					ix++;
					}
				}
		} else {
			// Just copy all the tverts and faces
			if (tvFaceM) {
				tvert.SetCount(numTV);
				cont.SetCount(numTV);
				vsel.SetSize(numTV);
				for (int i=0; i<numTV; i++) {
					tvert[i] = tVertsM[i];
					cont[i]  = NULL;
					}
				for (i=0; i<mesh.getNumFaces(); i++) {
					tvFace[i] = tvFaceM[i];
					}
			} else {
				Box3 box = mesh.getBoundingBox();
				tvert.SetCount(mesh.getNumVerts());
				cont.SetCount(mesh.getNumVerts());
				vsel.SetSize(mesh.getNumVerts());
				for (int i=0; i<mesh.getNumVerts(); i++) {
					// Do a planar mapping if there are no tverts
					tvert[i].x = mesh.verts[i].x/box.Width().x + 0.5f;
					tvert[i].y = mesh.verts[i].y/box.Width().y + 0.5f;
					tvert[i].z = mesh.verts[i].z/box.Width().z + 0.5f;
					cont[i]  = NULL;
					}
				for (i=0; i<mesh.getNumFaces(); i++) {
					for (int j=0; j<3; j++) 
						tvFace[i].t[j] = mesh.faces[i].v[j];
					}
				}
			}
		if (hView && editMod==this) {
			InvalidateView();
			}
		}
	}

void UnwrapMod::GetUVWIndices(int &i1, int &i2)
	{
	switch (uvw) {
		case 0: i1 = 0; i2 = 1; break;
		case 1: i1 = 1; i2 = 2; break;
		case 2: i1 = 0; i2 = 2; break;
		}
	}


//--- Floater Dialog -------------------------------------------------


#define TOOL_HEIGHT		30
#define SPINNER_HEIGHT	30

#define WM_SETUPMOD	WM_USER+0x18de

static HIMAGELIST hToolImages = NULL;
class DeleteResources {
	public:
		~DeleteResources() {
			if (hToolImages) ImageList_Destroy(hToolImages);			
			}
	};
static DeleteResources	theDelete;


static BOOL CALLBACK UnwrapFloaterDlgProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	UnwrapMod *mod = (UnwrapMod*)GetWindowLong(hWnd,GWL_USERDATA);

	switch (msg) {
		case WM_INITDIALOG:
			mod = (UnwrapMod*)lParam;
			SetWindowLong(hWnd,GWL_USERDATA,lParam);
			mod->SetupDlg(hWnd);
			break;

		case WM_SIZE:
			mod->SizeDlg();
			break;

		case WM_PAINT: {
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hWnd,&ps);
			Rect rect;
			GetClientRect(hWnd,&rect);			
			rect.top += TOOL_HEIGHT-2;
			SelectObject(hdc,GetStockObject(WHITE_BRUSH));			
			WhiteRect3D(hdc,rect,TRUE);
			EndPaint(hWnd,&ps);
			break;
			}

		case CC_SPINNER_BUTTONDOWN:
			theHold.SuperBegin();
			mod->PlugControllers();			
			theHold.Begin();
			break;

		case CC_SPINNER_CHANGE:
			if (!theHold.Holding()) {
				theHold.SuperBegin();
				mod->PlugControllers();			
				theHold.Begin();
				}
			
			switch (LOWORD(wParam)) {
				case IDC_UNWRAP_USPIN:
					mod->TypeInChanged(0);
					break;
				case IDC_UNWRAP_VSPIN:
					mod->TypeInChanged(1);
					break;
				case IDC_UNWRAP_WSPIN:
					mod->TypeInChanged(2);
					break;
				}
			UpdateWindow(hWnd);
			break;

		case WM_CUSTEDIT_ENTER:
		case CC_SPINNER_BUTTONUP:
			if (HIWORD(wParam) || msg==WM_CUSTEDIT_ENTER) {
				theHold.Accept(_T("Move UVWs"));
				theHold.SuperAccept(_T("Move UVWs"));
			} else {
				theHold.Cancel();
				theHold.SuperCancel();
				mod->NotifyDependents(FOREVER,TEXMAP_CHANNEL,REFMSG_CHANGE);
				mod->InvalidateView();
				UpdateWindow(hWnd);
				mod->ip->RedrawViews(mod->ip->GetTime());
				}
			break;

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case ID_TOOL_MOVE:
				case ID_TOOL_ROTATE:
				case ID_TOOL_SCALE:
				case ID_TOOL_PAN:
				case ID_TOOL_ZOOM:
				case ID_TOOL_ZOOMREG:
					mod->move = mod->iMove->GetCurFlyOff();
					mod->SetMode(LOWORD(wParam));
					break;				

				case ID_TOOL_ZOOMEXT:
					mod->ZoomExtents();
					break;

				case ID_TOOL_PICKMAP:
					mod->PickMap();
					break;
				
				case ID_TOOL_UVW:
					mod->uvw = mod->iUVW->GetCurFlyOff();
					mod->InvalidateView();
					break;

				case ID_TOOL_PROP:
					mod->PropDialog();
					break;

				case ID_TOOL_SHOWMAP:
					mod->showMap = mod->iShowMap->IsChecked();
					mod->InvalidateView();
					break;

				case ID_TOOL_UPDATE:
					mod->SetupImage();
					mod->InvalidateView();
					break;
				}
			break;

		case WM_CLOSE:
			DestroyWindow(hWnd);
			break;

		case WM_DESTROY:
			mod->DestroyDlg();
			break;

		default:
			return FALSE;
		}
	return TRUE;
	}

static LRESULT CALLBACK UnwrapViewProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	UnwrapMod *mod = (UnwrapMod*)GetWindowLong(hWnd,GWL_USERDATA);

	switch (msg) {
		case WM_CREATE:
			break;

		case WM_SIZE:			
			if (mod) {
				mod->iBuf->Resize();
				mod->InvalidateView();
				}
			break;

		case WM_PAINT:
			if (mod) mod->PaintView();
			break;

		case WM_LBUTTONDOWN:
		case WM_LBUTTONDBLCLK:
		case WM_LBUTTONUP:		
		case WM_RBUTTONDOWN:
		case WM_RBUTTONUP:
		case WM_RBUTTONDBLCLK:
		case WM_MOUSEMOVE:
			return mod->mouseMan.MouseWinProc(hWnd,msg,wParam,lParam);

		default:
			return DefWindowProc(hWnd,msg,wParam,lParam);
		}	
	return 0;
	}

void UnwrapMod::DestroyDlg()
	{
	ReleaseICustToolbar(iTool);
	iTool = NULL;
	ReleaseICustButton(iMove   ); iMove    = NULL;
	ReleaseICustButton(iRot    ); iRot     = NULL;
	ReleaseICustButton(iScale  ); iScale   = NULL;
	ReleaseICustButton(iPan    ); iPan     = NULL;
	ReleaseICustButton(iZoom   ); iZoom    = NULL;
	ReleaseICustButton(iPickMap); iPickMap = NULL;
	ReleaseICustButton(iUpdate ); iUpdate  = NULL;
	ReleaseICustButton(iZoomReg); iZoomReg = NULL;
	ReleaseICustButton(iZoomExt); iZoomExt = NULL;
	ReleaseICustButton(iUVW	   ); iUVW	   = NULL;
	ReleaseICustButton(iProp   ); iProp    = NULL;
	ReleaseICustButton(iShowMap); iShowMap = NULL;
	ReleaseISpinner(iU); iU = NULL;
	ReleaseISpinner(iV); iV = NULL;
	ReleaseISpinner(iW); iW = NULL;

	DestroyIOffScreenBuf(iBuf); iBuf   = NULL;
	hWnd = NULL;
	delete moveMode; moveMode = NULL;
	delete rotMode; rotMode = NULL;
	delete scaleMode; scaleMode = NULL;
	delete panMode; panMode = NULL;
	delete zoomMode; zoomMode = NULL;
	delete zoomRegMode; zoomRegMode = NULL;
	delete rightMode; rightMode = NULL;	
	mouseMan.SetMouseProc(NULL,LEFT_BUTTON,0);
	mouseMan.SetMouseProc(NULL,RIGHT_BUTTON,0);
	}

void UnwrapMod::SetupDlg(HWND hWnd)
	{
	this->hWnd = hWnd;
	
	hView = GetDlgItem(hWnd,IDC_UNWRAP_VIEW);
	SetWindowLong(hView,GWL_USERDATA,(LONG)this);
	iBuf = CreateIOffScreenBuf(hView);
	iBuf->SetBkColor(RGB(150,150,150));
	viewValid    = FALSE;
	typeInsValid = FALSE;

	moveMode = new MoveMode(this);
	rotMode = new RotateMode(this);
	scaleMode = new ScaleMode(this);
	panMode = new PanMode(this);
	zoomMode = new ZoomMode(this);
	zoomRegMode = new ZoomRegMode(this);
	rightMode = new RightMouseMode(this);
	mouseMan.SetMouseProc(rightMode,RIGHT_BUTTON,1);

	iU = GetISpinner(GetDlgItem(hWnd,IDC_UNWRAP_USPIN));
	iU->LinkToEdit(GetDlgItem(hWnd,IDC_UNWRAP_U),EDITTYPE_FLOAT);
	iU->SetLimits(-9999999, 9999999, FALSE);
	iU->SetAutoScale();
	
	iV = GetISpinner(GetDlgItem(hWnd,IDC_UNWRAP_VSPIN));
	iV->LinkToEdit(GetDlgItem(hWnd,IDC_UNWRAP_V),EDITTYPE_FLOAT);
	iV->SetLimits(-9999999, 9999999, FALSE);
	iV->SetAutoScale();
	
	iW = GetISpinner(GetDlgItem(hWnd,IDC_UNWRAP_WSPIN));
	iW->LinkToEdit(GetDlgItem(hWnd,IDC_UNWRAP_W),EDITTYPE_FLOAT);
	iW->SetLimits(-9999999, 9999999, FALSE);
	iW->SetAutoScale();	

	iTool = GetICustToolbar(GetDlgItem(hWnd,IDC_UNWARP_TOOLBAR));
	iTool->SetBottomBorder(TRUE);	
	iTool->SetImage(hToolImages);
	iTool->AddTool(ToolSeparatorItem(5));
	iTool->AddTool(
		ToolButtonItem(CTB_CHECKBUTTON,
			0, 0, 1, 1, 16, 15, 23, 22, ID_TOOL_MOVE));
	iTool->AddTool(
		ToolButtonItem(CTB_CHECKBUTTON,
			2, 2, 3, 3, 16, 15, 23, 22, ID_TOOL_ROTATE));
	iTool->AddTool(
		ToolButtonItem(CTB_CHECKBUTTON,
			4, 4, 5, 5, 16, 15, 23, 22, ID_TOOL_SCALE));
	
	iTool->AddTool(ToolSeparatorItem(10));
	iTool->AddTool(
		ToolButtonItem(CTB_CHECKBUTTON,
			6, 6, 7, 7, 16, 15, 23, 22, ID_TOOL_PAN));
	iTool->AddTool(
		ToolButtonItem(CTB_CHECKBUTTON,
			8, 8, 9, 9, 16, 15, 23, 22, ID_TOOL_ZOOM));
	iTool->AddTool(
		ToolButtonItem(CTB_CHECKBUTTON,
			10, 10, 11, 11, 16, 15, 23, 22, ID_TOOL_ZOOMREG));
	iTool->AddTool(
		ToolButtonItem(CTB_PUSHBUTTON,
			22, 22, 22, 22, 16, 15, 23, 22, ID_TOOL_ZOOMEXT));

	iTool->AddTool(ToolSeparatorItem(10));
	iTool->AddTool(
		ToolButtonItem(CTB_PUSHBUTTON,
			0, 0, 0, 0, 16, 15, 70, 22, ID_TOOL_PICKMAP));
	iTool->AddTool(
		ToolButtonItem(CTB_PUSHBUTTON,
			0, 0, 0, 0, 16, 15, 70, 22, ID_TOOL_UPDATE));
	iTool->AddTool(
		ToolButtonItem(CTB_CHECKBUTTON,
			12, 12, 13, 13, 16, 15, 23, 22, ID_TOOL_SHOWMAP));
	iTool->AddTool(
		ToolButtonItem(CTB_PUSHBUTTON,
			18, 18, 18, 18, 16, 15, 23, 22, ID_TOOL_UVW));
	
	iTool->AddTool(ToolSeparatorItem(10));
	iTool->AddTool(
		ToolButtonItem(CTB_PUSHBUTTON,
			21, 21, 21, 21, 16, 15, 23, 22, ID_TOOL_PROP));

	iMove    = iTool->GetICustButton(ID_TOOL_MOVE);
	iRot     = iTool->GetICustButton(ID_TOOL_ROTATE);
	iScale   = iTool->GetICustButton(ID_TOOL_SCALE);	
	iPan     = iTool->GetICustButton(ID_TOOL_PAN);	
	iZoom    = iTool->GetICustButton(ID_TOOL_ZOOM);
	iPickMap = iTool->GetICustButton(ID_TOOL_PICKMAP);
	iUpdate  = iTool->GetICustButton(ID_TOOL_UPDATE);
	iZoomExt = iTool->GetICustButton(ID_TOOL_ZOOMEXT);
	iZoomReg = iTool->GetICustButton(ID_TOOL_ZOOMREG);
	iUVW	 = iTool->GetICustButton(ID_TOOL_UVW);
	iProp	 = iTool->GetICustButton(ID_TOOL_PROP);
	iShowMap = iTool->GetICustButton(ID_TOOL_SHOWMAP);
	
	iMove->SetTooltip(TRUE,GetString(IDS_RB_MOVE));
	iRot->SetTooltip(TRUE,GetString(IDS_RB_ROTATE));
	iScale->SetTooltip(TRUE,GetString(IDS_RB_SCALE));
	iPan->SetTooltip(TRUE,GetString(IDS_RB_PAN));
	iZoom->SetTooltip(TRUE,GetString(IDS_RB_ZOOM));
	iPickMap->SetTooltip(TRUE,GetString(IDS_RB_PICKMAP));
	iUpdate->SetTooltip(TRUE,GetString(IDS_RB_UPDATE));
	iZoomExt->SetTooltip(TRUE,GetString(IDS_RB_ZOOMEXT));
	iZoomReg->SetTooltip(TRUE,GetString(IDS_RB_ZOOMREG));
	iUVW->SetTooltip(TRUE,GetString(IDS_RB_UVW));
	iProp->SetTooltip(TRUE,GetString(IDS_RB_PROP));
	iShowMap->SetTooltip(TRUE,GetString(IDS_RB_SHOWMAP));

	iMove->SetHighlightColor(GREEN_WASH);
	iRot->SetHighlightColor(GREEN_WASH);
	iScale->SetHighlightColor(GREEN_WASH);
	iPan->SetHighlightColor(GREEN_WASH);
	iZoom->SetHighlightColor(GREEN_WASH);	
	iZoomReg->SetHighlightColor(GREEN_WASH);	

	iPickMap->SetImage(NULL,0,0,0,0,0,0);
	iPickMap->SetText(_T("Pick Map"));
	iUpdate->SetImage(NULL,0,0,0,0,0,0);
	iUpdate->SetText(_T("Update"));

	FlyOffData fdata1[] = {
		{18, 18, 18, 18},
		{19, 19, 19, 19},
		{20, 20, 20, 20}};
	iUVW->SetFlyOff(3,fdata1,ip->GetFlyOffTime(),uvw,FLY_DOWN);

	FlyOffData fdata2[] = {
		{ 0,  0,  1,  1},
		{14, 14, 15, 15},
		{16, 16, 17, 17}};
	iMove->SetFlyOff(3,fdata2,ip->GetFlyOffTime(),move,FLY_DOWN);

	iShowMap->SetCheck(showMap);
	if (image) iShowMap->Enable();
	else iShowMap->Disable();

	SizeDlg();
	SetMode(mode);
	}

static void SetWindowYPos(HWND hWnd,int y)
	{
	Rect rect;
	GetClientRectP(hWnd,&rect);
	SetWindowPos(hWnd,NULL,rect.left,y,0,0,SWP_NOSIZE|SWP_NOZORDER);
	}

void UnwrapMod::SizeDlg()
	{
	Rect rect;
	GetClientRect(hWnd,&rect);
	MoveWindow(GetDlgItem(hWnd,IDC_UNWARP_TOOLBAR),
		0, 0, rect.w()-1, TOOL_HEIGHT, TRUE);
	MoveWindow(GetDlgItem(hWnd,IDC_UNWRAP_VIEW),
		2, TOOL_HEIGHT, rect.w()-5, rect.h()-TOOL_HEIGHT-SPINNER_HEIGHT-3,FALSE);
	
	int ys = rect.h()-TOOL_HEIGHT+3;
	int yl = rect.h()-TOOL_HEIGHT+5;
	
	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_ULABEL),yl);
	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_VLABEL),yl);
	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_WLABEL),yl);

	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_U),ys);
	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_V),ys);
	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_W),ys);
	
	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_USPIN),ys);
	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_VSPIN),ys);
	SetWindowYPos(GetDlgItem(hWnd,IDC_UNWRAP_WSPIN),ys);

	InvalidateRect(hWnd,NULL,TRUE);
	}

Point2 UnwrapMod::UVWToScreen(Point3 pt,float xzoom, float yzoom,int w,int h)
	{	
	int i1, i2;
	GetUVWIndices(i1,i2);
	int tx = (w-int(xzoom))/2;
	int ty = (h-int(yzoom))/2;
	return Point2(pt[i1]*xzoom+xscroll+tx, (float(h)-pt[i2]*yzoom)+yscroll-ty);
	}

void UnwrapMod::ComputeZooms(
		HWND hWnd, float &xzoom, float &yzoom,int &width, int &height)
	{
 	Rect rect;
	GetClientRect(hWnd,&rect);	
	width = rect.w()-1;
	height = rect.h()-1;
	xzoom = zoom*aspect*float(height);
	yzoom = zoom*float(height);
	}

void UnwrapMod::PaintView()
	{	
	PAINTSTRUCT		ps;
	BeginPaint(hView,&ps);
	EndPaint(hView,&ps);
	TimeValue t = ip->GetTime();

	if (!image && map) SetupImage();

	SetupTypeins();
	
	if (!viewValid) {
		viewValid = TRUE;
		Point2 pt[3];
		float xzoom, yzoom;
		int width,height;
		ComputeZooms(hView,xzoom,yzoom,width,height);
		iBuf->Erase();
		HDC hdc = iBuf->GetDC();
		int i1, i2;
		GetUVWIndices(i1,i2);

		if (image && showMap) {						
			Point3 p(0,0,0);
			p[i2] = 1.0f;
			Point2 sp;
			sp = UVWToScreen(p,xzoom,yzoom,width,height);
			Rect dest;
			dest.left   = (int)sp.x;
			dest.top    = (int)sp.y;
			dest.right  = dest.left + int(xzoom)-1;
			dest.bottom = dest.top + int(yzoom)-1;
			Rect src;
			src.left   = src.top = 0;
			src.right  = iw-1;
			src.bottom = ih-1;
			GetGPort()->DisplayMap(hdc, dest, src, image, ByteWidth(iw*3));
		} else {
			HPEN gPen = CreatePen(PS_SOLID,2,RGB(100,100,100));
			Point3 p1(0,0,0),p2(0,0,0);
			Point2 sp1, sp2;
			p2[i1] = 1.0f;
			p2[i2] = 1.0f;
			sp1 = UVWToScreen(p1,xzoom,yzoom,width,height);
			sp2 = UVWToScreen(p2,xzoom,yzoom,width,height);			
			SelectObject(hdc,gPen);
			Rectangle(hdc,(int)sp1.x,(int)sp2.y,(int)sp2.x,(int)sp1.y);
			SelectObject(hdc,GetStockObject(WHITE_PEN));
			DeleteObject(gPen);
			}

		// Paint faces		
		HPEN selPen   = CreatePen(PS_SOLID,2,selColor);
		HPEN unselPen = CreatePen(PS_SOLID,0,lineColor);
		
		SelectObject(hdc,unselPen);
		for (int i=0; i<tvFace.Count(); i++) {
			// Grap the three points, xformed
			for (int j=0; j<3; j++) {
				pt[j] = UVWToScreen(GetPoint(t,tvFace[i].t[j]),xzoom,yzoom,width,height);
				}
			// Now draw the face
			MoveToEx(hdc,(int)pt[0].x, (int)pt[0].y, NULL);
			for (j=0; j<3; j++) {
				LineTo(hdc,(int)pt[(j+1)%3].x, (int)pt[(j+1)%3].y);
				}
			}

		// Now paint points		
		for (i=0; i<tvert.Count(); i++) {
			pt[0] = UVWToScreen(GetPoint(t,i),xzoom,yzoom,width,height);
			if (vsel[i]) SelectObject(hdc,selPen);
			else SelectObject(hdc,unselPen);
			Rectangle(hdc,
				(int)pt[0].x-3,(int)pt[0].y-3,
				(int)pt[0].x+3,(int)pt[0].y+3);					
			}
		SelectObject(hdc,GetStockObject(BLACK_PEN));
		DeleteObject(selPen);
		DeleteObject(unselPen);		
		}		
	iBuf->Blit();
	}

void UnwrapMod::InvalidateView()
	{
	InvalidateTypeins();
	viewValid = FALSE;
	InvalidateRect(hView,NULL,TRUE);
	}

void UnwrapMod::SetMode(int m)
	{
	switch (mode) {
		case ID_TOOL_MOVE:    iMove->SetCheck(FALSE);   break;
		case ID_TOOL_ROTATE:  iRot->SetCheck(FALSE);    break;
		case ID_TOOL_SCALE:   iScale->SetCheck(FALSE);  break;
		case ID_TOOL_PAN:     iPan->SetCheck(FALSE);    break;
		case ID_TOOL_ZOOM:    iZoom->SetCheck(FALSE);   break;
		case ID_TOOL_ZOOMREG: iZoomReg->SetCheck(FALSE);break;
		}

	mode = m;

	switch (mode) {
		case ID_TOOL_MOVE:   
			iMove->SetCheck(TRUE);  
			mouseMan.SetMouseProc(moveMode, LEFT_BUTTON);
			break;

		case ID_TOOL_ROTATE: 
			iRot->SetCheck(TRUE);   
			mouseMan.SetMouseProc(rotMode, LEFT_BUTTON);
			break;

		case ID_TOOL_SCALE:  
			iScale->SetCheck(TRUE); 
			mouseMan.SetMouseProc(scaleMode, LEFT_BUTTON);
			break;

		case ID_TOOL_PAN:    
			iPan->SetCheck(TRUE);   
			mouseMan.SetMouseProc(panMode, LEFT_BUTTON);
			break;

		case ID_TOOL_ZOOM:   
			iZoom->SetCheck(TRUE);
			mouseMan.SetMouseProc(zoomMode, LEFT_BUTTON);
			break;

		case ID_TOOL_ZOOMREG:
			iZoomReg->SetCheck(TRUE);
			mouseMan.SetMouseProc(zoomRegMode, LEFT_BUTTON);
			break;
		}
	}

void UnwrapMod::RegisterClasses()
	{
	if (!hToolImages) {
		HBITMAP hBitmap, hMask;	
		hToolImages = ImageList_Create(16, 15, TRUE, 4, 0);
		hBitmap = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_UNWRAPTOOL));
		hMask   = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MASK_UNWRAPTOOL));
		ImageList_Add(hToolImages,hBitmap,hMask);
		DeleteObject(hBitmap);
		DeleteObject(hMask);
		}

	static BOOL registered = FALSE;
	if (!registered) {
		registered = TRUE;
		WNDCLASS  wc;
		wc.style         = 0;
		wc.hInstance     = hInstance;
		wc.hIcon         = NULL;
		wc.hCursor       = NULL;
		wc.hbrBackground = NULL; //(HBRUSH)GetStockObject(WHITE_BRUSH);	
		wc.lpszMenuName  = NULL;
		wc.cbClsExtra    = 0;
		wc.cbWndExtra    = 0;
		wc.lpfnWndProc   = UnwrapViewProc;
		wc.lpszClassName = _T("UnwrapView");
		RegisterClass(&wc);
		}
	}

BOOL UnwrapMod::HitTest(Rect rect,Tab<int> &hits,BOOL selOnly)
	{
	Point2 pt;
	float xzoom, yzoom;
	int width,height;
	TimeValue t = ip->GetTime();
	ComputeZooms(hView,xzoom,yzoom,width,height);	
	for (int i=0; i<tvert.Count(); i++) {
		if (selOnly && !vsel[i]) continue;
		pt = UVWToScreen(GetPoint(t,i),xzoom,yzoom,width,height);
		IPoint2 ipt(int(pt.x),int(pt.y));
		if (rect.Contains(ipt)) {
			hits.Append(1,&i,10);
			}
		}
	return hits.Count();
	}

void UnwrapMod::InvalidateTypeins()
	{
	typeInsValid = FALSE;	
	}

void UnwrapMod::SetupTypeins()
	{
	if (typeInsValid) return;
	typeInsValid = TRUE;

	Point3 uv(0,0,0);
	BOOL found = FALSE;
	BOOL u = TRUE, v = TRUE, w = TRUE;

	for (int i=0; i<tvert.Count(); i++) {
		if (!vsel[i]) continue;
		
		if (found) {
			if (uv.x!=tvert[i].x) {
				u = FALSE;				
				}
			if (uv.y!=tvert[i].y) {
				v = FALSE;				
				}
			if (uv.z!=tvert[i].z) {
				w = FALSE;				
				}			
		} else {
			uv = tvert[i];
			found = TRUE;
			}
		}

	if (!found) {
		iU->Disable();
		iV->Disable();
		iW->Disable();
	} else {
		iU->Enable();
		iV->Enable();
		iW->Enable();
		if (u) {
			iU->SetIndeterminate(FALSE);
			iU->SetValue(uv.x,FALSE);
		} else {
			iU->SetIndeterminate(TRUE);
			}

		if (v) {
			iV->SetIndeterminate(FALSE);
			iV->SetValue(uv.y,FALSE);
		} else {
			iV->SetIndeterminate(TRUE);
			}

		if (w) {
			iW->SetIndeterminate(FALSE);
			iW->SetValue(uv.z,FALSE);
		} else {
			iW->SetIndeterminate(TRUE);
			}
		}
	}

void UnwrapMod::Select(Tab<int> &hits,BOOL toggle,BOOL subtract,BOOL all)
	{
	HoldSelection();
	for (int i=0; i<hits.Count(); i++) {
		if (toggle) vsel.Set(hits[i],!vsel[hits[i]]);
		else if (subtract) vsel.Set(hits[i],FALSE);
		else vsel.Set(hits[i],TRUE);
		if (!all) break;
		}	
	}

void UnwrapMod::ClearSelect()
	{
	HoldSelection();
	vsel.ClearAll();	
	}

void UnwrapMod::HoldPoints()
	{
	if (theHold.Holding() && !TestAFlag(A_HELD)) {
		SetAFlag(A_HELD);
		theHold.Put(new TVertRestore(this));
		}
	}

void UnwrapMod::HoldSelection()
	{
	if (theHold.Holding() && !TestAFlag(A_HELD)) {	
		SetAFlag(A_HELD);
		theHold.Put(new TSelRestore(this));
		}
	}

void UnwrapMod::TypeInChanged(int which)
	{
	TimeValue t = ip->GetTime();
	HoldPoints();
	Point3 uvw;
	uvw[0] = iU->GetFVal();
	uvw[1] = iV->GetFVal();
	uvw[2] = iW->GetFVal();

	for (int i=0; i<tvert.Count(); i++) {
		if (vsel[i]) {
			if (cont[i]) cont[i]->GetValue(t,&tvert[i],FOREVER);
			tvert[i][which] = uvw[which];
			if (cont[i]) cont[i]->SetValue(t,&tvert[i]);
			}
		}

	NotifyDependents(FOREVER,TEXMAP_CHANNEL,REFMSG_CHANGE);
	InvalidateView();
	ip->RedrawViews(ip->GetTime());
	}

void UnwrapMod::MovePoints(Point2 pt)
	{
	int i1, i2;
	GetUVWIndices(i1,i2);
	HoldPoints();	
	TimeValue t = ip->GetTime();

	for (int i=0; i<tvert.Count(); i++) {
		if (vsel[i]) {
			if (cont[i]) cont[i]->GetValue(t,&tvert[i],FOREVER);
			tvert[i][i1] += pt.x;
			tvert[i][i2] += pt.y;
			if (cont[i]) cont[i]->SetValue(t,&tvert[i]);
			}
		}
	NotifyDependents(FOREVER,TEXMAP_CHANNEL,REFMSG_CHANGE);
	InvalidateView();
	}

void UnwrapMod::RotatePoints(float ang)
	{
	HoldPoints();	
	TimeValue t = ip->GetTime();

	Point3 cent(0,0,0);
	int ct = 0;
	for (int i=0; i<tvert.Count(); i++) {
		if (vsel[i]) {
			cent += tvert[i];
			ct++;
			}
		}
	if (!ct) return;
	cent /= float(ct);

	Matrix3 mat(1);	
	mat.Translate(-cent);
	switch (uvw) {
		case 0: mat.RotateZ(ang); break;
		case 1: mat.RotateX(ang); break;
		case 2: mat.RotateY(ang); break;
		}
	mat.Translate(cent);

	for (i=0; i<tvert.Count(); i++) {
		if (vsel[i]) {
			if (cont[i]) cont[i]->GetValue(t,&tvert[i],FOREVER);
			tvert[i] = mat * tvert[i];
			if (cont[i]) cont[i]->SetValue(t,&tvert[i]);
			}
		}

	NotifyDependents(FOREVER,TEXMAP_CHANNEL,REFMSG_CHANGE);
	InvalidateView();
	}

void UnwrapMod::ScalePoints(float scale)
	{
	HoldPoints();	
	TimeValue t = ip->GetTime();

	Point3 cent(0,0,0);
	int ct = 0;
	for (int i=0; i<tvert.Count(); i++) {
		if (vsel[i]) {
			cent += tvert[i];
			ct++;
			}
		}
	if (!ct) return;
	cent /= float(ct);

	Matrix3 mat(1);	
	mat.Translate(-cent);
	Point3 sc(1,1,1);
	int i1, i2;
	GetUVWIndices(i1,i2);
	sc[i1] = scale;
	sc[i2] = scale;
	mat.Scale(sc,TRUE);
	mat.Translate(cent);

	for (i=0; i<tvert.Count(); i++) {
		if (vsel[i]) {
			if (cont[i]) cont[i]->GetValue(t,&tvert[i],FOREVER);
			tvert[i] = mat * tvert[i];
			if (cont[i]) cont[i]->SetValue(t,&tvert[i]);
			}
		}

	NotifyDependents(FOREVER,TEXMAP_CHANNEL,REFMSG_CHANGE);
	InvalidateView();
	}

void UnwrapMod::PickMap()
	{	
	BOOL newMat=FALSE, cancel=FALSE;
	MtlBase *mtl = ip->DoMaterialBrowseDlg(
		hWnd,
		BROWSE_MAPSONLY|BROWSE_INCNONE|BROWSE_INSTANCEONLY,
		newMat,cancel);
	if (cancel) {
		if (newMat) mtl->DeleteThis();
		return;
		}
	ReplaceReference(0,mtl);
	SetupImage();
	}

void UnwrapMod::SetupImage()
	{
	delete image; image = NULL;
	if (map) {		
		iw = rendW;
		ih = rendH;
		if (iw==0 || ih==0) return;
		map->Update(ip->GetTime(), FOREVER);
		map->LoadMapFiles(ip->GetTime());
		SetCursor(LoadCursor(NULL,IDC_WAIT));
		image = RenderTexMap(map,iw,ih);
		SetCursor(LoadCursor(NULL,IDC_ARROW));
		InvalidateView();
		}
	if (image) iShowMap->Enable();
	else iShowMap->Disable();
	}

void UnwrapMod::PlugControllers()
	{
	theHold.Begin();
	SuspendAnimate();
	AnimateOff();
	for (int i=0; i<tvert.Count(); i++) {
		if (vsel[i] && !cont[i]) {
			ReplaceReference(i+1,NewDefaultPoint3Controller());			
			cont[i]->SetValue(0,&tvert[i]);
			}
		}
	ResumeAnimate();
	theHold.Accept(_T("Assign Controllers"));
	}

Point3 UnwrapMod::GetPoint(TimeValue t,int i)
	{
	if (cont[i]) cont[i]->GetValue(t,&tvert[i],FOREVER);
	return tvert[i];
	}


void UnwrapMod::ZoomExtents()
	{
	Rect brect;
	Point2 pt;
	float xzoom, yzoom;
	int width,height;
	TimeValue t = ip->GetTime();
	ComputeZooms(hView,xzoom,yzoom,width,height);	
	brect.SetEmpty();
	for (int i=0; i<tvert.Count(); i++) {		
		pt = UVWToScreen(GetPoint(t,i),xzoom,yzoom,width,height);
		IPoint2 ipt(int(pt.x),int(pt.y));
		brect += ipt;		
		}
	Rect srect;
	GetClientRect(hView,&srect);		
	float rat1, rat2;
	rat1 = float(srect.w()-1)/float(fabs(double(brect.w()-1)));
	rat2 = float(srect.h()-1)/float(fabs(double(brect.h()-1)));
	float rat = (rat1<rat2?rat1:rat2) * 0.9f;
	zoom *= rat;
	IPoint2 delta = srect.GetCenter() - brect.GetCenter();
	xscroll += delta.x;
	yscroll += delta.y;
	xscroll *= rat;
	yscroll *= rat;	
	InvalidateView();
	}


//--- Mouse procs for modes -----------------------------------------------

int SelectMode::proc(HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
			if (point==0) {
				// First click
				
				region   = FALSE;
				toggle   = flags&MOUSE_CTRL;
				subtract = flags&MOUSE_ALT;
				
				// Hit test
				Tab<int> hits;
				Rect rect;
				rect.left = m.x-2;
				rect.right = m.x+2;
				rect.top = m.y-2;
				rect.bottom = m.y+2;
				
				// First hit test sel only
				if (!toggle && !subtract && mod->HitTest(rect,hits,TRUE)) {
					return subproc(hWnd,msg,point,flags,m);
				} else
				// Next hit test everything
				if (mod->HitTest(rect,hits,subtract)) {
					theHold.Begin();
					if (!toggle && !subtract) mod->ClearSelect();
					mod->Select(hits,toggle,subtract,FALSE);
					mod->InvalidateView();
					theHold.Accept(_T("Select UVW"));
					if (toggle || subtract) return FALSE;
					return subproc(hWnd,msg,point,flags,m);
				} else {					
					region = TRUE;
					lm = om = m;
					XORDottedRect(hWnd,om,m);
					}				
			} else {
				// Second click
				if (region) {
					Rect rect;
					rect.left   = om.x;
					rect.top    = om.y;
					rect.right  = m.x;
					rect.bottom = m.y;
					rect.Rectify();					
					Tab<int> hits;
					theHold.Begin();
					if (!toggle && !subtract) mod->ClearSelect();
					if (mod->HitTest(rect,hits,subtract)) {						
						mod->Select(hits,FALSE,subtract,TRUE);											
						}
					theHold.Accept(_T("Select UVW"));
					mod->InvalidateView();
				} else {
					return subproc(hWnd,msg,point,flags,m);
					}
				}
			break;			

		case MOUSE_MOVE:
			SetCursor(LoadCursor(NULL, IDC_ARROW));
			if (region) {
				XORDottedRect(hWnd,om,lm);
				XORDottedRect(hWnd,om,m);
				lm = m;
			} else {
				SetCursor(GetXFormCur());
				return subproc(hWnd,msg,point,flags,m);
				}
			break;

		case MOUSE_FREEMOVE: {
			Tab<int> hits;
			Rect rect;
			rect.left = m.x-2;
			rect.right = m.x+2;
			rect.top = m.y-2;
			rect.bottom = m.y+2;
			if (mod->HitTest(rect,hits,FALSE)) {
				if (mod->vsel[hits[0]]) {
					SetCursor(GetXFormCur());
				} else {
					SetCursor(selCur);
					}
			} else {
				SetCursor(LoadCursor(NULL, IDC_ARROW));
				}
			return subproc(hWnd,msg,point,flags,m);
			}

		case MOUSE_ABORT:
			if (region) {
				InvalidateRect(hWnd,NULL,FALSE);
			} else {
				return subproc(hWnd,msg,point,flags,m);
				}
			break;
		}
	return 1;
	}

int MoveMode::subproc(HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
			if (point==0) {
				theHold.SuperBegin();
				mod->PlugControllers();
				theHold.Begin();
				om = m;
			} else {
				theHold.Accept(_T("Move UVWs"));
				theHold.SuperAccept(_T("Move UVWs"));
				mod->ip->RedrawViews(mod->ip->GetTime());
				}
			break;

		case MOUSE_MOVE: {
			theHold.Restore();
			float xzoom, yzoom;
			int width, height;
			IPoint2 delta = m-om;
			if (flags&MOUSE_SHIFT && mod->move==0) {
				if (abs(delta.x) > abs(delta.y)) delta.y = 0;
				else delta.x = 0;
			} else if (mod->move==1) {
				delta.y = 0;
			} else if (mod->move==2) {
				delta.x = 0;
				}
			mod->ComputeZooms(hWnd,xzoom,yzoom,width,height);
			Point2 mv;
			mv.x = delta.x/xzoom;
			mv.y = -delta.y/yzoom;
			mod->MovePoints(mv);
			if (mod->update) mod->ip->RedrawViews(mod->ip->GetTime());
			UpdateWindow(hWnd);
			break;		
			}

		case MOUSE_ABORT:
			theHold.Cancel();
			theHold.SuperCancel();
			mod->ip->RedrawViews(mod->ip->GetTime());
			break;
		}
	return 1;
	}

#define ZOOM_FACT	0.01f
#define ROT_FACT	DegToRad(0.5f)

int RotateMode::subproc(HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
			if (point==0) {
				theHold.SuperBegin();
				mod->PlugControllers();
				theHold.Begin();
				om = m;
			} else {
				theHold.Accept(_T("Rotate UVWs"));
				theHold.SuperAccept(_T("Rotate UVWs"));
				mod->ip->RedrawViews(mod->ip->GetTime());
				}
			break;

		case MOUSE_MOVE:
			theHold.Restore();
			mod->RotatePoints(float(m.y-om.y)*ROT_FACT);
			if (mod->update) mod->ip->RedrawViews(mod->ip->GetTime());
			UpdateWindow(hWnd);
			break;		

		case MOUSE_ABORT:
			theHold.Cancel();
			theHold.SuperCancel();
			mod->ip->RedrawViews(mod->ip->GetTime());
			break;
		}
	return 1;
	}

int ScaleMode::subproc(HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
			if (point==0) {
				theHold.SuperBegin();
				mod->PlugControllers();
				theHold.Begin();
				om = m;
			} else {
				theHold.Accept(_T("Scale UVWs"));
				theHold.SuperAccept(_T("Scale UVWs"));
				mod->ip->RedrawViews(mod->ip->GetTime());
				}
			break;

		case MOUSE_MOVE: {
			theHold.Restore();
			IPoint2 delta = om-m;
			float z;
			if (delta.y<0)
				 z = (1.0f/(1.0f-ZOOM_FACT*delta.y));
			else z = (1.0f+ZOOM_FACT*delta.y);
			mod->ScalePoints(z);
			if (mod->update) mod->ip->RedrawViews(mod->ip->GetTime());
			UpdateWindow(hWnd);
			break;
			}

		case MOUSE_ABORT:
			theHold.Cancel();
			theHold.SuperCancel();
			mod->ip->RedrawViews(mod->ip->GetTime());
			break;
		}
	return 1;
	}

int PanMode::proc(HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
			if (point==0) {
				om = m;
				oxscroll = mod->xscroll;
				oyscroll = mod->yscroll;
				}
			break;

		case MOUSE_MOVE: {
			IPoint2 delta = m-om;
			mod->xscroll = oxscroll + float(delta.x);
			mod->yscroll = oyscroll + float(delta.y);
			mod->InvalidateView();
			SetCursor(GetPanCursor());
			break;
			}

		case MOUSE_ABORT:
			mod->xscroll = oxscroll;
			mod->yscroll = oyscroll;
			mod->InvalidateView();
			break;

		case MOUSE_FREEMOVE:
			SetCursor(GetPanCursor());
			break;		
		}
	return 1;
	}

int ZoomMode::proc(HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
			if (point==0) {
				om = m;
				ozoom = mod->zoom;
				oxscroll = mod->xscroll;
				oyscroll = mod->yscroll;
				}
			break;

		case MOUSE_MOVE: {
			IPoint2 delta = om-m;
			float z;
			if (delta.y<0)
				 z = (1.0f/(1.0f-ZOOM_FACT*delta.y));
			else z = (1.0f+ZOOM_FACT*delta.y);
			mod->zoom = ozoom * z;
			mod->xscroll = oxscroll*z;
			mod->yscroll = oyscroll*z;
			mod->InvalidateView();
			SetCursor(LoadCursor(NULL, IDC_ARROW));
			break;
			}

		case MOUSE_ABORT:
			mod->zoom = ozoom;
			mod->xscroll = oxscroll;
			mod->yscroll = oyscroll;
			mod->InvalidateView();
			break;

		case MOUSE_FREEMOVE:
			SetCursor(LoadCursor(NULL, IDC_ARROW));
			break;		
		}
	return 1;
	}

int ZoomRegMode::proc(HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
			if (point==0) {
				lm = om = m;
				XORDottedRect(hWnd,om,lm);
			} else {
				if (om!=m) {
					Rect rect;
					GetClientRect(hWnd,&rect);
					IPoint2 mcent = (om+m)/2;
					IPoint2 scent = rect.GetCenter();
					IPoint2 delta = m-om;
					float rat1, rat2;
					rat1 = float(rect.w()-1)/float(fabs((double)delta.x));
					rat2 = float(rect.h()-1)/float(fabs((double)delta.y));
					float rat = rat1<rat2?rat1:rat2;
					mod->zoom *= rat;
					delta = scent - mcent;
					mod->xscroll += delta.x;
					mod->yscroll += delta.y;
					mod->xscroll *= rat;
					mod->yscroll *= rat;
					}
				mod->InvalidateView();
				}
			break;

		case MOUSE_MOVE:
			XORDottedRect(hWnd,om,lm);
			XORDottedRect(hWnd,om,m);
			lm = m;
			SetCursor(LoadCursor(NULL, IDC_ARROW));
			break;

		case MOUSE_ABORT:
			InvalidateRect(hWnd,NULL,FALSE);
			break;
		
		case MOUSE_FREEMOVE:
			SetCursor(LoadCursor(NULL, IDC_ARROW));
			break;		
		}
	return 1;
	}

int RightMouseMode::proc(HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
		case MOUSE_PROPCLICK:
			mod->SetMode(ID_TOOL_MOVE);
			break;
		}
	return 1;
	}

static BOOL CALLBACK PropDlgProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	static IColorSwatch *iSelColor, *iLineColor;
	static ISpinnerControl *spinW, *spinH;

	UnwrapMod *mod = (UnwrapMod*)GetWindowLong(hWnd,GWL_USERDATA);

	switch (msg) {
		case WM_INITDIALOG: {
			mod = (UnwrapMod*)lParam;
			SetWindowLong(hWnd,GWL_USERDATA,lParam);
			
			iSelColor = GetIColorSwatch(GetDlgItem(hWnd,IDC_UNWRAP_SELCOLOR), 
				mod->selColor, _T("Line Color"));			
			iLineColor = GetIColorSwatch(GetDlgItem(hWnd,IDC_UNWRAP_LINECOLOR), 
				mod->lineColor, _T("Selection Color"));						
			
			CheckDlgButton(hWnd,IDC_UNWRAP_CONSTANTUPDATE,mod->update);
			
			spinW = SetupIntSpinner(
				hWnd,IDC_UNWRAP_WIDTHSPIN,IDC_UNWRAP_WIDTH,
				10,1000,mod->rendW);			
			spinH = SetupIntSpinner(
				hWnd,IDC_UNWRAP_HEIGHTSPIN,IDC_UNWRAP_HEIGHT,
				10,1000,mod->rendH);			
			break;
			}

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDOK:
					mod->lineColor = iLineColor->GetColor();
					mod->selColor  = iSelColor->GetColor();					
					mod->update = IsDlgButtonChecked(hWnd,IDC_UNWRAP_CONSTANTUPDATE);
					mod->rendW = spinW->GetIVal();
					mod->rendH = spinH->GetIVal();
					if (mod->rendW!=mod->iw ||
						mod->rendH!=mod->ih) {
						mod->SetupImage();
						}
					mod->InvalidateView();
					//fall through
					
				case IDCANCEL:
					ReleaseIColorSwatch(iSelColor);
					ReleaseIColorSwatch(iLineColor);
					ReleaseISpinner(spinW);
					ReleaseISpinner(spinH);
					EndDialog(hWnd,0);
					break;

				case IDC_UNWRAP_DEFAULTS:
					iLineColor->SetColor(RGB(255,255,255));
					iSelColor->SetColor(RGB(255,0,0));
					spinW->SetValue(256,FALSE);
					spinH->SetValue(256,FALSE);					
					CheckDlgButton(hWnd,IDC_UNWRAP_CONSTANTUPDATE,FALSE);
					break;
				}
			break;

		default:
			return FALSE;
		} 
	return TRUE;
	}

void UnwrapMod::PropDialog() 
	{
	DialogBoxParam(
		hInstance,
		MAKEINTRESOURCE(IDD_UNWRAP_PROP),
		hWnd,
		PropDlgProc,
		(LONG)this);
	}