/**********************************************************************
 *<
	FILE: meshsel.cpp

	DESCRIPTION:  A selection modifier for meshes

	CREATED BY: Rolf Berteig

	HISTORY: 10/23/96

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

#include "mods.h"
#include "iparamm.h"
#include "meshadj.h"
#include "namesel.h"
#include "nsclip.h"
#include "istdplug.h"

#define WM_UPDATE_CACHE		(WM_USER+0x287)

class MeshSelMod : public IMeshSelect {	
	public:				
		DWORD selLevel;
		Tab<TSTR*> namedSel[3];		
		Tab<DWORD> ids[3];

		static IObjParam *ip;
		static IParamMap *pmapParam;
		static MeshSelMod *editMod;
		static int selType;
		static BOOL selByVert;
		static BOOL ignoreBackfaces;
		static BOOL ignoreVisEdge;
		static HWND hParams;
		static SelectModBoxCMode *selectMode;
		static BOOL updateCachePosted;

		MeshSelMod();

		// From Animatable
		void DeleteThis() { delete this; }
		void GetClassName(TSTR& s) {s = GetString(IDS_RB_MESHSELMOD);}  
		virtual Class_ID ClassID() { return Class_ID(MESHSELECT_CLASS_ID,0);}		
		RefTargetHandle Clone(RemapDir& remap = NoRemap());
		TCHAR *GetObjectName() {return GetString(IDS_RB_MESHSELMOD);}

		// From modifier
		ChannelMask ChannelsUsed()  {return PART_GEOM|PART_TOPO|PART_SELECT|PART_SUBSEL_TYPE;}
		ChannelMask ChannelsChanged() {return PART_SELECT|PART_SUBSEL_TYPE|PART_TOPO;} // RB 5/27/97: Had to include topo channel because in edge select mode this modifier turns on hidden edges -- which may cause the edge list to be rebuilt, which is effectively a change in topology since the edge list is now part of the topo channel.
		Class_ID InputType() {return triObjectClassID;}
		void ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node);
		Interval LocalValidity(TimeValue t) {return FOREVER;}
		BOOL DependOnTopology(ModContext &mc) {return TRUE;}

		// From BaseObject
		CreateMouseCallBack* GetCreateMouseCallBack() {return NULL;} 
		void BeginEditParams(IObjParam  *ip, ULONG flags,Animatable *prev);
		void EndEditParams(IObjParam *ip,ULONG flags,Animatable *next);		
		int HitTest(TimeValue t, INode* inode, int type, int crossing, int flags, IPoint2 *p, ViewExp *vpt, ModContext* mc);
		void ActivateSubobjSel(int level, XFormModes& modes);
		void SelectSubComponent(HitRecord *hitRec, BOOL selected, BOOL all, BOOL invert=FALSE);
		void ClearSelection(int selLevel);
		void SelectAll(int selLevel);
		void InvertSelection(int selLevel);
		void NotifyInputChanged(Interval changeInt, PartID partID, RefMessage message, ModContext *mc);

		BOOL SupportsNamedSubSels() {return TRUE;}
		void ActivateSubSelSet(TSTR &setName);
		void NewSetFromCurSel(TSTR &setName);
		void RemoveSubSelSet(TSTR &setName);
		void SetupNamedSelDropDown();
		int NumNamedSelSets();
		TSTR GetNamedSelSetName(int i);
		void SetNamedSelSetName(int i,TSTR &newName);
		void NewSetByOperator(TSTR &newName,Tab<int> &sets,int op);

		// From IMeshSelect
		DWORD GetSelLevel() {return selLevel;}
		void SetSelLevel(DWORD level);
		void LocalDataChanged();

		// IO
		IOResult Save(ISave *isave);
		IOResult Load(ILoad *iload);
		IOResult LoadNamedSelChunk(ILoad *iload,int level);
		IOResult SaveLocalData(ISave *isave, LocalModData *ld);
		IOResult LoadLocalData(ILoad *iload, LocalModData **pld);

		int NumRefs() {return 0;}
		RefTargetHandle GetReference(int i) {return NULL;}
		void SetReference(int i, RefTargetHandle rtarg) {}

		int NumSubs() {return 0;}
		Animatable* SubAnim(int i) {return NULL;}
		TSTR SubAnimName(int i) {return _T("");}

		RefResult NotifyRefChanged( Interval changeInt,RefTargetHandle hTarget, 
		   PartID& partID, RefMessage message) {return REF_SUCCEED;}

		void RemoveRollupPages();
		void SetRollupPages();
		void SetSelType(int type);
		float GetPolyFaceThesh();
		HitRecord *VertHitsToFaceHits(HitRecord *hitRec,BOOL all);
		void SelectFrom(int from);
		void SetEdgeVisCheckState();
		void SelectOpenEdges();
		void SelectByMatID(int id);
		void SetNumSelLabel();		
		void UpdateCache(TimeValue t);

		// Local methods for handling named selection sets
		int FindSet(TSTR &setName,int level);		
		DWORD AddSet(TSTR &setName,int level);
		void RemoveSet(TSTR &setName,int level);
		void ClearSetNames();
		void NSCopy();
		void NSPaste();
		BOOL GetUniqueSetName(TSTR &name);
		int SelectNamedSet();		

	};


class MeshSelData : public IMeshSelectData {
	public:
		// Selection sets
		BitArray vertSel;
		BitArray faceSel;
		BitArray edgeSel;

		// Lists of named selection sets
		NamedSelSetList vselSet;
		NamedSelSetList fselSet;
		NamedSelSetList eselSet;

		BOOL held;
		Mesh *mesh;

		MeshSelData(Mesh &mesh);
		MeshSelData() {held=0;mesh=NULL;}
		~MeshSelData() {FreeCache();}
		LocalModData *Clone();
		Mesh *GetMesh() {return mesh;}
		void SetCache(Mesh &mesh);
		void FreeCache();
		void SynchBitArrays();

		void SelVertByFace();
		void SelVertByEdge();
		void SelFaceByVert();
		void SelFaceByEdge();
		void SelEdgeByVert();
		void SelEdgeByFace();

		BitArray &GetVertSel() {return vertSel;}
		BitArray &GetFaceSel() {return faceSel;}
		BitArray &GetEdgeSel() {return edgeSel;}
		
		void SetVertSel(BitArray &set);
		void SetFaceSel(BitArray &set);
		void SetEdgeSel(BitArray &set);
	};


class MeshSelRestore : public RestoreObj {
	public:
		BitArray usel, rsel;
		BitArray *sel;
		MeshSelMod *mod;
		MeshSelData *d;
		int level;

		MeshSelRestore(MeshSelMod *m, MeshSelData *d);
		void Restore(int isUndo);
		void Redo();
		int Size() { return 1; }
		void EndHold() {d->held=FALSE;}
		TSTR Description() { return TSTR(_T("SelectRestore")); }
	};

class AppendSetRestore : public RestoreObj {
	public:
		BitArray set;		
		DWORD id;
		NamedSelSetList *setList;		

		AppendSetRestore(NamedSelSetList *sl) {setList = sl;}  
		void Restore(int isUndo) {
			set  = *setList->sets[setList->Count()-1];			
			id   = setList->ids[setList->Count()-1];			
			setList->RemoveSet(id);			
			}
		void Redo() {
			setList->AppendSet(set,id);
			}
				
		TSTR Description() {return TSTR(_T("Append Set"));}
	};
class AppendSetNameRestore : public RestoreObj {
	public:		
		TSTR name;
		DWORD id;
		MeshSelMod *et;
		Tab<TSTR*> *sets;
		Tab<DWORD> *ids;

		AppendSetNameRestore(MeshSelMod *e,Tab<TSTR*> *s,Tab<DWORD> *i) 
			{et = e; sets = s; ids = i;}
		void Restore(int isUndo) {			
			name = *(*sets)[sets->Count()-1];
			id   = (*ids)[sets->Count()-1];
			delete (*sets)[sets->Count()-1];
			sets->Delete(sets->Count()-1,1);			
			if (et->ip) et->ip->NamedSelSetListChanged();
			}
		void Redo() {
			TSTR *nm = new TSTR(name);
			sets->Append(1,&nm);
			ids->Append(1,&id);
			if (et->ip) et->ip->NamedSelSetListChanged();
			}
				
		TSTR Description() {return TSTR(_T("Append Set Name"));}
	};

class DeleteSetRestore : public RestoreObj {
	public:
		BitArray set;
		DWORD id;
		//int index;
		NamedSelSetList *setList;		

		//DeleteSetRestore(int i,NamedSelSetList *sl) {
		DeleteSetRestore(NamedSelSetList *sl,DWORD i) {
			setList = sl; 
			//index = i;
			id = i;
			BitArray *ptr = setList->GetSet(id);
			if (ptr) set = *ptr;
			//set  = *setList->sets[index];			
			}   		
		void Restore(int isUndo) {
			//BitArray *n = new BitArray(set);			
			//setList->sets.Insert(index,1,&n);			
			setList->AppendSet(set,id);
			}
		void Redo() {
			//setList->DeleteSet(index);			
			setList->RemoveSet(id);
			}
				
		TSTR Description() {return TSTR(_T("Delete Set"));}
	};
class DeleteSetNameRestore : public RestoreObj {
	public:		
		TSTR name;
		//int index;		
		DWORD id;
		MeshSelMod *et;
		Tab<TSTR*> *sets;
		Tab<DWORD> *ids;

		DeleteSetNameRestore(Tab<TSTR*> *s,MeshSelMod *e,Tab<DWORD> *i, DWORD id) {
			sets = s; et = e; //index = i;			
			this->id = id;
			ids = i;
			int index = -1;
			for (int j=0; j<sets->Count(); j++) {
				if ((*ids)[j]==id) {
					index = j;
					break;
					}
				}
			if (index>=0) {
				name = *(*sets)[index];
				}
			}   		
		void Restore(int isUndo) {			
			TSTR *nm = new TSTR(name);			
			//sets->Insert(index,1,&nm);
			sets->Append(1,&nm);
			ids->Append(1,&id);
			if (et->ip) et->ip->NamedSelSetListChanged();
			}
		void Redo() {
			int index = -1;
			for (int j=0; j<sets->Count(); j++) {
				if ((*ids)[j]==id) {
					index = j;
					break;
					}
				}
			if (index>=0) {
				sets->Delete(index,1);
				ids->Delete(index,1);
				}
			//sets->Delete(index,1);
			if (et->ip) et->ip->NamedSelSetListChanged();
			}
				
		TSTR Description() {return TSTR(_T("Delete Set Name"));}
	};

class SetNameRestore : public RestoreObj {
	public:
		TSTR undo, redo;
		//int index;
		DWORD id;
		Tab<TSTR*> *sets;
		Tab<DWORD> *ids;
		MeshSelMod *et;
		SetNameRestore(Tab<TSTR*> *s,MeshSelMod *e,Tab<DWORD> *i,DWORD id) {
			//index = i; 
			this->id = id;
			ids = i;
			sets = s; et = e;
			int index = -1;
			for (int j=0; j<sets->Count(); j++) {
				if ((*ids)[j]==id) {
					index = j;
					break;
					}
				}
			if (index>=0) {
				undo = *(*sets)[index];
				}			
			}

		void Restore(int isUndo) {
			int index = -1;
			for (int j=0; j<sets->Count(); j++) {
				if ((*ids)[j]==id) {
					index = j;
					break;
					}
				}
			if (index>=0) {
				redo = *(*sets)[index];
				*(*sets)[index] = undo;
				}			
			if (et->ip) et->ip->NamedSelSetListChanged();
			}
		void Redo() {
			int index = -1;
			for (int j=0; j<sets->Count(); j++) {
				if ((*ids)[j]==id) {
					index = j;
					break;
					}
				}
			if (index>=0) {
				*(*sets)[index] = redo;
				}
			if (et->ip) et->ip->NamedSelSetListChanged();
			}
				
		TSTR Description() {return TSTR(_T("Set Name"));}
	};


//--- ClassDescriptor and class vars ---------------------------------

#define SELTYPE_SINGLE	1
#define SELTYPE_POLY	2
#define SELTYPE_ELEMENT	3

#define SEL_OBJECT	0
#define SEL_VERTEX	1
#define SEL_FACE	2
#define SEL_EDGE	3


IObjParam             *MeshSelMod::ip              = NULL;
IParamMap             *MeshSelMod::pmapParam       = NULL;
MeshSelMod            *MeshSelMod::editMod         = NULL;
HWND                   MeshSelMod::hParams         = NULL;
int                    MeshSelMod::selType         = SELTYPE_POLY;
BOOL                   MeshSelMod::selByVert       = FALSE;
BOOL                   MeshSelMod::ignoreBackfaces = FALSE;
BOOL                   MeshSelMod::ignoreVisEdge   = FALSE;
SelectModBoxCMode     *MeshSelMod::selectMode      = NULL;
BOOL                   MeshSelMod::updateCachePosted = FALSE;

static float lastFaceThresh = 1.0f;
static int lastMatID = 1;

class MeshSelClassDesc:public ClassDesc {
	public:
	int 			IsPublic() { return 1; }
	void *			Create(BOOL loading = FALSE) { return new MeshSelMod; }
	const TCHAR *	ClassName() { return GetString(IDS_RB_MESHSELMOD); }
	SClass_ID		SuperClassID() { return OSM_CLASS_ID; }
	Class_ID		ClassID() { return Class_ID(MESHSELECT_CLASS_ID,0); }
	const TCHAR* 	Category() { return GetString(IDS_RB_DEFDEFORMATIONS);}
	};

static MeshSelClassDesc meshSelDesc;
ClassDesc* GetMeshSelModDesc() {return &meshSelDesc;}


static HIMAGELIST hFaceImages = NULL;
static void LoadImages()
	{
	if (!hFaceImages) {
		HBITMAP hBitmap, hMask;
		hFaceImages = ImageList_Create(16, 15, TRUE, 3, 0);
		hBitmap     = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_FACESELTYPES));
		hMask       = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MASK_FACESELTYPES));
		ImageList_Add(hFaceImages,hBitmap,hMask);
		DeleteObject(hBitmap);
		DeleteObject(hMask);
		}
	}	

static BOOL CALLBACK EditTriObjectProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
static BOOL CALLBACK EditTriVertProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
static BOOL CALLBACK EditTriFaceProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
static BOOL CALLBACK EditTriEdgeProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

static DLGPROC windProcs[] = {EditTriObjectProc,EditTriVertProc,EditTriFaceProc,EditTriEdgeProc};
static int dlgIDs[] = {IDD_MESHSEL_OBJECT,IDD_MESHSEL_VERTEX,IDD_MESHSEL_FACE,IDD_MESHSEL_EDGE};
static int dlgTitles[] = {IDS_RB_SELOBJECT,IDS_RB_SELVERTEX,IDS_RB_SELFACE,IDS_RB_SELEDGE};

// Table to convert selLevel values to mesh selLevel flags.
const int meshLevel[] = {MESH_OBJECT,MESH_VERTEX,MESH_FACE,MESH_EDGE,0,0};

// Get display flags based on selLevel.
const DWORD levelDispFlags[] = {0,DISP_VERTTICKS|DISP_SELVERTS,DISP_SELFACES,DISP_SELEDGES,0,0};

// For hit testing...
const int hitLevel[] = {0,SUBHIT_VERTS,SUBHIT_FACES,SUBHIT_EDGES,0,0};


//--- MeshSel mod methods -------------------------------

MeshSelMod::MeshSelMod()
	{	
	selLevel = SEL_FACE;
	}

RefTargetHandle MeshSelMod::Clone(RemapDir& remap)
	{
	MeshSelMod *mod = new MeshSelMod();
	mod->selLevel = selLevel;
	return mod;
	}

void MeshSelMod::ModifyObject(
		TimeValue t, ModContext &mc, ObjectState *os, INode *node)
	{
	if (os->obj->IsSubClassOf(triObjectClassID)) {
		TriObject *tobj = (TriObject*)os->obj;
		MeshSelData *d  = (MeshSelData*)mc.localData;
		if (!d) {
			mc.localData = d = new MeshSelData(tobj->mesh);
			}
		if (editMod==this) {
			if (!d->GetMesh()) {
				d->SetCache(tobj->mesh);
				}
			}

		BitArray vertSel = d->vertSel;
		BitArray faceSel = d->faceSel;
		BitArray edgeSel = d->edgeSel;
		vertSel.SetSize(tobj->mesh.getNumVerts(),TRUE);
		faceSel.SetSize(tobj->mesh.getNumFaces(),TRUE);
		edgeSel.SetSize(tobj->mesh.getNumFaces()*3,TRUE);
		tobj->mesh.vertSel = vertSel;
		tobj->mesh.faceSel = faceSel;
		tobj->mesh.edgeSel = edgeSel;
		
		if (d->GetMesh()) {
			// Keep the cache up to date if it exists.
			d->GetMesh()->vertSel = vertSel;
			d->GetMesh()->faceSel = faceSel;
			d->GetMesh()->edgeSel = edgeSel;
			}

		/*
		d->vertSel.SetSize(tobj->mesh.getNumVerts(),TRUE);
		d->faceSel.SetSize(tobj->mesh.getNumFaces(),TRUE);
		d->edgeSel.SetSize(tobj->mesh.getNumFaces()*3,TRUE);
		tobj->mesh.vertSel = d->vertSel;
		tobj->mesh.faceSel = d->faceSel;
		tobj->mesh.edgeSel = d->edgeSel;
		
		if (d->GetMesh()) {
			// Keep the cache up to date if it exists.
			d->GetMesh()->vertSel = d->vertSel;
			d->GetMesh()->faceSel = d->faceSel;
			d->GetMesh()->edgeSel = d->edgeSel;
			}
		*/

		tobj->mesh.dispFlags = 0;

		switch (selLevel) {
			case SEL_VERTEX:
				tobj->mesh.SetDispFlag(DISP_VERTTICKS|DISP_SELVERTS);
				break;
			case SEL_FACE:
				if (selType == SELTYPE_POLY || selType == SELTYPE_ELEMENT) {
					tobj->mesh.SetDispFlag(DISP_SELPOLYS);
				} else {
					tobj->mesh.SetDispFlag(DISP_SELFACES);
					}				
				break;
			case SEL_EDGE:
				tobj->mesh.SetDispFlag(DISP_SELEDGES);
				tobj->mesh.displayAllEdges(TRUE);
				break;
			}
		tobj->mesh.selLevel = meshLevel[selLevel];
		}	
	}


void MeshSelMod::NotifyInputChanged(Interval changeInt, PartID partID, RefMessage message, ModContext *mc)
	{
	if (mc->localData && ip) {
		((MeshSelData*)mc->localData)->FreeCache();
		TimeValue t = ip->GetTime();

		if (hParams && editMod==this && !updateCachePosted) {
			PostMessage(hParams,WM_UPDATE_CACHE,(WPARAM)t,0);
			updateCachePosted = TRUE;
			}
		
		/*		
		NotifyDependents(Interval(t,t), 
			PART_GEOM|SELECT_CHANNEL|PART_SUBSEL_TYPE|
			PART_DISPLAY|PART_TOPO,
		    REFMSG_MOD_EVAL);
		*/
		}
	}

void MeshSelMod::UpdateCache(TimeValue t)
	{
	NotifyDependents(Interval(t,t), 
		PART_GEOM|SELECT_CHANNEL|PART_SUBSEL_TYPE|
		PART_DISPLAY|PART_TOPO,
		REFMSG_MOD_EVAL);
	updateCachePosted = FALSE;
	}

void MeshSelMod::RemoveRollupPages()
	{
	if (hParams) ip->DeleteRollupPage(hParams);
	hParams = NULL;	
	}

void MeshSelMod::SetRollupPages()
	{
	RemoveRollupPages();
	hParams = ip->AddRollupPage( 
		hInstance, 
		MAKEINTRESOURCE(dlgIDs[selLevel]),
		windProcs[selLevel],
		GetString(dlgTitles[selLevel]),
		(LPARAM)this);	
	SetNumSelLabel();
	}

void MeshSelMod::SetSelType(int type)
	{
	int old = selType;
	selType = type;
	
	ICustToolbar *iToolbar = GetICustToolbar(GetDlgItem(hParams,IDC_SELTYPE));
	ICustButton *iSingle   = iToolbar->GetICustButton(IDC_SELSINGLE);
	ICustButton *iPoly     = iToolbar->GetICustButton(IDC_SELPOLY);
	ICustButton *iElement  = iToolbar->GetICustButton(IDC_SELELEMENT);

	switch (type) {
		case SELTYPE_SINGLE:
			iSingle->SetCheck(TRUE);
			iPoly->SetCheck(FALSE);
			iElement->SetCheck(FALSE);
			break;
		case SELTYPE_POLY:
			iSingle->SetCheck(FALSE);
			iPoly->SetCheck(TRUE);
			iElement->SetCheck(FALSE);
			break;
		case SELTYPE_ELEMENT:
			iSingle->SetCheck(FALSE);
			iPoly->SetCheck(FALSE);
			iElement->SetCheck(TRUE);
			break;
		}
	
	ReleaseICustToolbar(iToolbar);
	ReleaseICustButton(iSingle);
	ReleaseICustButton(iPoly);
	ReleaseICustButton(iElement);

	if (old != selType) {
		//if (selType==SELTYPE_SINGLE) {
		//	mesh.InvalidateEdgeList();
		//	}
		//SetSubobjectLevel(selLevel);
		NotifyDependents(FOREVER, PART_DISPLAY, REFMSG_CHANGE);
		UpdateWindow(hParams);
		ip->RedrawViews(ip->GetTime());
		}
	}

void MeshSelMod::BeginEditParams(
		IObjParam  *ip, ULONG flags,Animatable *prev)
	{
	this->ip = ip;	
	editMod  = this;

	SetRollupPages();
	
	selectMode = new SelectModBoxCMode(this,ip);

	// Add our sub object type
	TSTR type1(GetString(IDS_RB_VERTEX));
	TSTR type2(GetString(IDS_RB_FACE));
	TSTR type3(GetString(IDS_RB_EDGE));
	const TCHAR *ptype[] = {type1, type2, type3};
	ip->RegisterSubObjectTypes(ptype, 3);

	// Restore the selection level.
	ip->SetSubObjectLevel(selLevel);

	// Disable show end result.
	ip->EnableShowEndResult(FALSE);
		
	NotifyDependents(FOREVER, PART_DISPLAY, REFMSG_CHANGE);
	}

void MeshSelMod::EndEditParams(
		IObjParam *ip,ULONG flags,Animatable *next)
	{
	ModContextList list;
	INodeTab nodes;	
	ip->GetModContexts(list,nodes);
	for (int i=0; i<list.Count(); i++) {
		MeshSelData *md = (MeshSelData*)list[i]->localData;
		if (md) md->FreeCache();
		}
	nodes.DisposeTemporary();

	RemoveRollupPages();

	ip->DeleteMode(selectMode);
	if (selectMode) delete selectMode;
	selectMode = NULL;

	// Enable show end result
	ip->EnableShowEndResult(TRUE);

	this->ip = NULL;
	editMod  = NULL;
	hParams  = NULL;
	}

int MeshSelMod::HitTest(
		TimeValue t, INode* inode, int type, int crossing, 
		int flags, IPoint2 *p, ViewExp *vpt, ModContext* mc)
	{
	Interval valid;
	int savedLimits, res = 0;
	GraphicsWindow *gw = vpt->getGW();
	HitRegion hr;
	
	// Setup GW
	MakeHitRegion(hr,type, crossing,4,p);
	gw->setHitRegion(&hr);
	Matrix3 mat = inode->GetObjectTM(t);
	gw->setTransform(mat);	
	gw->setRndLimits(((savedLimits = gw->getRndLimits()) | GW_PICK) & ~GW_ILLUM);
	if (ignoreBackfaces) 
		 gw->setRndLimits(gw->getRndLimits() |  GW_BACKCULL);
	else gw->setRndLimits(gw->getRndLimits() & ~GW_BACKCULL);
	gw->clearHitCode();
		
	SubObjHitList hitList;
	MeshSubHitRec *rec;	
	
	if (!((MeshSelData*)mc->localData)->GetMesh()) return 0;

	Mesh &mesh = *((MeshSelData*)mc->localData)->GetMesh();
	if (selLevel==SEL_FACE && selByVert) {
		res = mesh.SubObjectHitTest(gw, gw->getMaterial(), &hr,
			flags|hitLevel[SEL_VERTEX]|SUBHIT_USEFACESEL, hitList);
	} else {
		res = mesh.SubObjectHitTest(gw, gw->getMaterial(), &hr,
				flags|hitLevel[selLevel], hitList);
		}

	rec = hitList.First();
	while (rec) {
		vpt->LogHit(inode,mc,rec->dist,rec->index,NULL);
		rec = rec->Next();
		}

	gw->setRndLimits(savedLimits);	
	return res;	
	}

void MeshSelMod::ActivateSubobjSel(int level, XFormModes& modes)
	{
	// Set the meshes level
	//SetSubobjectLevel(level);
	selLevel = level;

	// Fill in modes with our sub-object modes
	if (level!=SEL_OBJECT) {
		modes = XFormModes(NULL,NULL,NULL,NULL,NULL,selectMode);
		}

	// Setup rollup pages
	SetRollupPages();
	
	// Setup named selection sets	
	SetupNamedSelDropDown();

	NotifyDependents(FOREVER, PART_SUBSEL_TYPE|PART_DISPLAY, REFMSG_CHANGE);
	ip->PipeSelLevelChanged();
	NotifyDependents(FOREVER, SELECT_CHANNEL|DISP_ATTRIB_CHANNEL|SUBSEL_TYPE_CHANNEL, REFMSG_CHANGE);
	}

void MeshSelMod::SetSelLevel(DWORD level)
	{
	selLevel = level;
	if (ip && editMod==this) {
		ip->SetSubObjectLevel(selLevel);
		}
	}

void MeshSelMod::LocalDataChanged() 
	{
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	if (ip && editMod==this) {
		SetNumSelLabel();
		ip->ClearCurNamedSelSet();
		}
	}


HitRecord *MeshSelMod::VertHitsToFaceHits(
		HitRecord *hitRec,BOOL all)
	{	
	HitRecord *ptr = NULL;
	TimeValue t = ip->GetTime();

	while (hitRec) {		
		Mesh &mesh = *(((MeshSelData*)hitRec->modContext->localData)->GetMesh());
		AdjEdgeList el(mesh);		
		
		// Add faces adjacent to this vertex
		DWORDTab &list = el[hitRec->hitInfo];
		for (int i=0; i<list.Count(); i++) {
			if (el.edges[list[i]].f[0]!=UNDEFINED) {
				ptr = new HitRecord(			
					ptr,
					hitRec->nodeRef,
					hitRec->modContext,
					hitRec->distance,
					el.edges[list[i]].f[0],
					NULL);
				}
			if (el.edges[list[i]].f[1]!=UNDEFINED) {
				ptr = new HitRecord(				
					ptr,
					hitRec->nodeRef,
					hitRec->modContext,
					hitRec->distance,
					el.edges[list[i]].f[1],
					NULL);
				}
			}
		
		if (!all) break;
		hitRec = hitRec->Next();
		}
	return ptr;
	}

static AdjFaceList *BuildAdjFaceList(Mesh &mesh)
	{
	AdjEdgeList ae(mesh);
	return new AdjFaceList(mesh,ae);
	}

static int whichEdge(Face *f, DWORD a, DWORD b) {
	DWORD *v = f->getAllVerts();
	if (a==v[0]) {
		if (b==v[1]) return 0;
		if (b==v[2]) return 2;	
		} 
	else 
	if (a==v[1]) {
		if (b==v[0]) return 0;
		if (b==v[2]) return 1;	
		}
	// a = v[2]
	if (b==v[0]) return 2;
	if (b==v[1]) return 1;	
	return 0;
	}

inline Point3 FaceNormal(Mesh &mesh, DWORD fi)
	{
	Face *f = &mesh.faces[fi];
	Point3 v1 = mesh.verts[f->v[1]] - mesh.verts[f->v[0]];
	Point3 v2 = mesh.verts[f->v[2]] - mesh.verts[f->v[1]];
	return v1^v2;
	}

inline BOOL FacesCoplanar(
		Mesh& mesh,DWORD f0, DWORD f1, float thresh)
	{
	Point3 n0 =  Normalize(FaceNormal(mesh,f0));
	Point3 n1 =  Normalize(FaceNormal(mesh,f1));	
	float dp = DotProd(n0,n1);
	if ( dp > float(1) ) {
		return TRUE;
		}
	if ( dp < float(-1) ) {
		return FALSE;
		}
	float angle = float(acos(dp));
	return fabs(angle) < fabs(DegToRad(thresh));
	}

static void PolyFromFace(
		Mesh& mesh,AdjFaceList &al,
		DWORD f,BitArray &set,float thresh, BOOL ignoreVisEdges)
	{
	if (set[f]) return;
 	Tab<DWORD> stack;	
				
	// Start by pushing this face on the stack
	stack.Append(1,&f,10);

	while (stack.Count()) {
		// Pop a face off the stack
		DWORD f = stack[stack.Count()-1];
		stack.Delete(stack.Count()-1,1);

		// Select this face		
		set.Set(f);

		// Push neighbors on the stack
		for (int i=0; i<3; i++) {
			// Adjacent face index
			DWORD af = al[f].f[i];
			
			// If it's been done, then continue;
			if (af==UNDEFINED || set[af]) continue;

			// It must be adjacent across hidden edges and within
			// the threshold angle.
			if (ignoreVisEdges || !mesh.faces[f].getEdgeVis(i)) {				
				Face *face = &mesh.faces[af];
				DWORD v0 = mesh.faces[f].v[i];
				DWORD v1 = mesh.faces[f].v[i==2?0:i+1];
				if (ignoreVisEdges || !face->getEdgeVis(whichEdge(face,v0,v1))) {
					if (FacesCoplanar(mesh,f,af,thresh)) {
						stack.Append(1,&af,10);
						} 
					}				
				}
			}
		}
	}

static void ElementFromFace(
		Mesh &mesh, AdjFaceList &al,
		DWORD f, BitArray &set)
	{		
	if (set[f]) return;
	Tab<DWORD> stack;

	// Start by pushing this face on the stack
	stack.Append(1,&f,10);

	while (stack.Count()) {
		// Pop a face off the stack
		DWORD f = stack[stack.Count()-1];
		stack.Delete(stack.Count()-1,1);

		// Select this face
		set.Set(f);

		// Push neighbors on the stack
		for (int i=0; i<3; i++) {
			// Adjacent face index
			DWORD af = al[f].f[i];
		
			// Make sure it hasn't been done.
			if (af!=UNDEFINED && !set[af]) {
				stack.Append(1,&af,10);
				}
			}
		}
	}

void MeshSelMod::SelectSubComponent(
		HitRecord *hitRec, BOOL selected, BOOL all, BOOL invert)
	{
	MeshSelData *d = NULL, *od = NULL;	

	ip->ClearCurNamedSelSet();

	switch (selLevel) {
		case SEL_VERTEX:
			while (hitRec) {
				d = (MeshSelData*)hitRec->modContext->localData;				
				if (theHold.Holding() && !d->held) theHold.Put(new MeshSelRestore(this,d));
				d->SynchBitArrays();
				BOOL state = selected;
				if (invert) state = !d->vertSel[hitRec->hitInfo];
				if (state) d->vertSel.Set(hitRec->hitInfo);
				else       d->vertSel.Clear(hitRec->hitInfo);
				if (!all) break;
				hitRec = hitRec->Next();
				}			
			break;

		case SEL_FACE: {
			HitRecord *faceHitRec = NULL;

			// If we're selecting verts by faces, convert the
			// vertex hits into face hits
			if (selByVert) {
				hitRec = faceHitRec = 
					VertHitsToFaceHits(hitRec,all);
				all = TRUE;
				if (!hitRec) break;
				}

			if (selType==SELTYPE_POLY) {				
				float thresh = GetPolyFaceThesh();
				BitArray set;
				AdjFaceList *al = NULL;

				while (hitRec) {
					Mesh &mesh = *(((MeshSelData*)hitRec->modContext->localData)->GetMesh());
					od = d;
					d  = (MeshSelData*)hitRec->modContext->localData;
					if (theHold.Holding() && !d->held) theHold.Put(new MeshSelRestore(this,d));
					d->SynchBitArrays();

					// Build it the first time
					if (!al) al = BuildAdjFaceList(mesh);

					if (d!=od && od) {
						// Apply to the previous mod context
						if (invert) od->faceSel ^= set;
						else if (selected) od->faceSel |= set;
						else od->faceSel &= ~set;
						set.ClearAll();

						// Build a new adjacency list
						delete al;
						al = BuildAdjFaceList(mesh);
						}
					
					set.SetSize(mesh.getNumFaces());
					 
					PolyFromFace(mesh,*al,hitRec->hitInfo,
						set,thresh,ignoreVisEdge);
					
					if (!all) break;					
					hitRec = hitRec->Next();
					}

				if (al) delete al;

				if (d) {
					if (invert) d->faceSel ^= set;
					else if (selected) d->faceSel |= set;
					else d->faceSel &= ~set;
					}

			} else if (selType==SELTYPE_ELEMENT) {				
				BitArray set;
				AdjFaceList *al = NULL;

				while (hitRec) {
					Mesh &mesh = *(((MeshSelData*)hitRec->modContext->localData)->GetMesh());
					od = d;
					d = (MeshSelData*)hitRec->modContext->localData;
					if (theHold.Holding() && !d->held) theHold.Put(new MeshSelRestore(this,d));
					d->SynchBitArrays();

					// Build it the first time
					if (!al) al = BuildAdjFaceList(mesh);

					if (d!=od && od) {
						// Apply to the previous mod context
						if (invert) od->faceSel ^= set;
						else if (selected) od->faceSel |= set;
						else od->faceSel &= ~set;
						set.ClearAll();

						// Build a new adjacency list
						delete al;
						al = BuildAdjFaceList(mesh);
						}
					
					set.SetSize(mesh.getNumFaces());
					
					ElementFromFace(mesh,*al,
						hitRec->hitInfo,set);					
					
					if (!all) break;
					hitRec = hitRec->Next();
					}				

				if (al) delete al;

				if (d) {
					if (invert) d->faceSel ^= set;
					else if (selected) d->faceSel |= set;
					else d->faceSel &= ~set;
					}

			} else {
				while (hitRec) {					
					d = (MeshSelData*)hitRec->modContext->localData;					
					if (theHold.Holding() && !d->held) theHold.Put(new MeshSelRestore(this,d));
					d->SynchBitArrays();
					BOOL state = selected;
					if (invert) state = !d->faceSel[hitRec->hitInfo];
					if (state) d->faceSel.Set(hitRec->hitInfo);
					else       d->faceSel.Clear(hitRec->hitInfo);
					if (!all) break;
					hitRec = hitRec->Next();
					}
				}			
			
			if (selByVert) {
				// Delete the hits
				HitRecord *ptr = faceHitRec;
				while (ptr) {
					faceHitRec = ptr;
					ptr = ptr->Next();
					delete faceHitRec;
					}				
				}			
			break;
			}

		case SEL_EDGE:
			while (hitRec) {				
				d = (MeshSelData*)hitRec->modContext->localData;				
				if (theHold.Holding() && !d->held) theHold.Put(new MeshSelRestore(this,d));
				d->SynchBitArrays();
				BOOL state = selected;
				if (invert) state = !d->edgeSel[hitRec->hitInfo];
				if (state) d->edgeSel.Set(hitRec->hitInfo);
				else       d->edgeSel.Clear(hitRec->hitInfo);					
				if (!all) break;
				hitRec = hitRec->Next();
				}
			break;
		};

	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	SetNumSelLabel();
	}

void MeshSelMod::ClearSelection(int selLevel)
	{
	ModContextList list;
	INodeTab nodes;	
	ip->GetModContexts(list,nodes);
	MeshSelData *d;
	for (int i=0; i<list.Count(); i++) {
		d = (MeshSelData*)list[i]->localData;
		switch (selLevel) {
			case SEL_VERTEX: 
				if (!d->vertSel.NumberSet()) continue; else break;
			case SEL_FACE:
				if (!d->faceSel.NumberSet()) continue; else break;
			case SEL_EDGE:
				if (!d->edgeSel.NumberSet()) continue; else break;
			}
		if (theHold.Holding() && !d->held) theHold.Put(new MeshSelRestore(this,d));
		d->SynchBitArrays();
		switch (selLevel) {
			case SEL_VERTEX: d->vertSel.ClearAll(); break;
			case SEL_FACE:   d->faceSel.ClearAll(); break;
			case SEL_EDGE:   d->edgeSel.ClearAll(); break;
			}
		}
	ip->ClearCurNamedSelSet();
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	SetNumSelLabel();
	}

void MeshSelMod::SelectAll(int selLevel)
	{
	ModContextList list;
	INodeTab nodes;	
	ip->GetModContexts(list,nodes);
	MeshSelData *d;
	for (int i=0; i<list.Count(); i++) {
		d = (MeshSelData*)list[i]->localData;		
		if (theHold.Holding() && !d->held) theHold.Put(new MeshSelRestore(this,d));
		d->SynchBitArrays();
		switch (selLevel) {
			case SEL_VERTEX: d->vertSel.SetAll(); break;
			case SEL_FACE:   d->faceSel.SetAll(); break;
			case SEL_EDGE:   d->edgeSel.SetAll(); break;
			}
		}
	ip->ClearCurNamedSelSet();
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	SetNumSelLabel();
	}

void MeshSelMod::InvertSelection(int selLevel)
	{
	ModContextList list;
	INodeTab nodes;	
	ip->GetModContexts(list,nodes);
	MeshSelData *d;
	for (int i=0; i<list.Count(); i++) {
		d = (MeshSelData*)list[i]->localData;		
		if (theHold.Holding() && !d->held) theHold.Put(new MeshSelRestore(this,d));
		d->SynchBitArrays();
		switch (selLevel) {
			case SEL_VERTEX: d->vertSel = ~d->vertSel; break;
			case SEL_FACE:   d->faceSel = ~d->faceSel; break;
			case SEL_EDGE:   d->edgeSel = ~d->edgeSel; break;
			}
		}
	ip->ClearCurNamedSelSet();
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	SetNumSelLabel();
	}

void MeshSelMod::SelectByMatID(int id)
	{
	BOOL add = GetKeyState(VK_CONTROL)<0 ? TRUE : FALSE;
	BOOL sub = GetKeyState(VK_MENU)<0 ? TRUE : FALSE;
	theHold.Begin();
	ModContextList list;
	INodeTab nodes;	
	ip->GetModContexts(list,nodes);
	MeshSelData *d;	
	for (int i=0; i<list.Count(); i++) {
		d = (MeshSelData*)list[i]->localData;		
		if (!d->held) theHold.Put(new MeshSelRestore(this,d));
		d->SynchBitArrays();
		if (!add && !sub) d->faceSel.ClearAll();
		Mesh &mesh = *d->mesh;
		for (int i=0; i<mesh.getNumFaces(); i++) {
			if (mesh.faces[i].getMatID()==(MtlID)id) {
				if (sub) d->faceSel.Clear(i);
				else     d->faceSel.Set(i);
				}
			}
		}	
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	ip->RedrawViews(ip->GetTime());
	ip->ClearCurNamedSelSet();
	theHold.Accept(_T("Select By Material ID"));
	SetNumSelLabel();
	}

void MeshSelMod::SelectOpenEdges()
	{
	theHold.Begin();
	ModContextList list;
	INodeTab nodes;	
	ip->GetModContexts(list,nodes);
	MeshSelData *d;	
	for (int i=0; i<list.Count(); i++) {
		d = (MeshSelData*)list[i]->localData;		
		if (!d->held) theHold.Put(new MeshSelRestore(this,d));
		d->SynchBitArrays();
		d->edgeSel.ClearAll();

		AdjEdgeList adj(*d->mesh);
		for (int j=0; j<adj.edges.Count(); j++) {
			if (adj.edges[j].f[0]==UNDEFINED) {
				int wh = whichEdge(
					&d->mesh->faces[adj.edges[j].f[1]],
					adj.edges[j].v[0],
					adj.edges[j].v[1]);
				d->edgeSel.Set(adj.edges[j].f[1]*3+wh);
				}
			if (adj.edges[j].f[1]==UNDEFINED) {
				int wh = whichEdge(
					&d->mesh->faces[adj.edges[j].f[0]],
					adj.edges[j].v[0],
					adj.edges[j].v[1]);
				d->edgeSel.Set(adj.edges[j].f[0]*3+wh);
				}
			}
		}	
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	ip->RedrawViews(ip->GetTime());
	ip->ClearCurNamedSelSet();
	theHold.Accept(_T("Select Open Edges"));
	SetNumSelLabel();
	}

void MeshSelMod::SelectFrom(int from) 
	{
	ModContextList list;
	INodeTab nodes;	
	ip->GetModContexts(list,nodes);
	MeshSelData *d;
	theHold.Begin();
	for (int i=0; i<list.Count(); i++) {
		d = (MeshSelData*)list[i]->localData;
		if (!d->held) theHold.Put(new MeshSelRestore(this,d));
		d->SynchBitArrays();
		switch (selLevel) {
			case SEL_VERTEX: 
				if (from==SEL_FACE) d->SelVertByFace();
				else d->SelVertByEdge();
				break;
			case SEL_FACE:   
				if (from==SEL_VERTEX) d->SelFaceByVert();
				else d->SelFaceByEdge();
				break;
			case SEL_EDGE:
				if (from==SEL_VERTEX) d->SelEdgeByVert();
				else d->SelEdgeByFace();
				break;
			}
		}
	theHold.Accept(GetString(IDS_DS_SELECT));
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	ip->RedrawViews(ip->GetTime());
	ip->ClearCurNamedSelSet();
	SetNumSelLabel();
	}


float MeshSelMod::GetPolyFaceThesh()
	{
	ISpinnerControl *spin = GetISpinner(GetDlgItem(hParams,IDC_PLANARSPINNER));
	float thresh = spin->GetFVal();
	ReleaseISpinner(spin);
	return thresh;
	}


#define SELLEVEL_CHUNKID		0x0100
#define VERTSEL_CHUNKID			0x0200
#define FACESEL_CHUNKID			0x0210
#define EDGESEL_CHUNKID			0x0220

#define NAMEDVSEL_NAMES_CHUNK	0x2805
#define NAMEDFSEL_NAMES_CHUNK	0x2806
#define NAMEDESEL_NAMES_CHUNK	0x2807
#define NAMEDSEL_STRING_CHUNK	0x2809
#define NAMEDSEL_ID_CHUNK		0x2810

#define VSELSET_CHUNK			0x2845
#define FSELSET_CHUNK			0x2846
#define ESELSET_CHUNK			0x2847

static int namedSelID[] = {NAMEDVSEL_NAMES_CHUNK,NAMEDFSEL_NAMES_CHUNK,NAMEDESEL_NAMES_CHUNK};


IOResult MeshSelMod::Save(ISave *isave)
	{
	IOResult res;
	ULONG nb;
	Modifier::Save(isave);
	isave->BeginChunk(SELLEVEL_CHUNKID);
	res = isave->Write(&selLevel, sizeof(selLevel), &nb);
	isave->EndChunk();

	for (int j=0; j<3; j++) {
		if (namedSel[j].Count()) {
			isave->BeginChunk(namedSelID[j]);			
			for (int i=0; i<namedSel[j].Count(); i++) {
				isave->BeginChunk(NAMEDSEL_STRING_CHUNK);
				isave->WriteWString(*namedSel[j][i]);
				isave->EndChunk();

				isave->BeginChunk(NAMEDSEL_ID_CHUNK);
				isave->Write(&ids[j][i],sizeof(DWORD),&nb);
				isave->EndChunk();
				}
			isave->EndChunk();			
			}
		}

	return res;
	}

IOResult MeshSelMod::LoadNamedSelChunk(ILoad *iload,int level)
	{	
	IOResult res;
	DWORD ix=0;
	ULONG nb;

	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case NAMEDSEL_STRING_CHUNK: {
				TCHAR *name;
				res = iload->ReadWStringChunk(&name);
				//AddSet(TSTR(name),level+1);
				TSTR *newName = new TSTR(name);
				namedSel[level].Append(1,&newName);				
				ids[level].Append(1,&ix);
				ix++;
				break;
				}
			case NAMEDSEL_ID_CHUNK:
				iload->Read(&ids[level][ids[level].Count()-1],sizeof(DWORD), &nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

IOResult MeshSelMod::Load(ILoad *iload)
	{
	IOResult res;
	ULONG nb;
	Modifier::Load(iload);
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case SELLEVEL_CHUNKID:
				iload->Read(&selLevel, sizeof(selLevel), &nb);
				break;

			case NAMEDVSEL_NAMES_CHUNK: {				
				res = LoadNamedSelChunk(iload,0);
				break;
				}

			case NAMEDFSEL_NAMES_CHUNK: {
				res = LoadNamedSelChunk(iload,1);
				break;
				}

			case NAMEDESEL_NAMES_CHUNK: {
				res = LoadNamedSelChunk(iload,2);
				break;
				}
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

IOResult MeshSelMod::SaveLocalData(ISave *isave, LocalModData *ld)
	{	
	MeshSelData *d = (MeshSelData*)ld;

	isave->BeginChunk(VERTSEL_CHUNKID);
	d->vertSel.Save(isave);
	isave->EndChunk();

	isave->BeginChunk(FACESEL_CHUNKID);
	d->faceSel.Save(isave);
	isave->EndChunk();

	isave->BeginChunk(EDGESEL_CHUNKID);
	d->edgeSel.Save(isave);
	isave->EndChunk();
	
	if (d->vselSet.Count()) {
		isave->BeginChunk(VSELSET_CHUNK);
		d->vselSet.Save(isave);
		isave->EndChunk();
		}
	if (d->eselSet.Count()) {
		isave->BeginChunk(ESELSET_CHUNK);
		d->eselSet.Save(isave);
		isave->EndChunk();
		}
	if (d->fselSet.Count()) {
		isave->BeginChunk(FSELSET_CHUNK);
		d->fselSet.Save(isave);
		isave->EndChunk();
		}

	return IO_OK;
	}

IOResult MeshSelMod::LoadLocalData(ILoad *iload, LocalModData **pld)
	{
	MeshSelData *d = new MeshSelData;
	*pld = d;
	IOResult res;	
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case VERTSEL_CHUNKID:
				d->vertSel.Load(iload);
				break;
			case FACESEL_CHUNKID:
				d->faceSel.Load(iload);
				break;
			case EDGESEL_CHUNKID:
				d->edgeSel.Load(iload);
				break;

			case VSELSET_CHUNK:
				res = d->vselSet.Load(iload);
				break;
			case FSELSET_CHUNK:
				res = d->fselSet.Load(iload);
				break;
			case ESELSET_CHUNK:
				res = d->eselSet.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}



// Window Procs ------------------------------------------------------

static BOOL CALLBACK EditTriObjectProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	MeshSelMod *mod = (MeshSelMod*)GetWindowLong(hWnd,GWL_USERDATA);
	switch (msg) {
		case WM_INITDIALOG:
			mod = (MeshSelMod*)lParam;
			SetWindowLong(hWnd,GWL_USERDATA,lParam);
			mod->hParams = hWnd;
			break;
		
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			mod->ip->RollupMouseMessage(hWnd,msg,wParam,lParam);
			break;

		case WM_UPDATE_CACHE:
			mod->UpdateCache((TimeValue)wParam);
 			break;

		default: return FALSE;
		}
	return TRUE;
	}

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

			ICustButton *but = GetICustButton(GetDlgItem(hWnd,IDC_VERT_PASTENS));
			if (GetMeshNamedSelClip(CLIP_VERT)) but->Enable();
			else but->Disable();
			ReleaseICustButton(but);
			break;
			}
		
		case WM_UPDATE_CACHE:
			mod->UpdateCache((TimeValue)wParam);
 			break;

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_MESHSEL_GETFACE: mod->SelectFrom(SEL_FACE); break;
				case IDC_MESHSEL_GETEDGE: mod->SelectFrom(SEL_EDGE); break;

				case IDC_VERT_COPYNS:  mod->NSCopy();  break;
				case IDC_VERT_PASTENS: mod->NSPaste(); break;
				}
			break;

		default: return FALSE;
		}
	return TRUE;
	}

void MeshSelMod::SetEdgeVisCheckState()
	{
	if (selType==SELTYPE_POLY) {
		EnableWindow(GetDlgItem(hParams,IDC_IGNORE_VISEDGE),TRUE);
	} else {
		EnableWindow(GetDlgItem(hParams,IDC_IGNORE_VISEDGE),FALSE);
		}
	}

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

			ICustToolbar *iToolbar = 
		 		GetICustToolbar(GetDlgItem(hWnd,IDC_SELTYPE));
			iToolbar->SetImage(hFaceImages);
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,0,0,0,0,16,15,24,23,IDC_SELSINGLE));
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,1,1,1,1,16,15,24,23,IDC_SELPOLY));
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,2,2,2,1,16,15,24,23,IDC_SELELEMENT));
			ReleaseICustToolbar(iToolbar);
			mod->SetSelType(mod->selType);

			ISpinnerControl *spin = GetISpinner(GetDlgItem(hWnd,IDC_PLANARSPINNER));
			spin->SetLimits(0, 180, FALSE);
			spin->SetScale(0.1f);
			spin->LinkToEdit(GetDlgItem(hWnd,IDC_PLANAR), EDITTYPE_FLOAT);
		 	spin->SetValue(lastFaceThresh,FALSE);
			ReleaseISpinner(spin);

			spin = GetISpinner(GetDlgItem(hWnd,IDC_MATIDSPIN));
			spin->SetLimits(1, 65535, FALSE);
			spin->SetScale(0.1f);
			spin->LinkToEdit(GetDlgItem(hWnd,IDC_MATID), EDITTYPE_INT);
		 	spin->SetValue(lastMatID,FALSE);
			ReleaseISpinner(spin);

			CheckDlgButton(hWnd,IDC_SEL_BYVERT,mod->selByVert);
			CheckDlgButton(hWnd,IDC_IGNORE_BACKFACES,mod->ignoreBackfaces);
			CheckDlgButton(hWnd,IDC_IGNORE_VISEDGE,mod->ignoreVisEdge);
			mod->SetEdgeVisCheckState();

			ICustButton *but = GetICustButton(GetDlgItem(hWnd,IDC_FACE_PASTENS));
			if (GetMeshNamedSelClip(CLIP_FACE)) but->Enable();
			else but->Disable();
			ReleaseICustButton(but);
			break;
			}

		case WM_UPDATE_CACHE:
			mod->UpdateCache((TimeValue)wParam);
 			break;

		case WM_DESTROY: {			
			ISpinnerControl *spin = GetISpinner(GetDlgItem(hWnd,IDC_PLANARSPINNER));
			lastFaceThresh = spin->GetFVal();
			ReleaseISpinner(spin);
			spin = GetISpinner(GetDlgItem(hWnd,IDC_MATIDSPIN));
			lastMatID = spin->GetIVal();
			ReleaseISpinner(spin);
			break;
			}

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_MESHSEL_SELBYMAT: {
					ISpinnerControl *spin = GetISpinner(GetDlgItem(hWnd,IDC_MATIDSPIN));
					mod->SelectByMatID(spin->GetIVal()-1);
					ReleaseISpinner(spin);
					break;
					}

				case IDC_SELSINGLE:
					mod->SetSelType(SELTYPE_SINGLE);
					mod->SetEdgeVisCheckState();
					break;
				case IDC_SELPOLY:
					mod->SetSelType(SELTYPE_POLY);
					mod->SetEdgeVisCheckState();
					break;
				case IDC_SELELEMENT:
					mod->SetSelType(SELTYPE_ELEMENT);
					mod->SetEdgeVisCheckState();
					break;

				case IDC_SEL_BYVERT:
					mod->selByVert = IsDlgButtonChecked(hWnd,IDC_SEL_BYVERT);
					break;

				case IDC_IGNORE_BACKFACES:
					mod->ignoreBackfaces = IsDlgButtonChecked(hWnd,IDC_IGNORE_BACKFACES);
					break;

				case IDC_IGNORE_VISEDGE:
					mod->ignoreVisEdge = IsDlgButtonChecked(hWnd,IDC_IGNORE_VISEDGE);
					break;

				case IDC_MESHSEL_GETVERT: mod->SelectFrom(SEL_VERTEX); break;
				case IDC_MESHSEL_GETEDGE: mod->SelectFrom(SEL_EDGE); break;

				case IDC_FACE_COPYNS:  mod->NSCopy();  break;
				case IDC_FACE_PASTENS: mod->NSPaste(); break;
				}
			break;

		case WM_NOTIFY:
			if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {
				LPTOOLTIPTEXT lpttt;
				lpttt = (LPTOOLTIPTEXT)lParam;				
				switch (lpttt->hdr.idFrom) {
					case IDC_SELSINGLE:
						lpttt->lpszText = GetString(IDS_RB_FACESEL);
						break;
					case IDC_SELPOLY:
						lpttt->lpszText = GetString(IDS_RB_POLYGONSEL);
						break;
					case IDC_SELELEMENT:
						lpttt->lpszText = GetString(IDS_RB_ELEMENTSEL);
						break;
					}
				}
			break;
		
		default: return FALSE;
		}
	return TRUE;
	}

static BOOL CALLBACK EditTriEdgeProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	MeshSelMod *mod = (MeshSelMod*)GetWindowLong(hWnd,GWL_USERDATA);
	switch (msg) {
		case WM_INITDIALOG: {
			mod = (MeshSelMod*)lParam;
			SetWindowLong(hWnd,GWL_USERDATA,lParam);
			mod->hParams = hWnd;
			
			ICustButton *but = GetICustButton(GetDlgItem(hWnd,IDC_EDGE_PASTENS));
			if (GetMeshNamedSelClip(CLIP_EDGE)) but->Enable();
			else but->Disable();
			ReleaseICustButton(but);
			break;
			}
		
		case WM_UPDATE_CACHE:
			mod->UpdateCache((TimeValue)wParam);
 			break;

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_MESHSEL_GETVERT: mod->SelectFrom(SEL_VERTEX); break;
				case IDC_MESHSEL_GETFACE: mod->SelectFrom(SEL_FACE); break;

				case IDC_EDGE_COPYNS:  mod->NSCopy();  break;
				case IDC_EDGE_PASTENS: mod->NSPaste(); break;

				case IDC_MESHSEL_SELOPEN:
					mod->SelectOpenEdges();
					break;
				}
			break;

		default: return FALSE;
		}
	return TRUE;
	}


static BOOL CALLBACK PickSetNameDlgProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	static TSTR *name;

	switch (msg) {
		case WM_INITDIALOG: {
			name = (TSTR*)lParam;
			ICustEdit *edit =GetICustEdit(GetDlgItem(hWnd,IDC_SET_NAME));
			edit->SetText(*name);
			ReleaseICustEdit(edit);
			break;
			}

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDOK: {
					ICustEdit *edit =GetICustEdit(GetDlgItem(hWnd,IDC_SET_NAME));
					TCHAR buf[256];
					edit->GetText(buf,256);
					*name = TSTR(buf);
					ReleaseICustEdit(edit);
					EndDialog(hWnd,1);
					break;
					}

				case IDCANCEL:
					EndDialog(hWnd,0);
					break;
				}
			break;

		default:
			return FALSE;
		};
	return TRUE;
	}

BOOL MeshSelMod::GetUniqueSetName(TSTR &name)
	{
	while (1) {				
		Tab<TSTR*> &setList = namedSel[selLevel-1];

		BOOL unique = TRUE;
		for (int i=0; i<setList.Count(); i++) {
			if (name==*setList[i]) {
				unique = FALSE;
				break;
				}
			}
		if (unique) break;

		if (!DialogBoxParam(
			hInstance, 
			MAKEINTRESOURCE(IDD_PASTE_NAMEDSET),
			hParams, 
			PickSetNameDlgProc,
			(LPARAM)&name)) return FALSE;		
		}
	return TRUE;
	}

static BOOL CALLBACK PickSetDlgProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{	
	switch (msg) {
		case WM_INITDIALOG:	{
			Tab<TSTR*> *setList = (Tab<TSTR*>*)lParam;
			for (int i=0; i<setList->Count(); i++) {
				int pos  = SendDlgItemMessage(hWnd,IDC_NS_LIST,LB_ADDSTRING,0,
					(LPARAM)(TCHAR*)*(*setList)[i]);
				SendDlgItemMessage(hWnd,IDC_NS_LIST,LB_SETITEMDATA,pos,i);
				}
			break;
			}

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_NS_LIST:
					if (HIWORD(wParam)!=LBN_DBLCLK) break;
					// fall through

				case IDOK: {
					int sel = SendDlgItemMessage(hWnd,IDC_NS_LIST,LB_GETCURSEL,0,0);
					if (sel!=LB_ERR) {
						int res =SendDlgItemMessage(hWnd,IDC_NS_LIST,LB_GETITEMDATA,sel,0);
						EndDialog(hWnd,res);
						break;
						}
					// fall through
					}

				case IDCANCEL:
					EndDialog(hWnd,-1);
					break;
				}
			break;

		default:
			return FALSE;
		};
	return TRUE;
	}

int MeshSelMod::SelectNamedSet()
	{
	Tab<TSTR*> &setList = namedSel[selLevel-1];
	return DialogBoxParam(
		hInstance, 
		MAKEINTRESOURCE(IDD_SEL_NAMEDSET),
		hParams, 
		PickSetDlgProc,
		(LPARAM)&setList);
	}




// MeshSelData -----------------------------------------------------

LocalModData *MeshSelData::Clone()
	{
	MeshSelData *d = new MeshSelData;
	d->vertSel = vertSel;
	d->faceSel = faceSel;
	d->edgeSel = edgeSel;
	held = FALSE;
	mesh = NULL;
	return d;
	}

MeshSelData::MeshSelData(Mesh &mesh)
	{
	vertSel = mesh.vertSel;
	faceSel = mesh.faceSel;
	edgeSel = mesh.edgeSel;
	held = FALSE;
	this->mesh = NULL;
	}

void MeshSelData::SynchBitArrays()
	{
	if (mesh) {
		vertSel.SetSize(mesh->vertSel.GetSize(),TRUE);
		faceSel.SetSize(mesh->faceSel.GetSize(),TRUE);
		edgeSel.SetSize(mesh->edgeSel.GetSize(),TRUE);
		}
	}

void MeshSelData::SetCache(Mesh &mesh)
	{
	if (this->mesh) delete this->mesh;
	this->mesh = new Mesh(mesh);
	}

void MeshSelData::FreeCache()
	{
	if (mesh) delete mesh;
	mesh = NULL;
	}

void MeshSelData::SetVertSel(BitArray &set)
	{
	vertSel = set;
	if (mesh) mesh->vertSel = set;
	}

void MeshSelData::SetFaceSel(BitArray &set)
	{
	faceSel = set;
	if (mesh) mesh->faceSel = set;
	}

void MeshSelData::SetEdgeSel(BitArray &set)
	{
	edgeSel = set;
	if (mesh) mesh->edgeSel = set;
	}

void MeshSelData::SelVertByFace()
	{
	for (int i=0; i<mesh->getNumFaces(); i++) {
		if (faceSel[i]) {
			for (int j=0; j<3; j++) {
				vertSel.Set(mesh->faces[i].v[j],TRUE);
				}
			}
		}
	}

void MeshSelData::SelVertByEdge()
	{
	for (int i=0; i<mesh->getNumFaces(); i++) {		
		for (int j=0; j<3; j++) {
			if (edgeSel[i*3+j]) {
				vertSel.Set(mesh->faces[i].v[j],TRUE);
				vertSel.Set(mesh->faces[i].v[(j+1)%3],TRUE);
				}
			}
		}
	}

void MeshSelData::SelFaceByVert()
	{
	for (int i=0; i<mesh->getNumFaces(); i++) {
		for (int j=0; j<3; j++) {
			if (vertSel[mesh->faces[i].v[j]]) {
				faceSel.Set(i);
				break;
				}
			}
		}
	}

void MeshSelData::SelFaceByEdge()
	{
	for (int i=0; i<mesh->getNumFaces(); i++) {
		for (int j=0; j<3; j++) {
			if (edgeSel[i*3+j]) {
				faceSel.Set(i);
				break;
				}
			}
		}
	}

void MeshSelData::SelEdgeByVert()
	{
	for (int i=0; i<mesh->getNumFaces(); i++) {
		for (int j=0; j<3; j++) {
			if (vertSel[mesh->faces[i].v[j]]) {
				edgeSel.Set(i*3+j);
				}
			if (vertSel[mesh->faces[i].v[(j+1)%3]]) {
				edgeSel.Set(i*3+j);
				}
			}
		}
	}

void MeshSelData::SelEdgeByFace()
	{
	for (int i=0; i<mesh->getNumFaces(); i++) {
		if (faceSel[i]) {
			for (int j=0; j<3; j++) {			
				edgeSel.Set(i*3+j);
				}			
			}
		}
	}


// MeshSelRestore --------------------------------------------------

MeshSelRestore::MeshSelRestore(MeshSelMod *m, MeshSelData *data)
	{
	mod     = m;
	level   = mod->selLevel;
	d       = data;
	d->held = TRUE;
	switch (level) {
		case SEL_OBJECT: assert(0); break;
		case SEL_VERTEX: usel = d->vertSel; break;
		case SEL_FACE:   usel = d->faceSel; break;
		case SEL_EDGE:   usel = d->edgeSel; break;
		}
	}

void MeshSelRestore::Restore(int isUndo)
	{
	if (isUndo) {
		switch (level) {			
			case SEL_VERTEX: rsel = d->vertSel; break;
			case SEL_FACE:   rsel = d->faceSel; break;
			case SEL_EDGE:   rsel = d->edgeSel; break;
			}
		}
	switch (level) {		
		case SEL_VERTEX: d->vertSel = usel; break;
		case SEL_FACE:   d->faceSel = usel; break;
		case SEL_EDGE:   d->edgeSel = usel; break;
		}
	mod->NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	mod->SetNumSelLabel();
	}

void MeshSelRestore::Redo()
	{
	switch (level) {		
		case SEL_VERTEX: d->vertSel = rsel; break;
		case SEL_FACE:   d->faceSel = rsel; break;
		case SEL_EDGE:   d->edgeSel = rsel; break;
		}
	mod->NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	mod->SetNumSelLabel();
	}


//--- Named selection sets -----------------------------------------

int MeshSelMod::FindSet(TSTR &setName,int level)
	{
	for (int i=0; i<namedSel[level-1].Count(); i++) {
		if (setName == *namedSel[level-1][i]) {
			return i;			
			}
		}
	return -1;
	}

DWORD MeshSelMod::AddSet(TSTR &setName,int level)
	{
	DWORD id = 0;
	TSTR *name = new TSTR(setName);
	namedSel[level-1].Append(1,&name);
	BOOL found = FALSE;
	while (!found) {
		found = TRUE;
		for (int i=0; i<ids[level-1].Count(); i++) {
			if (ids[level-1][i]==id) {
				id++;
				found = FALSE;
				break;
				}
			}
		}
	ids[level-1].Append(1,&id);
	return id;
	}

void MeshSelMod::RemoveSet(TSTR &setName,int level)
	{
	int i = FindSet(setName,level);
	if (i>=0) {
		delete namedSel[level-1][i];
		namedSel[level-1].Delete(i,1);
		ids[level-1].Delete(i,1);
		}
	}

void MeshSelMod::ClearSetNames()
	{
	for (int i=0; i<3; i++) {
		for (int j=0; j<namedSel[i].Count(); j++) {
			delete namedSel[i][j];
			namedSel[i][j] = NULL;
			}
		}
	}

void MeshSelMod::ActivateSubSelSet(TSTR &setName)
	{
	ModContextList mcList;
	INodeTab nodes;
	int index = FindSet(setName,selLevel);	
	if (index<0 || !ip) return;

	ip->GetModContexts(mcList,nodes);

	for (int i = 0; i < mcList.Count(); i++) {
		MeshSelData *meshData = (MeshSelData*)mcList[i]->localData;
		if (!meshData) continue;
		if (theHold.Holding() && !meshData->held) theHold.Put(new MeshSelRestore(this,meshData));

		BitArray *set = NULL;

		switch (selLevel) {
			case SEL_VERTEX:
				set = meshData->vselSet.GetSet(ids[0][index]);
				if (set) {
					if (set->GetSize()!=meshData->vertSel.GetSize()) {
						set->SetSize(meshData->vertSel.GetSize(),TRUE);
						}				
					meshData->vertSel = *set;
					}
				break;

			case SEL_FACE:
				set = meshData->fselSet.GetSet(ids[1][index]);
				if (set) {
					if (set->GetSize()!=meshData->faceSel.GetSize()) {
						set->SetSize(meshData->faceSel.GetSize(),TRUE);
						}
					meshData->faceSel = *set;
					}
				break;

			case SEL_EDGE:
				set = meshData->eselSet.GetSet(ids[2][index]);
				if (set) {
					if (set->GetSize()!=meshData->edgeSel.GetSize()) {
						set->SetSize(meshData->edgeSel.GetSize(),TRUE);
						}
					meshData->edgeSel = *set;
					}
				break;
			}		

		/*
		switch (selLevel) {
			case SEL_VERTEX:
				set = meshData->vselSet.GetSet(ids[0][index]);
				if (meshData->vselSet[index].GetSize()!=meshData->vertSel.GetSize()) {
					meshData->vselSet[index].SetSize(meshData->vertSel.GetSize(),TRUE);
					}				
				meshData->vertSel = meshData->vselSet[index];
				break;

			case SEL_FACE:
				if (meshData->fselSet[index].GetSize()!=meshData->faceSel.GetSize()) {
					meshData->fselSet[index].SetSize(meshData->faceSel.GetSize(),TRUE);
					}
				meshData->faceSel = meshData->fselSet[index];				
				break;

			case SEL_EDGE:
				if (meshData->eselSet[index].GetSize()!=meshData->edgeSel.GetSize()) {
					meshData->eselSet[index].SetSize(meshData->edgeSel.GetSize(),TRUE);
					}
				meshData->edgeSel = meshData->eselSet[index];				 
				break;
			}
		*/
		}
	
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);	
	ip->RedrawViews(ip->GetTime());
	SetNumSelLabel();
	}

void MeshSelMod::NewSetFromCurSel(TSTR &setName)
	{
	ModContextList mcList;
	INodeTab nodes;	
	DWORD id = -1;
	int index = FindSet(setName,selLevel);	
	if (index<0) id = AddSet(setName,selLevel);
	else {
		switch (selLevel) {
			case SEL_VERTEX: id = ids[0][index]; break;
			case SEL_FACE:   id = ids[1][index]; break;
			case SEL_EDGE:   id = ids[2][index]; break;
			}
		}

	ip->GetModContexts(mcList,nodes);

	for (int i = 0; i < mcList.Count(); i++) {
		MeshSelData *meshData = (MeshSelData*)mcList[i]->localData;
		if (!meshData) continue;
		
		BitArray *set = NULL;

		switch (selLevel) {
			case SEL_VERTEX:	
				if (index>=0 && (set = meshData->vselSet.GetSet(id))) {
					*set = meshData->vertSel;
				} else {
					meshData->vselSet.AppendSet(meshData->vertSel,id);
					}
				break;

			case SEL_FACE:
				if (index>=0 && (set = meshData->fselSet.GetSet(id))) {
					*set = meshData->faceSel;
				} else {
					meshData->fselSet.AppendSet(meshData->faceSel,id);
					}
				break;

			case SEL_EDGE:
				if (index>=0 && (set = meshData->eselSet.GetSet(id))) {
					*set = meshData->edgeSel;
				} else {
					meshData->eselSet.AppendSet(meshData->edgeSel,id);
					}
				break;
			}		

		/*
		switch (selLevel) {
			case SEL_VERTEX:	
				if (index>=0) meshData->vselSet[index] = meshData->vertSel;
				else meshData->vselSet.AppendSet(meshData->vertSel);				
				break;

			case SEL_FACE:
				if (index>=0) meshData->fselSet[index] = meshData->faceSel;
				else meshData->fselSet.AppendSet(meshData->faceSel);					
				break;

			case SEL_EDGE:
				if (index>=0) meshData->eselSet[index] = meshData->edgeSel;
				else meshData->eselSet.AppendSet(meshData->edgeSel);				
				break;
			}	
		*/
		}	
	nodes.DisposeTemporary();
	}

void MeshSelMod::RemoveSubSelSet(TSTR &setName)
	{
	ModContextList mcList;
	INodeTab nodes;
	int index = FindSet(setName,selLevel);

	if (index<0 || !ip) return;		
	ip->GetModContexts(mcList,nodes);

	DWORD id = -1;
	switch (selLevel) {
		case SEL_VERTEX: id = ids[0][index]; break;
		case SEL_FACE:   id = ids[1][index]; break;
		case SEL_EDGE:   id = ids[2][index]; break;
		}

	for (int i = 0; i < mcList.Count(); i++) {
		MeshSelData *meshData = (MeshSelData*)mcList[i]->localData;
		if (!meshData) continue;		

		switch (selLevel) {
			case SEL_VERTEX:	
				if (theHold.Holding()) {
					theHold.Put(new DeleteSetRestore(&meshData->vselSet,id));
					}
				meshData->vselSet.RemoveSet(id);
				break;

			case SEL_FACE:
				if (theHold.Holding()) {
					theHold.Put(new DeleteSetRestore(&meshData->fselSet,id));
					}
				meshData->fselSet.RemoveSet(id);
				break;

			case SEL_EDGE:
				if (theHold.Holding()) {
					theHold.Put(new DeleteSetRestore(&meshData->eselSet,id));
					}
				meshData->eselSet.RemoveSet(id);
				break;
			}		
		}
	
	if (theHold.Holding()) {
		theHold.Put(new DeleteSetNameRestore(&namedSel[selLevel-1],this,&ids[selLevel-1],id));
		}
	RemoveSet(setName,selLevel);
	ip->ClearCurNamedSelSet();
	nodes.DisposeTemporary();
	}

void MeshSelMod::SetupNamedSelDropDown()
	{	
	if (selLevel != SEL_OBJECT) {
		ip->ClearSubObjectNamedSelSets();
		for (int i=0; i<namedSel[selLevel-1].Count(); i++) {
			ip->AppendSubObjectNamedSelSet(*namedSel[selLevel-1][i]);
			}
		}	
	}

int MeshSelMod::NumNamedSelSets()
	{
	return namedSel[selLevel-1].Count();
	}

TSTR MeshSelMod::GetNamedSelSetName(int i)
	{
	return *namedSel[selLevel-1][i];
	}

void MeshSelMod::SetNamedSelSetName(int i,TSTR &newName)
	{
	if (theHold.Holding()) {
		theHold.Put(new SetNameRestore(&namedSel[selLevel-1],this,&ids[selLevel-1],ids[selLevel-1][i]));
		}
	*namedSel[selLevel-1][i] = newName;
	}

void MeshSelMod::NewSetByOperator(TSTR &newName,Tab<int> &sets,int op)
	{
	ModContextList mcList;
	INodeTab nodes;		
	
	DWORD id = AddSet(newName,selLevel);
	if (theHold.Holding()) {
		theHold.Put(new AppendSetNameRestore(this,&namedSel[selLevel-1],&ids[selLevel-1]));
		}

	BOOL delSet = TRUE;
	ip->GetModContexts(mcList,nodes);
	for (int i = 0; i < mcList.Count(); i++) {
		MeshSelData *meshData = (MeshSelData*)mcList[i]->localData;
		if (!meshData) continue;
	
		BitArray bits;
		NamedSelSetList *setList;

		switch (selLevel) {
			case SEL_VERTEX: setList = &meshData->vselSet; break;				
			case SEL_FACE:   setList = &meshData->fselSet; break;			
			case SEL_EDGE:   setList = &meshData->eselSet; break;			
			}		
		
		bits = (*setList)[sets[0]];

		for (int i=1; i<sets.Count(); i++) {
			switch (op) {
				case NEWSET_MERGE:
					bits |= (*setList)[sets[i]];
					break;

				case NEWSET_INTERSECTION:
					bits &= (*setList)[sets[i]];
					break;

				case NEWSET_SUBTRACT:
					bits &= ~((*setList)[sets[i]]);
					break;
				}
			}
		if (bits.NumberSet()) delSet = FALSE;

		setList->AppendSet(bits,id);
		if (theHold.Holding()) {
			theHold.Put(new AppendSetRestore(setList));
			}
		}		
	if (delSet) RemoveSubSelSet(newName);
	}

void MeshSelMod::NSCopy()
	{
	int index = SelectNamedSet();
	if (index>=0) {		
		ModContextList mcList;
		INodeTab nodes;			
	
		MeshNamedSelClip *clip = new MeshNamedSelClip(*namedSel[selLevel-1][index]);
		
		ip->GetModContexts(mcList,nodes);
		for (int i = 0; i < mcList.Count(); i++) {
			MeshSelData *meshData = (MeshSelData*)mcList[i]->localData;
			if (!meshData) continue;

			NamedSelSetList *setList;
			switch (selLevel) {
				case SEL_VERTEX: setList = &meshData->vselSet; break;				
				case SEL_FACE:   setList = &meshData->fselSet; break;			
				case SEL_EDGE:   setList = &meshData->eselSet; break;			
				}		

			BitArray *bits = new BitArray(*setList->sets[index]);
			clip->sets.Append(1,&bits);			
			}
		SetMeshNamedSelClip(clip,selLevel);
		
		// Enable the paste button
		ICustButton *but;
		switch (selLevel) {
			case SEL_VERTEX:
				but = GetICustButton(GetDlgItem(hParams,IDC_VERT_PASTENS));
				break;
			case SEL_FACE:
				but = GetICustButton(GetDlgItem(hParams,IDC_FACE_PASTENS));
				break;
			case SEL_EDGE:
				but = GetICustButton(GetDlgItem(hParams,IDC_EDGE_PASTENS));
				break;
			}		
		but->Enable();
		ReleaseICustButton(but);
		}
	}

void MeshSelMod::NSPaste()
	{
	MeshNamedSelClip *clip = GetMeshNamedSelClip(selLevel);
	if (!clip) return;	
	TSTR name = clip->name;
	if (!GetUniqueSetName(name)) return;

	ModContextList mcList;
	INodeTab nodes;		
	
	theHold.Begin();

	DWORD id = AddSet(name,selLevel);	
	theHold.Put(new AppendSetNameRestore(this,&namedSel[selLevel-1],&ids[selLevel-1]));	

	ip->GetModContexts(mcList,nodes);
	for (int i = 0; i < mcList.Count(); i++) {
		MeshSelData *meshData = (MeshSelData*)mcList[i]->localData;
		if (!meshData) continue;

		NamedSelSetList *setList;
		switch (selLevel) {
			case SEL_VERTEX: setList = &meshData->vselSet; break;				
			case SEL_FACE:   setList = &meshData->fselSet; break;			
			case SEL_EDGE:   setList = &meshData->eselSet; break;			
			}		
				
		if (i>=clip->sets.Count()) {
			BitArray bits;
			setList->AppendSet(bits,id);
		} else {
			setList->AppendSet(*clip->sets[i],id);
			}
		theHold.Put(new AppendSetRestore(setList));		
		}	
	
	ActivateSubSelSet(name);
	ip->SetCurNamedSelSet(name);
	theHold.Accept(_T("Paste Named Selection"));
	SetupNamedSelDropDown();
	}

void MeshSelMod::SetNumSelLabel()
	{	
	TSTR buf;
	int num = 0;

	if (!hParams) return;

	ModContextList mcList;
	INodeTab nodes;

	ip->GetModContexts(mcList,nodes);
	for (int i = 0; i < mcList.Count(); i++) {
		MeshSelData *meshData = (MeshSelData*)mcList[i]->localData;
		if (!meshData) continue;

		switch (selLevel) {
			case SEL_VERTEX: num += meshData->vertSel.NumberSet(); break;
			case SEL_FACE:   num += meshData->faceSel.NumberSet(); break;
			case SEL_EDGE:   num += meshData->edgeSel.NumberSet(); break;			
			}
		}

	switch (selLevel) {
		case SEL_VERTEX:			
			if (num==1) buf = GetString(IDS_RB_NUMVERTSEL);
			else buf.printf(GetString(IDS_RB_NUMVERTSELP),num);
			break;

		case SEL_FACE:			
			if (num==1) buf = GetString(IDS_RB_NUMFACESEL);
			else buf.printf(GetString(IDS_RB_NUMFACESELP),num);
			break;

		case SEL_EDGE:			
			if (num==1) buf = GetString(IDS_RB_NUMEDGESEL);
			else buf.printf(GetString(IDS_RB_NUMEDGESELP),num);
			break;
		}
	
	SetDlgItemText(hParams,IDC_NUMSEL_LABEL,buf);
	}
