
/**********************************************************************
 *<
	FILE: editpat.cpp

	DESCRIPTION:  Edit Patch OSM

	CREATED BY: Tom Hudson, Dan Silva & Rolf Berteig

	HISTORY: created 23 June, 1995

 *>	Copyright (c) 1994, All Rights Reserved.
 **********************************************************************/
#include "mods.h"
#include "editpat.h"
#include "decomp.h"

#define DBGWELD_DUMPx
#define DBGWELD_ACTIONx

// Forward references
extern BOOL WeldPatch(PatchMesh *patch, float thresh, BOOL weldIdentical=FALSE, int startVert = 0);

// Our temporary prompts last 2 seconds:
#define PROMPT_TIME 2000

// in mods.cpp
extern HINSTANCE hInstance;

// Select by material parameters
static int sbmParams[2]     = {1,1};

HWND                EditPatchMod::hEditPatchParams = NULL;
IObjParam*          EditPatchMod::iObjParams      = NULL;
MoveModBoxCMode*    EditPatchMod::moveMode        = NULL;
RotateModBoxCMode*  EditPatchMod::rotMode 	      = NULL;
UScaleModBoxCMode*  EditPatchMod::uscaleMode      = NULL;
NUScaleModBoxCMode* EditPatchMod::nuscaleMode     = NULL;
SquashModBoxCMode *	EditPatchMod::squashMode      = NULL;
SelectModBoxCMode*  EditPatchMod::selectMode      = NULL;
ISpinnerControl*	EditPatchMod::weldSpin        = NULL;
ISpinnerControl*	EditPatchMod::stepsSpin       = NULL;
BOOL				EditPatchMod::settingViewportTess = FALSE;
ISpinnerControl*	EditPatchMod::uSpin           = NULL;
ISpinnerControl*	EditPatchMod::vSpin           = NULL;
ISpinnerControl*	EditPatchMod::edgeSpin        = NULL;
ISpinnerControl*	EditPatchMod::distSpin        = NULL;
ISpinnerControl*	EditPatchMod::angSpin         = NULL;
BOOL				EditPatchMod::patchUIValid    = TRUE;
PickPatchAttach		EditPatchMod::pickCB;

static float weldThreshold = 0.1f;

// Checkbox items for rollup pages
static int lockedHandles = 0;
static int patchDetachCopy = 0;
static int patchDetachReorient = 0;
static int attachReorient = 0;
static BOOL filterVerts = TRUE;
static BOOL filterVecs = TRUE;

static void SetVertFilter() {
	patchHitLevel[EP_VERTEX] = (filterVerts ? SUBHIT_PATCH_VERTS : 0) | (filterVecs ? SUBHIT_PATCH_VECS : 0);
	}

// This is a special override value which allows us to hit-test on
// any sub-part of a patch

int patchHitOverride = 0;	// If zero, no override is done

void SetPatchHitOverride(int value) {
	patchHitOverride = value;
	}

void ClearPatchHitOverride() {
	patchHitOverride = 0;
	}

/*-------------------------------------------------------------------*/

static
BOOL IsCompatible(BitArray &a, BitArray &b) {
	return (a.GetSize() == b.GetSize()) ? TRUE : FALSE;
	}


//--- Named Selection Set Methods ------------------------------------

void EditPatchMod::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;
			}
		}
	}

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

void EditPatchMod::AddSet(TSTR &setName,int level)
	{
	assert(level>0 && level<4);
	TSTR *name = new TSTR(setName);
	namedSel[level-1].Append(1,&name);
	}

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

static void AssignSetMatchSize(BitArray &dst, BitArray &src)
	{
	int size = dst.GetSize();
	dst = src;
	if (dst.GetSize() != size) {
		dst.SetSize(size,TRUE);
		}
	}

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

	iObjParams->GetModContexts(mcList,nodes);

	theHold.Begin();
	for (int i = 0; i < mcList.Count(); i++) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if (!patchData) continue;		
		patchData->StartChangeGroup();
		PatchMesh *mesh = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		patchData->BeginEdit(iObjParams->GetTime());

		switch (selLevel) {
			case EP_VERTEX:	{
				PVertSelRecord *rec = new PVertSelRecord();
				patchData->AddChangeRecord(rec);
				rec->oldSel = mesh->vertSel;
				if (theHold.Holding()) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}								
				AssignSetMatchSize(mesh->vertSel,patchData->vselSet[index]);				
				rec->newSel   = mesh->vertSel;
				break;
				}

			case EP_PATCH: {
				PatchSelRecord *rec = new PatchSelRecord();
				patchData->AddChangeRecord(rec);
				rec->oldSel = mesh->patchSel;
				if (theHold.Holding()) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				AssignSetMatchSize(mesh->patchSel,patchData->pselSet[index]);				
				rec->newSel    = mesh->patchSel;
				PatchSelChanged();
				break;
				}

			case EP_EDGE: {
				PEdgeSelRecord *rec = new PEdgeSelRecord();
				patchData->AddChangeRecord(rec);
				rec->oldSel = mesh->edgeSel;
				if (theHold.Holding()) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}	
				AssignSetMatchSize(mesh->edgeSel,patchData->eselSet[index]);				
				rec->newSel   = mesh->edgeSel;
				break;
				}
			}

		if (patchData->tempData) {
			patchData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
	
	theHold.Accept(GetString(IDS_DS_SELECT));
	nodes.DisposeTemporary();	
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime());
	}

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

	if (!iObjParams) return;
	
	iObjParams->GetModContexts(mcList,nodes);

	for (int i = 0; i < mcList.Count(); i++) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if (!patchData ) continue;		
		PatchMesh *mesh = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		patchData->BeginEdit(iObjParams->GetTime());
		
		switch (selLevel) {
			case EP_VERTEX:	
				if (index>=0) {
					patchData->vselSet[index] = mesh->vertSel;
				} else {
					patchData->vselSet.AppendSet(mesh->vertSel);
					}
				break;

			case EP_PATCH:
				if (index>=0) {
					patchData->pselSet[index] = mesh->patchSel;
				} else {
					patchData->pselSet.AppendSet(mesh->patchSel);
					}
				break;

			case EP_EDGE:
				if (index>=0) {
					patchData->eselSet[index] = mesh->edgeSel;
				} else {
					patchData->eselSet.AppendSet(mesh->edgeSel);
					}
				break;
			}		
		}	
	
	if (index<0) AddSet(setName,selLevel);		
	nodes.DisposeTemporary();
	}

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

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

	for (int i = 0; i < mcList.Count(); i++) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if (!patchData) continue;		
		PatchMesh *mesh = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		patchData->BeginEdit(iObjParams->GetTime());
		
		switch (selLevel) {
			case EP_VERTEX:	
				patchData->vselSet.DeleteSet(index);
				break;

			case EP_PATCH:
				patchData->pselSet.DeleteSet(index);
				break;

			case EP_EDGE:
				patchData->eselSet.DeleteSet(index);
				break;
			}

		if (patchData->tempData) {
			patchData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
	RemoveSet(setName,selLevel);
	iObjParams->ClearCurNamedSelSet();
	nodes.DisposeTemporary();
	}

void EditPatchMod::RemoveAllSets()
	{
	ModContextList mcList;
	INodeTab nodes;
	
	if (!iObjParams) return;	
	
	iObjParams->GetModContexts(mcList,nodes);

	for (int i=0; i < mcList.Count(); i++) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if (!patchData) continue;		
		
		int j;
		for (j=patchData->vselSet.Count()-1; j>=0; j--) {
			patchData->vselSet.DeleteSet(j);
			}
		for (j=patchData->pselSet.Count()-1; j>=0; j--) {
			patchData->pselSet.DeleteSet(j);
			}
		for (j=patchData->eselSet.Count()-1; j>=0; j--) {
			patchData->eselSet.DeleteSet(j);
			}		
		}	
	
	for (int j=0; j<3; j++) {
		for (int i=0; i<namedSel[j].Count(); i++) {
			delete namedSel[j][i];		
			}
		namedSel[j].Resize(0);
		}

	iObjParams->ClearCurNamedSelSet();
	iObjParams->ClearSubObjectNamedSelSets();
	nodes.DisposeTemporary();
	}


/*-------------------------------------------------------------------*/

// Deletes any vertices tagged, also any patches tagged.  Automatically deletes the vectors that
// are deleted as a result of the patch deletion and sweeps any vertices floating in space.

static void DeletePatchParts(PatchMesh *patch, BitArray &delVerts, BitArray &delPatches) {
	int patches = patch->getNumPatches();
	int verts = patch->getNumVerts();
	int vecs = patch->getNumVecs();
	int dest;

	// We treat vectors specially in order to clean up after welds.  First, we tag 'em all,
	// then untag only those on unselected patches so that any dangling vectors will be deleted.
	BitArray delVectors(vecs);
	delVectors.SetAll();

	// Untag vectors that are on nondeleted patches
	for(int i = 0; i < patches; ++i) {
		if(!delPatches[i]) {
			Patch& p = patch->patches[i];
			for(int j = 0; j < (p.type * 2); ++j) {
				delVectors.Clear(p.vec[j]);
				}
			for(j = 0; j < p.type; ++j)
				delVectors.Clear(p.interior[j]);
			}
		}

	// Make a table of vertices that are still in use -- Used to
	// delete those vertices which are floating, unused, in space.
	BitArray usedVerts(verts);
	usedVerts.ClearAll();
	for(i = 0; i < patches; ++i) {
		if(!delPatches[i]) {
			Patch& p = patch->patches[i];
			for(int j = 0; j < p.type; ++j) {
				usedVerts.Set(p.v[j]);
				}
			}
		}
	for(i = 0; i < verts; ++i) {
		if(!usedVerts[i])
			delVerts.Set(i);
		}

	// If we have texture vertices, handle them, too
	for(int chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
		int tverts = patch->numTVerts[chan];
		if(tverts && patch->tvPatches[chan]) {
			BitArray delTVerts(tverts);
			delTVerts.SetAll();
			for(i = 0; i < patches; ++i) {
				if(!delPatches[i]) {
					Patch& p = patch->patches[i];
					TVPatch& tp = patch->tvPatches[chan][i];
					for(int j = 0; j < p.type; ++j)
						delTVerts.Clear(tp.tv[j]);
					}
				}
			// Got the list of tverts to delete -- now delete 'em
			// Build a table of redirected texture vertex indices
			int newTVerts = tverts - delTVerts.NumberSet();
			IntTab tVertIndex;
			tVertIndex.SetCount(tverts);
			UVVert *newTVertArray = new UVVert[newTVerts];
			dest = 0;
			for(i = 0; i < tverts; ++i) {
				if(!delTVerts[i]) {
					newTVertArray[dest] = patch->tVerts[chan][i];
					tVertIndex[i] = dest++;
					}
				}
			delete[] patch->tVerts[chan];
			patch->tVerts[chan] = newTVertArray;
			patch->numTVerts[chan] = newTVerts;
			// Now, copy the untagged texture patches to a new array
			// While you're at it, redirect the vertex indices
			int newTVPatches = patches - delPatches.NumberSet();
			TVPatch *newArray = new TVPatch[newTVPatches];
			dest = 0;
			for(i = 0; i < patches; ++i) {
				if(!delPatches[i]) {
					Patch& p = patch->patches[i];
					TVPatch& tp = newArray[dest++];
					tp = patch->tvPatches[chan][i];
					for(int j = 0; j < p.type; ++j)
						tp.tv[j] = tVertIndex[tp.tv[j]];
					}
				}
			delete[] patch->tvPatches[chan];
			patch->tvPatches[chan] = newArray;;
			}
		}

	// Build a table of redirected vector indices
	IntTab vecIndex;
	vecIndex.SetCount(vecs);
	int newVectors = vecs - delVectors.NumberSet();
	PatchVec *newVecArray = new PatchVec[newVectors];
	dest = 0;
	for(i = 0; i < vecs; ++i) {
		if(!delVectors[i]) {
			newVecArray[dest] = patch->vecs[i];
			vecIndex[i] = dest++;
			}
		else
			vecIndex[i] = -1;
		}
	delete[] patch->vecs;
	patch->vecs = newVecArray;
	patch->numVecs = newVectors;

	// Build a table of redirected vertex indices
	int newVerts = verts - delVerts.NumberSet();
	IntTab vertIndex;
	vertIndex.SetCount(verts);
	PatchVert *newVertArray = new PatchVert[newVerts];
	BitArray newVertSel(newVerts);
	newVertSel.ClearAll();
	dest = 0;
	for(i = 0; i < verts; ++i) {
		if(!delVerts[i]) {
			newVertArray[dest] = patch->verts[i];
			newVertSel.Set(dest, patch->vertSel[i]);
			// redirect & adjust attached vector list
			PatchVert& v = newVertArray[dest];
			for(int j = 0; j < v.vectors.Count(); ++j) {
				v.vectors[j] = vecIndex[v.vectors[j]];
				if(v.vectors[j] < 0) {
					v.vectors.Delete(j, 1);
					j--;	// realign index
					}
				}
			vertIndex[i] = dest++;
			}
		}
	delete[] patch->verts;
	patch->verts = newVertArray;
	patch->numVerts = newVerts;
	patch->vertSel = newVertSel;

	// Now, copy the untagged patches to a new array
	// While you're at it, redirect the vertex and vector indices
	int newPatches = patches - delPatches.NumberSet();
	Patch *newArray = new Patch[newPatches];
	BitArray newPatchSel(newPatches);
	newPatchSel.ClearAll();
	dest = 0;
	for(i = 0; i < patches; ++i) {
		if(!delPatches[i]) {
			newArray[dest] = patch->patches[i];
			Patch& p = newArray[dest];
			for(int j = 0; j < p.type; ++j)
				p.v[j] = vertIndex[p.v[j]];
			for(j = 0; j < (p.type * 2); ++j)
				p.vec[j] = vecIndex[p.vec[j]];
			for(j = 0; j < p.type; ++j)
				p.interior[j] = vecIndex[p.interior[j]];
			newPatchSel.Set(dest++, patch->patchSel[i]);
			}
		}
	delete[] patch->patches;
	patch->patches = newArray;;
	patch->numPatches = newPatches;
	patch->patchSel.SetSize(newPatches,TRUE);
	patch->patchSel = newPatchSel;
	patch->buildLinkages();
	}

/*-------------------------------------------------------------------*/

// This function checks the current command mode and resets it to CID_OBJMOVE if
// it's one of our command modes

static
void CancelEditPatchModes(IObjParam *ip) {
	switch(ip->GetCommandMode()->ID()) {
		case CID_STDPICK:
			ip->SetStdCommandMode( CID_OBJMOVE );
			break;
		}
	}

// This gets rid of two-step modes, like booleans.  This is necessary because
// the first step, which activates the mode button, validates the selection set.
// If the selection set changes, the mode must be turned off because the new
// selection set may not be valid for the mode.
static
void Cancel2StepPatchModes(IObjParam *ip) {
//	switch(ip->GetCommandMode()->ID()) {
//		case CID_BOOLEAN:
//			ip->SetStdCommandMode( CID_OBJMOVE );
//			break;
//		}
	}

/*-------------------------------------------------------------------*/

static
TSTR detachName;

static
BOOL CALLBACK DetachDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
	TCHAR tempName[256];
	switch(message) {
		case WM_INITDIALOG:
			SetDlgItemText(hDlg, IDC_DETACH_NAME, detachName);
			SetFocus(GetDlgItem(hDlg, IDC_DETACH_NAME));
			return FALSE;
		case WM_COMMAND:
			switch(LOWORD(wParam)) {
				case IDOK:
					GetDlgItemText(hDlg, IDC_DETACH_NAME, tempName, 255);
					detachName = TSTR(tempName);
					EndDialog(hDlg, 1);
					return TRUE;
				case IDCANCEL:
					EndDialog(hDlg, 0);
					return TRUE;
			}
		}
	return FALSE;
	}

static
int GetDetachOptions(IObjParam *ip, TSTR& newName) {
	detachName = newName;
	ip->MakeNameUnique(detachName);	
	if(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DETACH), GetActiveWindow(), (DLGPROC)DetachDialogProc)==1) {
		newName = detachName;
		return 1;
		}
	return 0;
	}

/*-------------------------------------------------------------------*/

static EditPatchClassDesc editPatchDesc;
extern ClassDesc* GetEditPatchModDesc() { return &editPatchDesc; }

void EditPatchClassDesc::ResetClassParams(BOOL fileReset)
	{
	sbmParams[0]   = 1;
	sbmParams[1]   = 1;
	}

/*-------------------------------------------------------------------*/

PatchPointTab::PatchPointTab() {
	}

PatchPointTab::~PatchPointTab() {
	}

void PatchPointTab::Empty() {
	ptab.Delete(0, ptab.Count());
	vtab.Delete(0, vtab.Count());
	pttab.Delete(0, pttab.Count());
	}

void PatchPointTab::Zero() {
//DebugPrint("Zeroing\n");
	int points = ptab.Count();
	int vectors = vtab.Count();
	Point3 zero(0, 0, 0);

	for(int i = 0; i < points; ++i) {
		ptab[i] = zero;
		pttab[i] = 0;
		}
	for(i = 0; i < vectors; ++i)
		vtab[i] = zero;
	}

void PatchPointTab::MakeCompatible(PatchMesh& patch,int clear) {
	Point3 zero(0,0,0);
	int izero = 0;
	if(clear) {
		ptab.Delete(0, ptab.Count());
		pttab.Delete(0, pttab.Count());
		vtab.Delete(0, vtab.Count());
		}
	// First, the verts
	int size = patch.numVerts;
	if(ptab.Count() > size) {
		int diff = ptab.Count() - size;
		ptab.Delete( ptab.Count() - diff, diff );
		pttab.Delete( pttab.Count() - diff, diff );
		}
	if(ptab.Count() < size) {
		int diff = size - ptab.Count();
		ptab.Resize( size );
		pttab.Resize( size );
		for( int j = 0; j < diff; j++ ) {
			ptab.Append(1,&zero);
			pttab.Append(1,&izero);
			}
		}
	// Now, the vectors
	size = patch.numVecs;
	if(vtab.Count() > size) {
		int diff = vtab.Count() - size;
		vtab.Delete( vtab.Count() - diff, diff );
		}
	if(vtab.Count() < size) {
		int diff = size - vtab.Count();
		vtab.Resize( size );
		for( int j = 0; j < diff; j++ )
			vtab.Append(1,&zero);
		}
	}

PatchPointTab& PatchPointTab::operator=(PatchPointTab& from) {
	ptab = from.ptab;
	vtab = from.vtab;
	pttab = from.pttab;
	return *this;
	}

BOOL PatchPointTab::IsCompatible(PatchMesh &patch) {
	if(ptab.Count() != patch.numVerts)
		return FALSE;
	if(pttab.Count() != patch.numVerts)
		return FALSE;
	if(vtab.Count() != patch.numVecs)
		return FALSE;
	return TRUE;
	}

void PatchPointTab::RescaleWorldUnits(float f) {
	Matrix3 stm = ScaleMatrix(Point3(f, f, f));
	int points = ptab.Count();
	int vectors = vtab.Count();

	for(int i = 0; i < points; ++i)
		ptab[i] = ptab[i] * stm;
	for(i = 0; i < vectors; ++i)
		vtab[i] = vtab[i] * stm;
	}

#define PPT_VERT_CHUNK		0x1000
#define PPT_VEC_CHUNK		0x1010
#define PPT_VERTTYPE_CHUNK	0x1020

IOResult PatchPointTab::Save(ISave *isave) {	
	int i;
	ULONG nb;
	isave->BeginChunk(PPT_VERT_CHUNK);
	int count = ptab.Count();
	isave->Write(&count,sizeof(int),&nb);
	for(i = 0; i < count; ++i)
		isave->Write(&ptab[i],sizeof(Point3),&nb);
	isave->EndChunk();
	isave->BeginChunk(PPT_VERTTYPE_CHUNK);
	count = pttab.Count();
	isave->Write(&count,sizeof(int),&nb);
	for(i = 0; i < count; ++i)
		isave->Write(&pttab[i],sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(PPT_VEC_CHUNK);
	count = vtab.Count();
	isave->Write(&count,sizeof(int),&nb);
	for(i = 0; i < count; ++i)
		isave->Write(&vtab[i],sizeof(Point3),&nb);
	isave->EndChunk();
	return IO_OK;
	}

IOResult PatchPointTab::Load(ILoad *iload) {	
	int i, count;
	Point3 workpt;
	int workint;
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case PPT_VERT_CHUNK:
				ptab.Delete(0,ptab.Count());
				iload->Read(&count,sizeof(int),&nb);
				for(i = 0; i < count; ++i) {
					iload->Read(&workpt,sizeof(Point3),&nb);
					ptab.Append(1,&workpt);
					}
				break;
			case PPT_VERTTYPE_CHUNK:
				pttab.Delete(0,pttab.Count());
				iload->Read(&count,sizeof(int),&nb);
				for(i = 0; i < count; ++i) {
					iload->Read(&workint,sizeof(int),&nb);
					pttab.Append(1,&workint);
					}
				break;
			case PPT_VEC_CHUNK:
				vtab.Delete(0,vtab.Count());
				iload->Read(&count,sizeof(int),&nb);
				for(i = 0; i < count; ++i) {
					iload->Read(&workpt,sizeof(Point3),&nb);
					vtab.Append(1,&workpt);
					}
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/

void PatchVertexDelta::SetSize(PatchMesh& patch, BOOL load)
	{
	dtab.MakeCompatible(patch, FALSE);
	
	// Load it if necessary
	if(load) {
		int verts = patch.numVerts;
		int vecs = patch.numVecs;
		for(int i = 0; i < verts; ++i) {
			dtab.ptab[i] = patch.verts[i].p;
			dtab.pttab[i] = patch.verts[i].flags & PVERT_COPLANAR;
			}
		for(i = 0; i < vecs; ++i)
			dtab.vtab[i] = patch.vecs[i].p;
		}
	}

void PatchVertexDelta::Apply(PatchMesh &patch)
	{
//DebugPrint(_T("PVD:Applying\n"));
	// This does nothing if the number of verts hasn't changed in the mesh.
	SetSize(patch, FALSE);

	// Apply the deltas
	int verts = patch.numVerts;
	int vecs = patch.numVecs;
	for(int i = 0; i < verts; ++i) {
		patch.verts[i].p += dtab.ptab[i];
		patch.verts[i].flags ^= dtab.pttab[i];
		}
	for(i = 0; i < vecs; ++i) {
		patch.vecs[i].p += dtab.vtab[i];
		}
	patch.computeInteriors();
	}

void PatchVertexDelta::UnApply(PatchMesh &patch)
	{
//DebugPrint(_T("PVD:UnApplying\n"));
	// This does nothing if the number of verts hasn't changed in the mesh.
	SetSize(patch, FALSE);

	// Apply the deltas
	int verts = patch.numVerts;
	int vecs = patch.numVecs;
	for(int i = 0; i < verts; ++i) {
		patch.verts[i].p -= dtab.ptab[i];
		patch.verts[i].flags ^= dtab.pttab[i];
		}
	for(i = 0; i < vecs; ++i) {
		patch.vecs[i].p -= dtab.vtab[i];
		}
	patch.computeInteriors();
	}

// This function applies the current changes to slave handles and their knots, and zeroes everything else
void PatchVertexDelta::ApplyHandlesAndZero(PatchMesh &patch, int handleVert) {
//DebugPrint(_T("PVD:ApplyAndZero\n"));
	// This does nothing if the number of verts hasn't changed in the mesh.
	SetSize(patch, FALSE);

	Point3 zeroPt(0.0f, 0.0f, 0.0f);

	// Apply the deltas	to just the slave handles
	int verts = patch.numVerts;
	int vecs = patch.numVecs;
	Point3Tab& delta = dtab.vtab;
	IntTab& kdelta = dtab.pttab;
	for(int i = 0; i < vecs; ++i) {
		if(!(delta[i] == zeroPt)) {
			if(i != handleVert)
				patch.vecs[i].p += delta[i];
			else
				delta[i] = zeroPt;
			}
		}

	for(i = 0; i < verts; ++i) {
		if(kdelta[i])
			patch.verts[i].flags ^= kdelta[i];
		}
	}


#define PVD_POINTTAB_CHUNK		0x1000

IOResult PatchVertexDelta::Save(ISave *isave) {
	isave->BeginChunk(PVD_POINTTAB_CHUNK);
	dtab.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PatchVertexDelta::Load(ILoad *iload) {
	IOResult res;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case PVD_POINTTAB_CHUNK:
				res = dtab.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/

EditPatchData::EditPatchData(EditPatchMod *mod)
	{
	meshSteps = mod->meshSteps;
//	meshAdaptive = mod->meshAdaptive;	// Future use (Not used now)
	viewTess = mod->viewTess;
	prodTess = mod->prodTess;
	displayLattice = mod->displayLattice;
	displaySurface = mod->displaySurface;
	flags = 0;
	tempData = NULL;
	changeGroup = -1;
	changeSerial = -1;
	}

EditPatchData::EditPatchData(EditPatchData& emc)
	{
	meshSteps = emc.meshSteps;
//	meshAdaptive = emc.meshAdaptive;	// Future use (Not used now)
	viewTess = emc.viewTess;
	prodTess = emc.prodTess;
	displayLattice = emc.displayLattice;
	displaySurface = emc.displaySurface;
	flags = emc.flags;
	tempData = NULL;
	changeGroup = emc.changeGroup;
	changeSerial = emc.changeSerial;
	// Now we must copy all the change data from the source EditPatchData object
	for(int i = 0; i < emc.changes.Count(); ++i) {
		PModRecord *ptr = emc.changes[i]->Clone();
		changes.Append(1, &ptr);
		}
	}


void EditPatchData::Apply(TimeValue t,PatchObject *patchOb,int selLevel)
	{
	// Either just copy it from the existing cache or rebuild from previous level!
	if ( !GetFlag(EPD_UPDATING_CACHE) && tempData 
	      && tempData->PatchCached(t) ) {
		patchOb->patch.DeepCopy( 
			tempData->GetPatch(t),
			PART_GEOM|SELECT_CHANNEL|PART_SUBSEL_TYPE|
			PART_DISPLAY|PART_TOPO|TEXMAP_CHANNEL );		
		patchOb->PointsWereChanged();
		}	
	else if ( GetFlag(EPD_HASDATA) ) {
		// We have to (Ugh!) replay all our editing events!  Potential performance problem!
		int count = changes.Count();
		for(int i = 0; i < count; ++i) {
			PModRecord *rec = changes[i];
			// If we hit one that didn't play back OK, we need to flush the remainder
			if(!rec->Redo(&patchOb->patch,1)) {
DebugPrint("Flushing >= change %d\n",i);
				for(int j = i; j < count; ++j)
					delete changes[j];
				changes.Delete(i, count - i);
				break;
				}
			}
		patchOb->PointsWereChanged();

		// Kind of a waste when there's no animation...		
		patchOb->UpdateValidity(GEOM_CHAN_NUM,FOREVER);
		patchOb->UpdateValidity(TOPO_CHAN_NUM,FOREVER);
		patchOb->UpdateValidity(SELECT_CHAN_NUM,FOREVER);
		patchOb->UpdateValidity(SUBSEL_TYPE_CHAN_NUM,FOREVER);
		patchOb->UpdateValidity(DISP_ATTRIB_CHAN_NUM,FOREVER);		
		}

	// Hand it its mesh interpolation info
	patchOb->SetMeshSteps(meshSteps);
//	patchOb->SetAdaptive(meshAdaptive);	// Future use (Not used now)
	patchOb->SetViewTess(viewTess);
	patchOb->SetProdTess(prodTess);
	patchOb->showMesh = displaySurface;
	patchOb->showLattice = displayLattice;
	patchOb->patch.dispFlags = displayLattice ? DISP_LATTICE : 0;
	switch ( selLevel ) {
		case EP_PATCH:
			patchOb->patch.SetDispFlag(DISP_SELPATCHES);
			break;
		case EP_EDGE:
			patchOb->patch.SetDispFlag(DISP_SELEDGES);
			break;
		case EP_VERTEX:
			patchOb->patch.SetDispFlag(DISP_VERTTICKS|DISP_SELVERTS|DISP_VERTS);
			break;
		}
	patchOb->patch.selLevel = patchLevel[selLevel];
	
	if ( GetFlag(EPD_UPDATING_CACHE) ) {
		assert(tempData);
		tempData->UpdateCache(patchOb);
		SetFlag(EPD_UPDATING_CACHE,FALSE);
		}		
	}

void EditPatchData::Invalidate(PartID part,BOOL patchValid)
	{
	if ( tempData ) {
		tempData->Invalidate(part,patchValid);
		}
	}

void EditPatchData::BeginEdit(TimeValue t)
	{
	assert(tempData);
	if ( !GetFlag(EPD_HASDATA) ) {
		PatchMesh *patch = tempData->GetPatch(t);
		SetFlag(EPD_HASDATA,TRUE);
		}
	}

EPTempData *EditPatchData::TempData(EditPatchMod *mod)
	{
	if ( !tempData ) {
		assert(mod->iObjParams);
		tempData = new EPTempData(mod,this);
		}
	return tempData;
	}

// Start a new group of change records.  More than one change record may be necessary
// to perform one modification to an object.  This allows you to group multiple records
// for a single change operation.
int EditPatchData::StartChangeGroup() {
	changeGroup++;
	changeSerial = 0;
//DebugPrint(_T("Started change group %d\n"),changeGroup);
	return 1;
	}

// Add a change record to the list in the modifier
int EditPatchData::AddChangeRecord(PModRecord *ptr) {
	// Serialize this change record
	ptr->groupNumber = changeGroup;
	ptr->serialNumber = changeSerial++;
	changes.Append(1,&ptr);
	return 1;
	}

// Add a change record to the list in the modifier but keep its existing serialization
int EditPatchData::AdoptChangeRecord(PModRecord *ptr) {
	changes.Append(1,&ptr);
	return 1;
	}

// Remove the last change record -- Returns 1 if more remain in this group
int EditPatchData::RemoveChangeRecord() {
	int index = changes.Count() - 1;
	int more = (changes[index]->serialNumber == 0) ? 0 : 1;
	changes.Delete(index,1);
	return more;
	}

// Remove the last change group 
int EditPatchData::RemoveChangeGroup() {
	while(RemoveChangeRecord());
	return 1;
	}

int EditPatchData::DeleteChangeRecord() {
	int index = changes.Count() - 1;
	int more = (changes[index]->serialNumber == 0) ? 0 : 1;
	delete changes[index];
	changes.Delete(index,1);
	return more;
	}

int EditPatchData::DeleteChangeGroup() {
	if(!changes.Count())
		return 0;
	while(DeleteChangeRecord());
	return 1;
	}

int EditPatchData::ResetChanges() {
	while(DeleteChangeGroup());
	changeGroup = -1;
	changeSerial = -1;
	return 1;
	}

int EditPatchData::FindChangeGroup(int group) {
	int count = changes.Count();
	for(int i = 0; i < count; ++i) {
		if(changes[i]->groupNumber == group)
			return i;
		}
	return -1;	// ERROR!  Couldn't find group
	}

void EditPatchData::RescaleWorldUnits(float f) {
	int count = changes.Count();
	for(int i = 0; i < count; ++i)
		changes[i]->RescaleWorldUnits(f);
	}

#define EPD_GENERAL_CHUNK	0x1000
#define CHANGE_CHUNK		0x1010
#define MESH_ATTRIB_CHUNK	0x1020
#define DISP_PARTS_CHUNK	0x1030
#define VTESS_ATTRIB_CHUNK	0x1070
#define PTESS_ATTRIB_CHUNK	0x1080

// Named sel set chunks
#define VSELSET_CHUNK		0x1040
#define ESELSET_CHUNK		0x1050
#define PSELSET_CHUNK		0x1060


IOResult EditPatchData::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(EPD_GENERAL_CHUNK);
	isave->Write(&flags,sizeof(DWORD),&nb);
	isave->Write(&changeGroup,sizeof(int),&nb);
	isave->Write(&changeSerial,sizeof(int),&nb);
	int chCount = changes.Count();
	isave->Write(&chCount,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(MESH_ATTRIB_CHUNK);
	isave->Write(&meshSteps,sizeof(int),&nb);
// Future use (Not used now)
	BOOL fakeAdaptive = FALSE;	
	isave->Write(&fakeAdaptive,sizeof(BOOL),&nb);
//	isave->Write(&meshAdaptive,sizeof(BOOL),&nb);	// Future use (Not used now)
	isave->EndChunk();
	isave->BeginChunk(VTESS_ATTRIB_CHUNK);
	viewTess.Save(isave);
	isave->EndChunk();
	isave->BeginChunk(PTESS_ATTRIB_CHUNK);
	prodTess.Save(isave);
	isave->EndChunk();
	isave->BeginChunk(DISP_PARTS_CHUNK);
	isave->Write(&displaySurface,sizeof(BOOL),&nb);
	isave->Write(&displayLattice,sizeof(BOOL),&nb);
	isave->EndChunk();
	
	// Save named sel sets
	if (vselSet.Count()) {
		isave->BeginChunk(VSELSET_CHUNK);
		vselSet.Save(isave);
		isave->EndChunk();
		}
	if (eselSet.Count()) {
		isave->BeginChunk(ESELSET_CHUNK);
		eselSet.Save(isave);
		isave->EndChunk();
		}
	if (pselSet.Count()) {
		isave->BeginChunk(PSELSET_CHUNK);
		pselSet.Save(isave);
		isave->EndChunk();
		}

	for(int i = 0; i < chCount; ++i) {
		isave->BeginChunk(changes[i]->ChunkID());
		changes[i]->Save(isave);
		isave->	EndChunk();
		}

	return IO_OK;
	}

IOResult EditPatchData::Load(ILoad *iload) {
	static int chCount;
	IOResult res;
	ULONG nb;
	PModRecord *theChange;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			
			// Load named selection sets
			case VSELSET_CHUNK:
				res = vselSet.Load(iload);
				break;
			case PSELSET_CHUNK:
				res = pselSet.Load(iload);
				break;
			case ESELSET_CHUNK:
				res = eselSet.Load(iload);
				break;

			case EPD_GENERAL_CHUNK:
				iload->Read(&flags,sizeof(DWORD),&nb);
				iload->Read(&changeGroup,sizeof(int),&nb);
				iload->Read(&changeSerial,sizeof(int),&nb);
				res = iload->Read(&chCount,sizeof(int),&nb);
				break;
			case MESH_ATTRIB_CHUNK:
				iload->Read(&meshSteps,sizeof(int),&nb);
				res = iload->Read(&meshAdaptive,sizeof(BOOL),&nb);	// Future use (Not used now)
				break;
			case VTESS_ATTRIB_CHUNK:
				viewTess.Load(iload);
				break;
			case PTESS_ATTRIB_CHUNK:
				prodTess.Load(iload);
				break;
			case DISP_PARTS_CHUNK:
				iload->Read(&displaySurface,sizeof(BOOL),&nb);
				res = iload->Read(&displayLattice,sizeof(BOOL),&nb);
				break;
			case CLEARVERTSELRECORD_CHUNK:
				theChange = new ClearPVertSelRecord;
				goto load_change;
			case SETVERTSELRECORD_CHUNK:
				theChange = new SetPVertSelRecord;
				goto load_change;
			case INVERTVERTSELRECORD_CHUNK:
				theChange = new InvertPVertSelRecord;
				goto load_change;
			case CLEAREDGESELRECORD_CHUNK:
				theChange = new ClearPEdgeSelRecord;
				goto load_change;
			case SETEDGESELRECORD_CHUNK:
				theChange = new SetPEdgeSelRecord;
				goto load_change;
			case INVERTEDGESELRECORD_CHUNK:
				theChange = new InvertPEdgeSelRecord;
				goto load_change;
			case CLEARPATCHSELRECORD_CHUNK:
				theChange = new ClearPatchSelRecord;
				goto load_change;
			case SETPATCHSELRECORD_CHUNK:
				theChange = new SetPatchSelRecord;
				goto load_change;
			case INVERTPATCHSELRECORD_CHUNK:
				theChange = new InvertPatchSelRecord;
				goto load_change;
			case VERTSELRECORD_CHUNK:
				theChange = new PVertSelRecord;
				goto load_change;
			case EDGESELRECORD_CHUNK:
				theChange = new PEdgeSelRecord;
				goto load_change;
			case PATCHSELRECORD_CHUNK:
				theChange = new PatchSelRecord;
				goto load_change;
			case PATCHDELETERECORD_CHUNK:
				theChange = new PatchDeleteRecord;
				goto load_change;
			case VERTMOVERECORD_CHUNK:
				theChange = new PVertMoveRecord;
				goto load_change;
			case PATCHCHANGERECORD_CHUNK:
				theChange = new PatchChangeRecord;
				goto load_change;
			case VERTCHANGERECORD_CHUNK:
				theChange = new PVertChangeRecord;
				goto load_change;
			case PATCHADDRECORD_CHUNK:
				theChange = new PatchAddRecord;
				goto load_change;
			case EDGESUBDIVIDERECORD_CHUNK:
				theChange = new EdgeSubdivideRecord;
				goto load_change;
			case PATCHSUBDIVIDERECORD_CHUNK:
				theChange = new PatchSubdivideRecord;
				goto load_change;
			case PATTACHRECORD_CHUNK:
				theChange = new PAttachRecord;
				goto load_change;
			case PATCHDETACHRECORD_CHUNK:
				theChange = new PatchDetachRecord;
				goto load_change;
			case PATCHMTLRECORD_CHUNK:
				theChange = new PatchMtlRecord;
				goto load_change;
			case VERTWELDRECORD_CHUNK:
				theChange = new PVertWeldRecord;
				goto load_change;
			case VERTDELETERECORD_CHUNK:
				theChange = new PVertDeleteRecord;
				// Intentional fall-thru!
				load_change:
				changes.Append(1,&theChange);
				changes[changes.Count()-1]->Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

// Each modification record must save and load this chunk!
#define MODREC_GENERAL_CHUNK		0x0100

/*-------------------------------------------------------------------*/		

BOOL ClearPVertSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->vertSel, sel))
		return FALSE;
	patch->vertSel = sel;
	return TRUE;
	}

BOOL ClearPVertSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		sel = patch->vertSel;
	patch->vertSel.ClearAll();
	return TRUE;
	}

#define CVSR_SEL_CHUNK 0x1000

IOResult ClearPVertSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(CVSR_SEL_CHUNK);
	sel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult ClearPVertSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case CVSR_SEL_CHUNK:
				res = sel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL SetPVertSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->vertSel, sel))
		return FALSE;
	patch->vertSel = sel;
	return TRUE;
	}

BOOL SetPVertSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		sel = patch->vertSel;
	patch->vertSel.SetAll();
	return TRUE;
	}

#define SVSR_SEL_CHUNK 0x1000

IOResult SetPVertSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(SVSR_SEL_CHUNK);
	sel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SetPVertSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case SVSR_SEL_CHUNK:
				res = sel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL InvertPVertSelRecord::Undo(PatchMesh *patch) {
	patch->vertSel = ~patch->vertSel;
	return TRUE;
	}

BOOL InvertPVertSelRecord::Redo(PatchMesh *patch,int reRecord) {
	patch->vertSel = ~patch->vertSel;
	return TRUE;
	}

IOResult InvertPVertSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult InvertPVertSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL ClearPEdgeSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->edgeSel, sel))
		return FALSE;
	patch->edgeSel = sel;
	return TRUE;
	}

BOOL ClearPEdgeSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		sel = patch->edgeSel;
	patch->edgeSel.ClearAll();
	return TRUE;
	}

#define CESR_SEL_CHUNK 0x1000

IOResult ClearPEdgeSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(CESR_SEL_CHUNK);
	sel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult ClearPEdgeSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case CESR_SEL_CHUNK:
				res = sel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL SetPEdgeSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->edgeSel, sel))
		return FALSE;
	patch->edgeSel = sel;
	return TRUE;
	}

BOOL SetPEdgeSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		sel = patch->edgeSel;
	patch->edgeSel.SetAll();
	return TRUE;
	}

#define SESR_SEL_CHUNK 0x1000

IOResult SetPEdgeSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(SESR_SEL_CHUNK);
	sel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SetPEdgeSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case SESR_SEL_CHUNK:
				res = sel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL InvertPEdgeSelRecord::Undo(PatchMesh *patch) {
	patch->edgeSel = ~patch->edgeSel;
	return TRUE;
	}

BOOL InvertPEdgeSelRecord::Redo(PatchMesh *patch,int reRecord) {
	patch->edgeSel = ~patch->edgeSel;
	return TRUE;
	}

IOResult InvertPEdgeSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult InvertPEdgeSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL ClearPatchSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->patchSel, sel))
		return FALSE;
	patch->patchSel = sel;
	return TRUE;
	}

BOOL ClearPatchSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		sel = patch->patchSel;
	patch->patchSel.ClearAll();
	return TRUE;
	}

#define CPSR_SEL_CHUNK 0x1000

IOResult ClearPatchSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(CPSR_SEL_CHUNK);
	sel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult ClearPatchSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case CPSR_SEL_CHUNK:
				res = sel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL SetPatchSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->patchSel, sel))
		return FALSE;
	patch->patchSel = sel;
	return TRUE;
	}

BOOL SetPatchSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		sel = patch->patchSel;
	patch->patchSel.SetAll();
	return TRUE;
	}

#define SPSR_SEL_CHUNK 0x1000

IOResult SetPatchSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(SPSR_SEL_CHUNK);
	sel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SetPatchSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case SPSR_SEL_CHUNK:
				res = sel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL InvertPatchSelRecord::Undo(PatchMesh *patch) {
	patch->patchSel = ~patch->patchSel;
	return TRUE;
	}

BOOL InvertPatchSelRecord::Redo(PatchMesh *patch,int reRecord) {
	patch->patchSel = ~patch->patchSel;
	return TRUE;
	}

IOResult InvertPatchSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult InvertPatchSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL PVertSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->vertSel, oldSel))
		return FALSE;
	patch->vertSel = oldSel;
	return TRUE;
	}

BOOL PVertSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(!IsCompatible(patch->vertSel, newSel))
		return FALSE;
	patch->vertSel = newSel;
	return TRUE;
	}

#define VSR_OLDSEL_CHUNK 0x1000
#define VSR_NEWSEL_CHUNK 0x1010

IOResult PVertSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(VSR_OLDSEL_CHUNK);
	oldSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VSR_NEWSEL_CHUNK);
	newSel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PVertSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case VSR_OLDSEL_CHUNK:
				res = oldSel.Load(iload);
				break;
			case VSR_NEWSEL_CHUNK:
				res = newSel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL PEdgeSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->edgeSel, oldSel))
		return FALSE;
	patch->edgeSel = oldSel;
	return TRUE;
	}

BOOL PEdgeSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(!IsCompatible(patch->edgeSel, newSel))
		return FALSE;
	patch->edgeSel = newSel;
	return TRUE;
	}

#define ESR_OLDSEL_CHUNK 0x1000
#define ESR_NEWSEL_CHUNK 0x1010

IOResult PEdgeSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(ESR_OLDSEL_CHUNK);
	oldSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(ESR_NEWSEL_CHUNK);
	newSel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PEdgeSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case ESR_OLDSEL_CHUNK:
				res = oldSel.Load(iload);
				break;
			case ESR_NEWSEL_CHUNK:
				res = newSel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL PatchSelRecord::Undo(PatchMesh *patch) {
	if(!IsCompatible(patch->patchSel, oldSel))
		return FALSE;
	patch->patchSel = oldSel;
	return TRUE;
	}

BOOL PatchSelRecord::Redo(PatchMesh *patch,int reRecord) {
	if(!IsCompatible(patch->patchSel, newSel))
		return FALSE;
	patch->patchSel = newSel;
	return TRUE;
	}

#define PSR_OLDSEL_CHUNK 0x1000
#define PSR_NEWSEL_CHUNK 0x1010

IOResult PatchSelRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PSR_OLDSEL_CHUNK);
	oldSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PSR_NEWSEL_CHUNK);
	newSel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PatchSelRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case PSR_OLDSEL_CHUNK:
				res = oldSel.Load(iload);
				break;
			case PSR_NEWSEL_CHUNK:
				res = newSel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PatchDeleteRecord::PatchDeleteRecord(PatchMesh *patch) : PModRecord() {
	oldPatch = *patch;
	}

BOOL PatchDeleteRecord::Undo(PatchMesh *patch) {
	*patch = oldPatch;
	return TRUE;
	}

static void DeleteSelPatches(PatchMesh *patch) {
	if(!patch->patchSel.NumberSet())
		return;		// Nothing to do!

	int patches = patch->getNumPatches();
	int verts = patch->getNumVerts();

	// Tag the patches that are selected
	BitArray delPatches(patches);
	delPatches = patch->patchSel;

	BitArray delVerts(verts);
	delVerts.ClearAll();

	DeletePatchParts(patch, delVerts, delPatches);
	patch->computeInteriors();
	}

BOOL PatchDeleteRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		oldPatch = *patch;
	DeleteSelPatches(patch);
	return TRUE;
	}

#define PDELR_PATCH_CHUNK		0x1060

IOResult PatchDeleteRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PDELR_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PatchDeleteRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case PDELR_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

BOOL PVertMoveRecord::Undo(PatchMesh *patch) {
	if(!delta.IsCompatible(*patch))
		return FALSE;
	delta.UnApply(*patch);
	return TRUE;
	}

BOOL PVertMoveRecord::Redo(PatchMesh *patch,int reRecord) {
	if(!delta.IsCompatible(*patch))
		return FALSE;
	delta.Apply(*patch);
	return TRUE;
	}

#define VMR_DELTA_CHUNK		0x1000

IOResult PVertMoveRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(VMR_DELTA_CHUNK);
	delta.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PVertMoveRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case VMR_DELTA_CHUNK:
				res = delta.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PVertDeleteRecord::PVertDeleteRecord(PatchMesh *patch) : PModRecord() {
	oldPatch = *patch;
	}

BOOL PVertDeleteRecord::Undo(PatchMesh *patch) {
	*patch = oldPatch;
	return TRUE;
	}

static void DeleteSelVerts(PatchMesh *patch) {
	if(!patch->vertSel.NumberSet())
		return;		// Nothing to do!

	int patches = patch->getNumPatches();
	int verts = patch->getNumVerts();

	// Tag the patches that use selected vertices
	BitArray delPatches(patches);
	delPatches.ClearAll();
	for(int i = 0; i < patches; ++i) {
		Patch& p = patch->patches[i];
		for(int j = 0; j < p.type; ++j) {
			if(patch->vertSel[p.v[j]]) {
				delPatches.Set(i);
				goto next_patch;
				}
			}
		next_patch:;
		}

	BitArray delVerts(verts);
	delVerts = patch->vertSel;
	DeletePatchParts(patch, delVerts, delPatches);
	patch->computeInteriors();
	}

BOOL PVertDeleteRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		oldPatch = *patch;
	DeleteSelVerts(patch);
	return TRUE;
	}

#define VDELR_PATCH_CHUNK		0x1060

IOResult PVertDeleteRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(VDELR_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PVertDeleteRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case VDELR_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PVertChangeRecord::PVertChangeRecord(PatchMesh *patch, int vertex, int type) : PModRecord() {
	oldPatch = *patch;
	this->index = vertex;
	this->type = type;
	}

BOOL PVertChangeRecord::Undo(PatchMesh *patch) {
	*patch = oldPatch;
	return TRUE;
	}

static void ChangeVertType(PatchMesh *patch, int index, int type) {
	// If positive vertex number, do it to just one vertex
	if(index >= 0) {
		patch->verts[index].flags = type;
		patch->ApplyConstraints();
		return;
		}

	// Otherwise, do it to all selected vertices!
	int verts = patch->numVerts;
	BitArray &vsel = patch->vertSel;
	for(int i = 0; i < verts; ++i) {
		if(vsel[i]) {
			patch->verts[i].flags = type;
			patch->ApplyConstraints(TRUE);
			}
		}
	}

BOOL PVertChangeRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		oldPatch = *patch;
	ChangeVertType(patch, index, type);
	return TRUE;
	}

#define VCHG_GENERAL_CHUNK		0x1001
#define VCHG_PATCH_CHUNK		0x1010

IOResult PVertChangeRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(VCHG_GENERAL_CHUNK);
	isave->Write(&index,sizeof(int),&nb);
	isave->Write(&type,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(VCHG_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->EndChunk();
	return IO_OK;
	}

IOResult PVertChangeRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case VCHG_GENERAL_CHUNK:
				res = iload->Read(&index,sizeof(int),&nb);
				res = iload->Read(&type,sizeof(int),&nb);
				break;
			case VCHG_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PAttachRecord::PAttachRecord(PatchMesh *patch, PatchMesh *attPatch, int mtlOffset) : PModRecord() {
	this->attPatch = *attPatch;
	oldPatchCount = patch->numPatches;
	this->mtlOffset = mtlOffset;
	}

BOOL PAttachRecord::Undo(PatchMesh *patch) {
	// Get rid of all the new patches we attached...
	BitArray delVerts(patch->numVerts);
	delVerts.ClearAll();
	BitArray delPatches(patch->numPatches);
	delPatches.ClearAll();
	for(int i = oldPatchCount; i < patch->numPatches; ++i)
		delPatches.Set(i);
	DeletePatchParts(patch, delVerts, delPatches);
	return TRUE;
	}

static void MakeDummyTVPatches(int channel, PatchMesh *patch) {
	patch->setNumTVertsChannel(channel, 1);
	patch->tVerts[channel][0] = UVVert(0,0,0);
	patch->setNumTVPatchesChannel(channel, patch->numPatches);
	for(int i = 0; i < patch->numPatches; ++i) {
		Patch &p = patch->patches[i];
		TVPatch &tp = patch->tvPatches[channel][i];
		tp.Init();	// Sets all indices to zero
		}
	}

static void DoAttach(PatchMesh *patch, PatchMesh *attPatch, int mtlOffset) {
	// Make sure they match in terms of texture info
	for(int chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {	
		if(patch->tvPatches[chan] || attPatch->tvPatches[chan]) {
			if(!patch->tvPatches[chan])
				MakeDummyTVPatches(chan, patch);
			if(!attPatch->tvPatches[chan])
				MakeDummyTVPatches(chan, attPatch);
			}
		}
	// Expand the number of verts
	int oldVerts = patch->numVerts;
	int oldVecs = patch->numVecs;
	int oldPatches = patch->numPatches;
	int newVerts = oldVerts + attPatch->numVerts;
	int newVecs = oldVecs + attPatch->numVecs;
	int newPatches = oldPatches + attPatch->numPatches;
	patch->setNumVerts(newVerts, TRUE);
	patch->setNumVecs(newVecs, TRUE);
	patch->setNumPatches(newPatches, TRUE);
	for(int i = 0, i2 = oldVerts; i < attPatch->numVerts; ++i, ++i2)
		patch->verts[i2] = attPatch->verts[i];
	for(i = 0, i2 = oldVecs; i < attPatch->numVecs; ++i, ++i2)
		patch->vecs[i2] = attPatch->vecs[i];
	for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
		int oldTVerts = patch->numTVerts[chan];
		int newTVerts = oldTVerts + attPatch->numTVerts[chan];
		patch->setNumTVertsChannel(chan, newTVerts, TRUE);
		for(i = 0, i2 = oldTVerts; i < attPatch->numTVerts[chan]; ++i, ++i2)
			patch->tVerts[chan][i2] = attPatch->tVerts[chan][i];
		if(patch->tvPatches[chan]) {
			for(i = 0, i2 = oldPatches; i < attPatch->numPatches; ++i, ++i2) {
				Patch &p = attPatch->patches[i];
				TVPatch &tp = attPatch->tvPatches[chan][i];
				TVPatch &tp2 = patch->tvPatches[chan][i2];
				tp2 = tp;
				for(int j = 0; j < p.type; ++j)	// Adjust vertices and interior vectors
					tp2.tv[j] += oldTVerts;
				}
			}
		}
	for(i = 0, i2 = oldPatches; i < attPatch->numPatches; ++i, ++i2) {
		Patch &p = attPatch->patches[i];
		Patch &p2 = patch->patches[i2];
		p2 = p;
		for(int j = 0; j < p2.type; ++j) {	// Adjust vertices and interior vectors
			p2.v[j] += oldVerts;
			p2.interior[j] += oldVecs;
			}
		for(j = 0; j < (p2.type * 2); ++j)	// Adjust edge vectors
			p2.vec[j] += oldVecs;
		// Adjust mat indices
		p2.setMatID(p.getMatID() + mtlOffset);
		}
	patch->computeInteriors();
	patch->buildLinkages();
	patch->vertSel.SetSize(newVerts, TRUE);
	patch->patchSel.SetSize(newPatches, TRUE);
	}

BOOL PAttachRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		oldPatchCount = patch->numPatches;
	DoAttach(patch, &attPatch, mtlOffset);
	return TRUE;
	}

#define ATTR_GENERAL_CHUNK		0x1001
#define ATTR_ATTPATCH_CHUNK		0x1010
#define ATTR_MTLOFFSET_CHUNK	0x1020

IOResult PAttachRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(ATTR_GENERAL_CHUNK);
	isave->Write(&oldPatchCount,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(ATTR_ATTPATCH_CHUNK);
	attPatch.Save(isave);
	isave->EndChunk();
	isave->BeginChunk(ATTR_MTLOFFSET_CHUNK);
	isave->Write(&mtlOffset,sizeof(int),&nb);
	isave->EndChunk();
	return IO_OK;
	}

IOResult PAttachRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case ATTR_GENERAL_CHUNK:
				res = iload->Read(&oldPatchCount,sizeof(int),&nb);
				break;
			case ATTR_ATTPATCH_CHUNK:
				res = attPatch.Load(iload);
				break;
			case ATTR_MTLOFFSET_CHUNK:
				res = iload->Read(&mtlOffset,sizeof(int),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PatchDetachRecord::PatchDetachRecord(PatchMesh *patch, int copy) : PModRecord() {
	this->copy = copy;
	if(!copy)
		oldPatch = *patch;
	}

BOOL PatchDetachRecord::Undo(PatchMesh *patch) {
	if(!copy) {
		*patch = oldPatch;
		}
	return TRUE;
	}

BOOL PatchDetachRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord && !copy)
		oldPatch = *patch;
	if(!copy) {
		BitArray vdel(patch->numVerts);
		vdel.ClearAll();
		BitArray pdel = patch->patchSel;
		DeletePatchParts(patch, vdel, pdel);
		}
	return TRUE;
	}

#define PDETR_GENERAL_CHUNK		0x1000
#define PDETR_PATCH_CHUNK		0x1030

IOResult PatchDetachRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PDETR_GENERAL_CHUNK);
	isave->Write(&copy,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PDETR_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PatchDetachRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case PDETR_GENERAL_CHUNK:
				res = iload->Read(&copy,sizeof(int),&nb);
				break;
			case PDETR_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PatchMtlRecord::PatchMtlRecord(PatchMesh *patch, MtlID index) : PModRecord() {
	this->index = index;
	int selected = patch->patchSel.NumberSet();
	materials.SetCount(selected);
	int j = 0;
	for(int i = 0; i < patch->numPatches; ++i) {
		if(patch->patchSel[i])
			materials[j++] = patch->patches[i].getMatID();
		}
	assert(j == selected);
	}

BOOL PatchMtlRecord::Undo(PatchMesh *patch) {
	int j = 0;
	for(int i = 0; i < patch->numPatches; ++i) {
		if(patch->patchSel[i])
			patch->patches[i].setMatID(materials[j++]);
		}
	return TRUE;
	}

BOOL PatchMtlRecord::Redo(PatchMesh *patch,int reRecord) {
	for(int i = 0; i < patch->numPatches; ++i) {
		if(patch->patchSel[i])
			patch->patches[i].setMatID(index);
		}
	return TRUE;
	}

#define PMTLR_GENERAL_CHUNK		0x1000
//#define PMTLR_MATERIAL_CHUNK	0x1010
#define PMTLR_INDEX_CHUNK		0x1020

IOResult PatchMtlRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->EndChunk();
//	isave->BeginChunk(PMTLR_MATERIAL_CHUNK);
//	materials.Save(isave);
//	isave->EndChunk();
	isave->BeginChunk(PMTLR_INDEX_CHUNK);
	isave->Write(&index,sizeof(MtlID),&nb);
	isave->EndChunk();
	return IO_OK;
	}

IOResult PatchMtlRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
//			case PMTLR_MATERIAL_CHUNK:
//				res = materials.Load(iload);
//				break;
			case PMTLR_INDEX_CHUNK:
				res = iload->Read(&index,sizeof(MtlID),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PatchChangeRecord::PatchChangeRecord(PatchMesh *patch, int index, int type) : PModRecord() {
	oldPatch = *patch;
	this->index = index;
	this->type = type;
	}

BOOL PatchChangeRecord::Undo(PatchMesh *patch) {
	*patch = oldPatch;
	return TRUE;
	}

static void ChangePatchType(PatchMesh *patch, int index, int type) {
	// If positive vertex number, do it to just one vertex
	if(index >= 0) {
		patch->patches[index].flags = type;
		patch->computeInteriors();
		return;
		}

	// Otherwise, do it to all selected vertices!
	int patches = patch->numPatches;
	BitArray &psel = patch->patchSel;
	for(int i = 0; i < patches; ++i) {
		if(psel[i])
			patch->patches[i].flags = type;
		}
	patch->computeInteriors();
	}

BOOL PatchChangeRecord::Redo(PatchMesh *patch,int reRecord) {
	if(index >= 0 && index >= patch->numPatches)
		return FALSE;
	if(reRecord)
		oldPatch = *patch;
	ChangePatchType(patch, index, type);
	return TRUE;
	}

#define PCHG_GENERAL_CHUNK		0x1001
#define PCHG_PATCH_CHUNK		0x1010

IOResult PatchChangeRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(PCHG_GENERAL_CHUNK);
	isave->Write(&index,sizeof(int),&nb);
	isave->Write(&type,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(PCHG_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->EndChunk();
	return IO_OK;
	}

IOResult PatchChangeRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case PCHG_GENERAL_CHUNK:
				res = iload->Read(&index,sizeof(int),&nb);
				res = iload->Read(&type,sizeof(int),&nb);
				break;
			case PCHG_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PatchAddRecord::PatchAddRecord(int type, PatchMesh *patch) : PModRecord() {
	postWeld = TRUE;
	this->type = type;
	oldPatch = *patch;
	}

BOOL PatchAddRecord::Undo(PatchMesh *patch) {
	*patch = oldPatch;
	return TRUE;
	}

static void AddPatches(int type, PatchMesh *patch, BOOL postWeld) {
	if(!patch->edgeSel.NumberSet())
		return;		// Nothing to do!

	int lastVert = patch->getNumVerts();
	int edges = patch->getNumEdges();

	// Add a patch of the desired type to each selected edge that doesn't have two patches atatched!
	for(int i = 0; i < edges; ++i) {
		if(patch->edgeSel[i]) {
			PatchEdge &edge = patch->edges[i];
			if(edge.patch2 < 0) {
				int verts = patch->getNumVerts();
				int vecs = patch->getNumVecs();
				int patches = patch->getNumPatches();
				patch->setNumPatches(patches+1, TRUE);			// Add a patch
				patch->patches[patches].SetType(type);			// Make it the type we want
				patch->setNumVerts(verts + type - 2, TRUE);		// Add the appropriate number of verts
				patch->setNumVecs(vecs + (type-1) * 2 + type, TRUE);	// And the appropriate vector count
				Point3 p1 = patch->verts[edge.v1].p;
				Point3 p2 = patch->verts[edge.v2].p;
				Point3 v12 = patch->vecs[edge.vec12].p;
				Point3 v21 = patch->vecs[edge.vec21].p;
				Point3 edgeCenter = (p1 + p2) / 2.0f;
				// Load up the patch with the correct vert/vector indices
				Patch &spatch = patch->patches[edge.patch1];
				Patch &dpatch = patch->patches[patches];
				switch(type) {
					case PATCH_TRI:
						dpatch.setVerts(edge.v2, edge.v1, verts);
						dpatch.setVecs(edge.vec21, edge.vec12, vecs, vecs+1, vecs+2, vecs+3);
						dpatch.setInteriors(vecs+4, vecs+5, vecs+6);
						switch(spatch.type) {
							case PATCH_TRI: {		// Tri from Tri
								// Find the opposite vertex in the source triangle
								int opposite, o2a, o1a;
								if(spatch.edge[0] == i) {
									opposite = 2;
									o1a = 5;
									o2a = 2;
									}
								else
								if(spatch.edge[1] == i) {
									opposite = 0;
									o1a = 1;
									o2a = 4;
									}
								else {
									opposite = 1;
									o1a = 3;
									o2a = 0;
									}
								// Compute the new vert position
								Point3 oppVec = edgeCenter - patch->verts[spatch.v[opposite]].p;
								float oppLen = Length(oppVec);
								if(oppLen == 0.0f) {
									oppVec = Point3(0,0,1);
									oppLen = 1.0f;
									}
								Point3 v1a = patch->vecs[spatch.vec[o1a]].p - p1;
								Point3 v2a = patch->vecs[spatch.vec[o2a]].p - p2;
								Point3 n1a, n2a;
								if(Length(v1a) > 0.0f)
									n1a = Normalize(v1a);
								else
									n1a = Normalize(oppVec);
								if(Length(v2a) > 0.0f)
									n2a = Normalize(v2a);
								else
									n2a = Normalize(oppVec);
								
								// Build a composite vector based on the two edge vectors
								Point3 compVec = Normalize((n1a + n2a) / 2.0f);
								
								// Create the new vertex
								Point3 newPos = edgeCenter - compVec * oppLen;
								patch->verts[verts].p = newPos;

								// Compute the vectors
								patch->vecs[vecs].p = p1 - v1a;
								patch->vecs[vecs+1].p = newPos - (newPos - p1) / 3.0f;
								patch->vecs[vecs+2].p = newPos - (newPos - p2) / 3.0f;
								patch->vecs[vecs+3].p = p2 - v2a;
								}
								break;
							case PATCH_QUAD: {	// Tri from Quad
								// Find the opposite edge verts in the source quad
								int opposite1, opposite2, o1a, o2a;
								if(spatch.edge[0] == i) {
									opposite1 = 2;
									opposite2 = 3;
									o1a = 7;
									o2a = 2;
									}
								else
								if(spatch.edge[1] == i) {
									opposite1 = 3;
									opposite2 = 0;
									o1a = 1;
									o2a = 4;
									}
								else
								if(spatch.edge[2] == i) {
									opposite1 = 0;
									opposite2 = 1;
									o1a = 3;
									o2a = 6;
									}
								else {
									opposite1 = 1;
									opposite2 = 2;
									o1a = 5;
									o2a = 0;
									}
								// Compute the new vert position
								Point3 otherCenter = (patch->verts[spatch.v[opposite1]].p + patch->verts[spatch.v[opposite2]].p) / 2.0f;
								Point3 oppVec = edgeCenter - otherCenter;
								float oppLen = Length(oppVec);
								if(oppLen == 0.0f) {
									oppVec = Point3(0,0,1);
									oppLen = 1.0f;
									}
								Point3 v1a = patch->vecs[spatch.vec[o1a]].p - p1;
								Point3 v2a = patch->vecs[spatch.vec[o2a]].p - p2;
								Point3 n1a, n2a;
								if(Length(v1a) > 0.0f)
									n1a = Normalize(v1a);
								else
									n1a = Normalize(oppVec);
								if(Length(v2a) > 0.0f)
									n2a = Normalize(v2a);
								else
									n2a = Normalize(oppVec);
								
								// Build a composite vector based on the two edge vectors
								Point3 compVec = Normalize((n1a + n2a) / 2.0f);
								
								// Create the new vertex
								Point3 newPos = edgeCenter - compVec * oppLen;
								patch->verts[verts].p = newPos;

								// Compute the vectors
								patch->vecs[vecs].p = p1 - v1a;
								patch->vecs[vecs+1].p = newPos - (newPos - p1) / 3.0f;
								patch->vecs[vecs+2].p = newPos - (newPos - p2) / 3.0f;
								patch->vecs[vecs+3].p = p2 - v2a;
								}
								break;
							}
						break;
					case PATCH_QUAD:
						dpatch.setVerts(edge.v2, edge.v1, verts, verts+1);
						dpatch.setVecs(edge.vec21, edge.vec12, vecs, vecs+1, vecs+2, vecs+3, vecs+4, vecs+5);
						dpatch.setInteriors(vecs+6, vecs+7, vecs+8, vecs+9);
						switch(spatch.type) {
							case PATCH_TRI: {		// Quad from Tri
								// Find the opposite vertex in the source triangle
								int opposite, o2a, o1a;
								if(spatch.edge[0] == i) {
									opposite = 2;
									o1a = 5;
									o2a = 2;
									}
								else
								if(spatch.edge[1] == i) {
									opposite = 0;
									o1a = 1;
									o2a = 4;
									}
								else {
									opposite = 1;
									o1a = 3;
									o2a = 0;
									}

								Point3 oppVec = edgeCenter - patch->verts[spatch.v[opposite]].p;
								float oppLen = Length(oppVec);
								if(oppLen == 0.0f) {
									oppVec = Point3(0,0,1);
									oppLen = 1.0f;
									}
								Point3 v1a = patch->vecs[spatch.vec[o1a]].p - p1;
								Point3 v2a = patch->vecs[spatch.vec[o2a]].p - p2;
								Point3 n1a, n2a;
								if(Length(v1a) > 0.0f)
									n1a = Normalize(v1a);
								else
									n1a = Normalize(oppVec);
								if(Length(v2a) > 0.0f)
									n2a = Normalize(v2a);
								else
									n2a = Normalize(oppVec);

								// Compute the new vert positions
								Point3 newPos1 = p1 - n1a * oppLen;
								Point3 newPos2 = p2 - n2a * oppLen;
								patch->verts[verts].p = newPos1;
								patch->verts[verts+1].p = newPos2;
								// Compute the vectors
								patch->vecs[vecs].p = p1 - v1a;
								patch->vecs[vecs+1].p = newPos1 - (newPos1 - p1) / 3.0f;
								patch->vecs[vecs+2].p = newPos1 + (v12 - p1);
								patch->vecs[vecs+3].p = newPos2 + (v21 - p2);
								patch->vecs[vecs+4].p = newPos2 + (p2 - newPos2) / 3.0f;
								patch->vecs[vecs+5].p = p2 - v2a;
								}
								break;
							case PATCH_QUAD: {	// Quad from Quad
								// Find the opposite edge verts in the source quad
								int opposite1, opposite2, o1a, o2a;
								if(spatch.edge[0] == i) {
									opposite1 = 2;
									opposite2 = 3;
									o1a = 7;
									o2a = 2;
									}
								else
								if(spatch.edge[1] == i) {
									opposite1 = 3;
									opposite2 = 0;
									o1a = 1;
									o2a = 4;
									}
								else
								if(spatch.edge[2] == i) {
									opposite1 = 0;
									opposite2 = 1;
									o1a = 3;
									o2a = 6;
									}
								else {
									opposite1 = 1;
									opposite2 = 2;
									o1a = 5;
									o2a = 0;
									}

								Point3 otherCenter = (patch->verts[spatch.v[opposite1]].p + patch->verts[spatch.v[opposite2]].p) / 2.0f;
								Point3 oppVec = edgeCenter - otherCenter;
								float oppLen = Length(oppVec);
								if(oppLen == 0.0f) {
									oppVec = Point3(0,0,1);
									oppLen = 1.0f;
									}
								Point3 v1a = patch->vecs[spatch.vec[o1a]].p - p1;
								Point3 v2a = patch->vecs[spatch.vec[o2a]].p - p2;
								Point3 n1a, n2a;
								if(Length(v1a) > 0.0f)
									n1a = Normalize(v1a);
								else
									n1a = Normalize(oppVec);
								if(Length(v2a) > 0.0f)
									n2a = Normalize(v2a);
								else
									n2a = Normalize(oppVec);

								// Compute the new vert position
								Point3 newPos1 = p1 - n1a * oppLen;
								Point3 newPos2 = p2 - n2a * oppLen;
								patch->verts[verts].p = newPos1;
								patch->verts[verts+1].p = newPos2;

								// Compute the vectors
								patch->vecs[vecs].p = p1 - v1a;
								patch->vecs[vecs+1].p = newPos1 - (newPos1 - p1) / 3.0f;
								patch->vecs[vecs+2].p = newPos1 + (v12 - p1);
								patch->vecs[vecs+3].p = newPos2 + (v21 - p2);
								patch->vecs[vecs+4].p = newPos2 + (p2 - newPos2) / 3.0f;
								patch->vecs[vecs+5].p = p2 - v2a;
								}
								break;
							}
						break;
					}
				}
			}
		}
	patch->computeInteriors();
	patch->buildLinkages();
	// This step welds all new identical verts
	if(postWeld && (patch->getNumVerts() != lastVert))
		WeldPatch(patch, 0.0f, TRUE, lastVert);
	}

BOOL PatchAddRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		oldPatch = *patch;
	AddPatches(type, patch, postWeld);
	return TRUE;
	}

#define PADDR_TYPE_CHUNK		0x1000
#define PADDR_PATCH_CHUNK		0x1010
#define PADDR_POSTWELD_CHUNK	0x1020

IOResult PatchAddRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PADDR_TYPE_CHUNK);
	isave->Write(&type, sizeof(int), &nb);
	isave->	EndChunk();
	isave->BeginChunk(PADDR_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->EndChunk();
	if(postWeld) {
		isave->BeginChunk(PADDR_POSTWELD_CHUNK);	// Only in MAX 2.0 and up
		isave->EndChunk();
		}
	return IO_OK;
	}

IOResult PatchAddRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	postWeld = FALSE;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case PADDR_TYPE_CHUNK:
				res = iload->Read(&type,sizeof(int),&nb);
				break;
			case PADDR_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			// If the following chunk is present, it's a MAX 2.0 file and a post-addition
			// weld is to be performed
			case PADDR_POSTWELD_CHUNK:
				postWeld = TRUE;
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

EdgeSubdivideRecord::EdgeSubdivideRecord(BOOL propagate, PatchMesh *patch) : PModRecord() {
	this->propagate = propagate;
	oldPatch = *patch;
	}

BOOL EdgeSubdivideRecord::Undo(PatchMesh *patch) {
	*patch = oldPatch;
	return TRUE;
	}

class NewEdge {
	public:
		int oldEdge;
		int v1;
		int vec12;
		int vec21;
		int v2;
		int vec23;
		int vec32;
		int v3;
		NewEdge() { oldEdge = v1 = v2 = v3 = vec12 = vec21 = vec23 = vec32 = -1; }
	};

class PatchDivInfo {
	public:
		BOOL div02;
		BOOL div13;
		PatchDivInfo() { div02 = div13 = FALSE; }
	};

// Compute midpoint division for patch vectors -- Provide patchmesh, patch number, 4 bez points
// returns 2 new vectors

static Point3 InterpCenter(PatchMesh *patch, int index, int e1, int i1, int i2, int e2, Point3 *v1=NULL, Point3 *v2=NULL, Point3 *v3=NULL, Point3 *v4=NULL) {
	PatchVec *v = patch->vecs;
	Patch &p = patch->patches[index];
	Point3 e1i1 = (v[p.vec[e1]].p + v[p.interior[i1]].p) / 2.0f;
	Point3 i1i2 = (v[p.interior[i1]].p + v[p.interior[i2]].p) / 2.0f;
	Point3 i2e2 = (v[p.interior[i2]].p + v[p.vec[e2]].p) / 2.0f;
	Point3 a = (e1i1 + i1i2) / 2.0f;
	Point3 b = (i1i2 + i2e2) / 2.0f;
	if(v1) *v1 = e1i1;
	if(v2) *v2 = a;
	if(v3) *v3 = b;
	if(v4) *v4 = i2e2;
	return (a + b) / 2.0f;
	}

static Point3 InterpCenter(PatchMesh *patch, int index, int e1, int i1, int e2, Point3 *v1=NULL, Point3 *v2=NULL) {
	PatchVec *v = patch->vecs;
	Patch &p = patch->patches[index];
	Point3 a = (p.aux[e1] + v[p.interior[i1]].p) / 2.0f;
	Point3 b = (v[p.interior[i1]].p + p.aux[e2]) / 2.0f;
	if(v1) *v1 = a;
	if(v2) *v2 = b;
	return (a + b) / 2.0f;
	}

static Point3 InterpCenter(Point3 e1, Point3 i1, Point3 i2, Point3 e2, Point3 *v1=NULL, Point3 *v2=NULL, Point3 *v3=NULL, Point3 *v4=NULL ) {
	Point3 e1i1 = (e1 + i1) / 2.0f;
	Point3 i1i2 = (i1 + i2) / 2.0f;
	Point3 i2e2 = (i2 + e2) / 2.0f;
	Point3 a = (e1i1 + i1i2) / 2.0f;
	Point3 b = (i1i2 + i2e2) / 2.0f;
	if(v1) *v1 = e1i1;
	if(v2) *v2 = a;
	if(v3) *v3 = b;
	if(v4) *v4 = i2e2;
	return (a + b) / 2.0f;
	}

static Point3 InterpCenter(Point3 e1, Point3 i1, Point3 e2, Point3 *v1=NULL, Point3 *v2=NULL) {
	Point3 a = (e1 + i1) / 2.0f;
	Point3 b = (i1 + e2) / 2.0f;
	if(v1) *v1 = a;
	if(v2) *v2 = b;
	return (a + b) / 2.0f;
	}

static Point3 InterpEdge(PatchMesh *patch, int index, float pct, int c1, int e1, int e2, int c2, Point3 *v1=NULL, Point3 *v2=NULL, Point3 *v3=NULL, Point3 *v4=NULL) {
	PatchVert *vert = patch->verts;
	PatchVec *v = patch->vecs;
	Patch &p = patch->patches[index];
	Point3 pv1 = vert[p.v[c1]].p;
	Point3 pv2 = vert[p.v[c2]].p;
	Point3 pe1 = v[p.vec[e1]].p;
	Point3 pe2 = v[p.vec[e2]].p;
	Point3 v1e1 = pv1 + (pe1 - pv1) * pct;
	Point3 e1e2 = pe1 + (pe2 - pe1) * pct;
	Point3 e2v2 = pe2 + (pv2 - pe2) * pct;
	Point3 a = v1e1 + (e1e2 - v1e1) * pct;
	Point3 b = e1e2 + (e2v2 - e1e2) * pct;
	if(v1) *v1 = v1e1;
	if(v2) *v2 = a;
	if(v3) *v3 = b;
	if(v4) *v4 = e2v2;
	return a + (b - a) * pct;
	}

static Point3 InterpPoint(PatchMesh *patch, int index, float pct, int e1, int i1, int i2, int e2, Point3 *v1=NULL, Point3 *v2=NULL, Point3 *v3=NULL, Point3 *v4=NULL) {
	PatchVec *v = patch->vecs;
	Patch &p = patch->patches[index];
	Point3 pe1 = v[p.vec[e1]].p;
	Point3 pe2 = v[p.vec[e2]].p;
	Point3 pi1 = v[p.interior[i1]].p;
	Point3 pi2 = v[p.interior[i2]].p;
	Point3 e1i1 = pe1 + (pi1 - pe1) * pct;
	Point3 i1i2 = pi1 + (pi2 - pi1) * pct;
	Point3 i2e2 = pi2 + (pe2 - pi2) * pct;
	Point3 a = e1i1 + (i1i2 - e1i1) * pct;
	Point3 b = i1i2 + (i2e2 - i1i2) * pct;
	if(v1) *v1 = e1i1;
	if(v2) *v2 = a;
	if(v3) *v3 = b;
	if(v4) *v4 = i2e2;
	return a + (b - a) * pct;
	}

static Point3 InterpPoint(float pct, Point3 e1, Point3 i1, Point3 i2, Point3 e2, Point3 *v1=NULL, Point3 *v2=NULL, Point3 *v3=NULL, Point3 *v4=NULL ) {
	Point3 e1i1 = e1 + (i1 - e1) * pct;
	Point3 i1i2 = i1 + (i2 - i1) * pct;
	Point3 i2e2 = i2 + (e2 - i2) * pct;
	Point3 a = e1i1 + (i1i2 - e1i1) * pct;
	Point3 b = i1i2 + (i2e2 - i1i2) * pct;
	if(v1) *v1 = e1i1;
	if(v2) *v2 = a;
	if(v3) *v3 = b;
	if(v4) *v4 = i2e2;
	return a + (b - a) * pct;
	}

static Point3 InterpLinear(Point3 a, Point3 b, float interp) {
	return a + (a - b) * interp;
	}

static Point3 InterpDegree2(Point3 a, Point3 b, Point3 c, float interp) {
	Point3 ab = a + (b - a) * interp;
	Point3 bc = b + (c - b) * interp;
	return ab + (bc - ab) * interp;
	}

static Point3 InterpDegree3(Point3 a, Point3 b, Point3 c, Point3 d, float interp) {
	Point3 ab = a + (b - a) * interp;
	Point3 bc = b + (c - b) * interp;
	Point3 cd = c + (d - c) * interp;
	Point3 abbc = ab + (bc - ab) * interp;
	Point3 bccd = bc + (cd - bc) * interp;
	return abbc + (bccd - abbc) * interp;
	}

// Handy fractional constants
#define _1_16 0.0625f
#define _1_8 0.125f
#define _3_16 0.1875f
#define _1_4 0.25f

static Point3 GetOuterInside(Point3 a, Point3 b, Point3 c, Point3 d, Point3 e, Point3 f) {
	return a * _1_8 + b * _1_8 + c * _1_4 + d * _1_8 + e * _1_4 + f * _1_8;
	}

static Point3 GetNewEdgeVec(Point3 a, Point3 b, Point3 c, Point3 d, Point3 e, Point3 f, Point3 g, Point3 h) {
	return a * _1_16 + b * _1_16 + c * _3_16 + d * _3_16 + e * _1_16 + f * _1_16 + g * _3_16 + h * _3_16;
	}

static Point3 GetCentralInterior(Point3 a, Point3 b, Point3 c, Point3 d, Point3 e, Point3 f, Point3 g, Point3 h, Point3 i, Point3 j) {
	return a * _1_16 + b * _1_8 + c * _1_16 + d * _1_16 + e * _1_16 + f * _1_16 + g * _1_16 + h * _3_16 + i * _3_16 + j * _1_8;
	}

static Point3 GetNewEdgeCenter(Point3 a, Point3 b, Point3 c, Point3 d, Point3 e, Point3 f, Point3 g, Point3 h, Point3 i) {
	return a * _1_16 + b * _1_8 + c * _1_16 + d * _1_8 + e * _1_16 + f * _1_16 + g * _1_4 + h * _1_8 + i * _1_8;
	}

static Point3 GetOuterOutside(Point3 a, Point3 b, Point3 c, Point3 d) {
	return a * _1_4 + b * _1_4 + c * _1_4 + d * _1_4;
	}

static void FindNewTriEdge(PatchMesh *patch, Patch &p, int vert, Point3 &e1, Point3 &e2, Point3 &e3) {
	int a = vert;
	int b = vert * 3;
	int c = b + 1;
	int d = (b + 8) % 9;
	int e = (b + 7) % 9;
	int f = (b + 4) % 9;
	int g = vert;
	int h = (g + 1) % 3;
	int i = (g + 2) % 3;
	int j = (b + 6) % 9;
	int k = (b + 5) % 9;
	int l = b + 2;
	int m = (b + 3) % 9;
	Point3 pa = patch->verts[p.v[a]].p;
	Point3 pb = p.aux[b];
	Point3 pc = p.aux[c];
	Point3 pd = p.aux[d];
	Point3 pe = p.aux[e];
	Point3 pf = p.aux[f];
	Point3 pg = patch->vecs[p.interior[g]].p;
	Point3 ph = patch->vecs[p.interior[h]].p;
	Point3 pi = patch->vecs[p.interior[i]].p;
	Point3 pj = p.aux[j];
	Point3 pk = p.aux[k];
	Point3 pl = p.aux[l];
	Point3 pm = p.aux[m];
	e1 = GetNewEdgeVec(pa,pb,pd,pe,pj,pk,pg,pi);
	e2 = GetNewEdgeCenter(pa,pb,pc,pd,pe,pf,pg,ph,pi);
	e3 = GetNewEdgeVec(pa,pd,pb,pc,pl,pm,pg,ph);
	}

static void FindNewOuterTriInteriors(PatchMesh *patch, Patch &p, int vert, Point3 &i1, Point3 &i2, Point3 &i3) {
	int a = vert;
	int b = vert * 3;
	int c = (b + 8) % 9;
	int d = (b + 7) % 9;
	int e = vert;
	int f = (e + 2) % 3;
	int g = b + 1;
	int h = (e + 1) % 3;
	Point3 pa = patch->verts[p.v[a]].p;
	Point3 pb = p.aux[b];
	Point3 pc = p.aux[c];
	Point3 pd = p.aux[d];
	Point3 pe = patch->vecs[p.interior[e]].p;
	Point3 pf = patch->vecs[p.interior[f]].p;
	Point3 pg = p.aux[g];
	Point3 ph = patch->vecs[p.interior[h]].p;
	i1 = GetOuterOutside(pa,pb,pc,pe);
	i2 = GetOuterInside(pa,pc,pb,pg,pe,ph);
	i3 = GetOuterInside(pa,pb,pc,pd,pe,pf);
	}

static void FindNewInnerTriInteriors(PatchMesh *patch, Patch &p, Point3 &i1, Point3 &i2, Point3 &i3) {
	Point3 pa = p.aux[0];
	Point3 pb = p.aux[1];
	Point3 pc = p.aux[2];
	Point3 pd = p.aux[3];
	Point3 pe = p.aux[4];
	Point3 pf = p.aux[5];
	Point3 pg = p.aux[6];
	Point3 ph = p.aux[7];
	Point3 pi = p.aux[8];
	Point3 pj = patch->vecs[p.interior[0]].p;
	Point3 pk = patch->vecs[p.interior[1]].p;
	Point3 pl = patch->vecs[p.interior[2]].p;
	i1 = GetCentralInterior(pa,pb,pc,pi,ph,pe,pd,pj,pk,pl);
	i2 = GetCentralInterior(pd,pe,pf,pc,pb,ph,pg,pk,pl,pj);
	i3 = GetCentralInterior(pg,ph,pi,pf,pe,pb,pa,pl,pj,pk);
	}

// This is a first shot at a degree reducer which turns a degree-4 curve into a degree-3 curve,
// it probably won't give very good results unless the curve was converted from degree 3 to degree 4
// returns just the vector points
static void CubicFromQuartic(Point3 q1, Point3 q2, Point3 q3, Point3 q4, Point3 q5, Point3 &c2, Point3 &c3) {
	c2 = q1 + (q2 - q1) * 1.33333f;
	c3 = q5 + (q4 - q5) * 1.33333f;
	}

#define SUBDIV_EDGES 0
#define SUBDIV_PATCHES 1

static void SubdividePatch(int type, BOOL propagate, PatchMesh *patch) {
	int i;

	int verts = patch->getNumVerts();
	int vecs = patch->getNumVecs();
	int edges = patch->getNumEdges();
	int patches = patch->getNumPatches();

	// Make an edge flags array to note which edges must be processed
	BitArray eDiv(edges);
	// Make a patch flags array to note which patches must be processed
	BitArray pDiv(patches);
	// Make an edge flags array to note which edges have been done
	BitArray eDone(edges);
	eDone.ClearAll();
	// Make a patch flags array to note which patches have been done
	BitArray pDone(patches);
	pDone.ClearAll();

	switch(type) {
		case SUBDIV_EDGES:
			if(!patch->edgeSel.NumberSet())
				return;		// Nothing to do!
			eDiv = patch->edgeSel;
			pDiv.ClearAll();
			break;
		case SUBDIV_PATCHES:
			if(!patch->patchSel.NumberSet())
				return;		// Nothing to do!
			eDiv.ClearAll();
			pDiv = patch->patchSel;
			for(i = 0; i < patches; ++i) {
				if(pDiv[i]) {
					Patch &p = patch->patches[i];
					// Mark all edges for division
					eDiv.Set(p.edge[0]);
					eDiv.Set(p.edge[1]);
					eDiv.Set(p.edge[2]);
					if(p.type == PATCH_QUAD)
						eDiv.Set(p.edge[3]);
					}
				}
			// If not propagating, mark the edges as done
			if(!propagate)
				eDone = eDiv;
			break;
		}

	BOOL more = TRUE;
	while(more) {
		BOOL altered = FALSE;
		for(i = 0; i < edges; ++i) {
			if(eDiv[i] && !eDone[i]) {
				PatchEdge &e = patch->edges[i];
				pDiv.Set(e.patch1);
				if(e.patch2 >= 0)
					pDiv.Set(e.patch2);
				eDone.Set(i);
				altered = TRUE;
				}
			}
		if(altered && propagate) {
			for(i = 0; i < patches; ++i) {
				if(pDiv[i] && !pDone[i]) {
					Patch &p = patch->patches[i];
					if(p.type == PATCH_TRI) {	// Triangle -- tag all edges for division
						eDiv.Set(p.edge[0]);
						eDiv.Set(p.edge[1]);
						eDiv.Set(p.edge[2]);
						}
					else {		// Quad -- Tag edges opposite tagged edges
						if(eDiv[p.edge[0]])
							eDiv.Set(p.edge[2]);
						if(eDiv[p.edge[1]])
							eDiv.Set(p.edge[3]);
						if(eDiv[p.edge[2]])
							eDiv.Set(p.edge[0]);
						if(eDiv[p.edge[3]])
							eDiv.Set(p.edge[1]);
						}
					pDone.Set(i);
					}
				}
			}
		else
			more = FALSE;
		}

	// Keep a count of the new interior vectors
	int newInteriors = 0;

	// Also keep a count of the new vertices inside double-divided quads
	int newCenters = 0;

	// And a count of new texture vertices
	int newTVerts[PATCH_TEXTURE_CHANNELS];
	for(int chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan)
		newTVerts[chan] = 0;

	// And a count of new patches
	int newPatches = 0;

	int divPatches = pDiv.NumberSet();
	PatchDivInfo *pInfo = new PatchDivInfo [divPatches];
	int pDivIx;

	// Tag the edges that are on tagged patches but aren't tagged (only happens in propagate=0)
	// And set up a table with useful division info
	for(i = 0, pDivIx = 0; i < patches; ++i) {
		if(pDiv[i]) {
			PatchDivInfo &pi = pInfo[pDivIx];
			Patch &p = patch->patches[i];
			if(p.type == PATCH_TRI) {	// Triangle -- tag all edges for division
				eDiv.Set(p.edge[0]);
				eDiv.Set(p.edge[1]);
				eDiv.Set(p.edge[2]);
				newInteriors += (6 + 12);
				newPatches += 4;
				for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
					if(patch->tvPatches[chan])
						newTVerts[chan] += 3;
					}
				}
			else {		// Quad -- Tag edges opposite tagged edges
				int divs = 0;
				pi.div02 = pi.div13 = FALSE;
				if(eDiv[p.edge[0]]) {
					eDiv.Set(p.edge[2]);
					divs++;
					pi.div02 = TRUE;
					}
				else
				if(eDiv[p.edge[2]]) {
					eDiv.Set(p.edge[0]);
					divs++;
					pi.div02 = TRUE;
					}
				if(eDiv[p.edge[1]]) {
					eDiv.Set(p.edge[3]);
					divs++;
					pi.div13 = TRUE;
					}
				else
				if(eDiv[p.edge[3]]) {
					eDiv.Set(p.edge[1]);
					divs++;
					pi.div13 = TRUE;
					}
				newPatches += (divs==1) ? 2 : 4;
				newInteriors += (divs==1) ? (2 + 8) : (8 + 16);
				for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
					if(patch->tvPatches[chan]) {
						if(divs == 2)
							newTVerts[chan] += 5;
						else
							newTVerts[chan] += 2;
						}
					}
				if(divs==2)
					newCenters++;
				}
			pDivIx++;
			}
		}

	// Figure out how many new verts and vecs we'll need...
	int divEdges = eDiv.NumberSet();
	int newVerts = divEdges + newCenters;		// 1 new vert per edge
	int newVecs = divEdges * 4 + newInteriors;	// 4 new vectors per edge + new interior verts

	int vert = verts;
	int tvert[PATCH_TEXTURE_CHANNELS];
	int tverts[PATCH_TEXTURE_CHANNELS];
	int tpat[PATCH_TEXTURE_CHANNELS];
	for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
		tverts[chan] = tvert[chan] = patch->getNumTVertsChannel(chan);
		tpat[chan] = patches;
		}
	int vec = vecs;
	int pat = patches;

	// Add the new vertices
	patch->setNumVerts(verts + newVerts, TRUE);

	// Add the new texture vertices
	for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan)
		patch->setNumTVertsChannel(chan, tverts[chan] + newTVerts[chan], TRUE);

	// Add the new vectors
	patch->setNumVecs(vecs + newVecs, TRUE);

	// Add the new patches
	patch->setNumPatches(patches + newPatches, TRUE);

	// Create a new edge map
	NewEdge *eMap = new NewEdge [edges];
	for(i = 0; i < edges; ++i) {
		if(eDiv[i]) {
			PatchEdge &edge = patch->edges[i];
			NewEdge &map = eMap[i];
			map.oldEdge = i;
			map.v1 = edge.v1;
			map.vec12 = vec++;
			map.vec21 = vec++;
			map.v2 = vert++;
			map.vec23 = vec++;
			map.vec32 = vec++;
			map.v3 = edge.v2;
			
			// Compute the new edge vertex and vectors
			Point3 v00 = patch->verts[edge.v1].p;
			Point3 v10 = patch->vecs[edge.vec12].p;
			Point3 v20 = patch->vecs[edge.vec21].p;
			Point3 v30 = patch->verts[edge.v2].p;
			Point3 v01 = (v10 + v00) / 2.0f;
			Point3 v21 = (v30 + v20) / 2.0f;
			Point3 v11 = (v20 + v10) / 2.0f;
			Point3 v02 = (v11 + v01) / 2.0f;
			Point3 v12 = (v21 + v11) / 2.0f;
			Point3 v03 = (v12 + v02) / 2.0f;

			patch->verts[map.v2].p = v03;
			patch->vecs[map.vec12].p = v01;
			patch->vecs[map.vec21].p = v02;
			patch->vecs[map.vec23].p = v12;
			patch->vecs[map.vec32].p = v21;
			}
		}

#ifdef DUMPING
// Dump edge map
DebugPrint("Edge map:\n");
for(i = 0; i < edges; ++i) {
	NewEdge &e = eMap[i];
	DebugPrint("Old edge: %d  New edge: %d (%d %d) %d (%d %d) %d\n",e.oldEdge,e.v1,e.vec12,e.vec21,e.v2,e.vec23,e.vec32,e.v3);
	}
#endif

	// Now go and subdivide them!

	for(i = 0, pDivIx = 0; i < patches; ++i) {
		if(pDiv[i]) {
			PatchDivInfo &pi = pInfo[pDivIx];
			Patch &p = patch->patches[i];
			if(p.type == PATCH_TRI) {
				// Need to create four new patches
				int newev1 = vec++;	// edge 0 -> edge 1
				int newev2 = vec++;	// edge 1 -> edge 0
				int newev3 = vec++;	// edge 1 -> edge 2
				int newev4 = vec++;	// edge 2 -> edge 1
				int newev5 = vec++;	// edge 2 -> edge 0
				int newev6 = vec++;	// edge 0 -> edge 2

				// Get pointers to new edges
				NewEdge &e0 = eMap[p.edge[0]];
				NewEdge &e1 = eMap[p.edge[1]];
				NewEdge &e2 = eMap[p.edge[2]];

				// See if edges need to be flopped
				BOOL flop0 = (e0.v1 == p.v[0]) ? FALSE : TRUE;
				BOOL flop1 = (e1.v1 == p.v[1]) ? FALSE : TRUE;
				BOOL flop2 = (e2.v1 == p.v[2]) ? FALSE : TRUE;

				// Create the four new patches
				Patch &p1 = patch->patches[pat++];
				Patch &p2 = patch->patches[pat++];
				Patch &p3 = patch->patches[pat++];
				Patch &p4 = patch->patches[pat++];

				p1.SetType(PATCH_TRI);
				p1.v[0] = e0.v2;
				p1.v[1] = flop1 ? e1.v3 : e1.v1;
				p1.v[2] = e1.v2;
				p1.vec[0] = flop0 ? e0.vec21 : e0.vec23;
				p1.vec[1] = flop0 ? e0.vec12 : e0.vec32;
				p1.vec[2] = flop1 ? e1.vec32 : e1.vec12;
				p1.vec[3] = flop1 ? e1.vec23 : e1.vec21;
				p1.vec[4] = newev2;
				p1.vec[5] = newev1;
				p1.interior[0] = vec++;
				p1.interior[1] = vec++;
				p1.interior[2] = vec++;

				p2.SetType(PATCH_TRI);
				p2.v[0] = e1.v2;
				p2.v[1] = flop2 ? e2.v3 : e2.v1;
				p2.v[2] = e2.v2;
				p2.vec[0] = flop1 ? e1.vec21 : e1.vec23;
				p2.vec[1] = flop1 ? e1.vec12 : e1.vec32;
				p2.vec[2] = flop2 ? e2.vec32 : e2.vec12;
				p2.vec[3] = flop2 ? e2.vec23 : e2.vec21;
				p2.vec[4] = newev4;
				p2.vec[5] = newev3;
				p2.interior[0] = vec++;
				p2.interior[1] = vec++;
				p2.interior[2] = vec++;

				p3.SetType(PATCH_TRI);
				p3.v[0] = e0.v2;
				p3.v[1] = e1.v2;
				p3.v[2] = e2.v2;
				p3.vec[0] = newev1;
				p3.vec[1] = newev2;
				p3.vec[2] = newev3;
				p3.vec[3] = newev4;
				p3.vec[4] = newev5;
				p3.vec[5] = newev6;
				p3.interior[0] = vec++;
				p3.interior[1] = vec++;
				p3.interior[2] = vec++;

				p4.SetType(PATCH_TRI);
				p4.v[0] = flop0 ? e0.v3 : e0.v1;
				p4.v[1] = e0.v2;
				p4.v[2] = e2.v2;
				p4.vec[0] = flop0 ? e0.vec32 : e0.vec12;
				p4.vec[1] = flop0 ? e0.vec23 : e0.vec21;
				p4.vec[2] = newev6;
				p4.vec[3] = newev5;
				p4.vec[4] = flop2 ? e2.vec21 : e2.vec23;
				p4.vec[5] = flop2 ? e2.vec12 : e2.vec32;
				p4.interior[0] = vec++;
				p4.interior[1] = vec++;
				p4.interior[2] = vec++;

				// If this patch is textured, create three new texture verts for it
				for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
					if(patch->tvPatches[chan]) {
						int tva = tvert[chan]++;
						int tvb = tvert[chan]++;
						int tvc = tvert[chan]++;
						TVPatch &tp = patch->tvPatches[chan][i];
						TVPatch &tp1 = patch->tvPatches[chan][tpat[chan]++];
						TVPatch &tp2 = patch->tvPatches[chan][tpat[chan]++];
						TVPatch &tp3 = patch->tvPatches[chan][tpat[chan]++];
						TVPatch &tp4 = patch->tvPatches[chan][tpat[chan]++];
						tp1.tv[0] = tva;
						tp1.tv[1] = tp.tv[1];
						tp1.tv[2] = tvb;
						tp2.tv[0] = tvb;
						tp2.tv[1] = tp.tv[2];
						tp2.tv[2] = tvc;
						tp3.tv[0] = tva;
						tp3.tv[1] = tvb;
						tp3.tv[2] = tvc;
						tp4.tv[0] = tp.tv[0];
						tp4.tv[1] = tva;
						tp4.tv[2] = tvc;
						patch->tVerts[chan][tva] = (patch->tVerts[chan][tp.tv[0]] + patch->tVerts[chan][tp.tv[1]]) / 2.0f;
						patch->tVerts[chan][tvb] = (patch->tVerts[chan][tp.tv[1]] + patch->tVerts[chan][tp.tv[2]]) / 2.0f;
						patch->tVerts[chan][tvc] = (patch->tVerts[chan][tp.tv[2]] + patch->tVerts[chan][tp.tv[0]]) / 2.0f;
						}
					}

				// Now we'll compute the vectors for the three new edges being created inside this patch
				// These come back as degree 4's, and we need to reduce them to degree 3 for use in our
				// edges -- This is a bit risky because we aren't guaranteed a perfect fit.
				Point3 i1, i2, i3, i4, i5, i6, i7, i8, i9;
				FindNewTriEdge(patch, p, 0, i1, i2, i3);
				FindNewTriEdge(patch, p, 1, i4, i5, i6);
				FindNewTriEdge(patch, p, 2, i7, i8, i9);
				Point3 v1, v2, v3, v4, v5, v6;
				CubicFromQuartic(patch->verts[e2.v2].p, i1, i2, i3, patch->verts[e0.v2].p, v1, v2);
				CubicFromQuartic(patch->verts[e0.v2].p, i4, i5, i6, patch->verts[e1.v2].p, v3, v4);
				CubicFromQuartic(patch->verts[e1.v2].p, i7, i8, i9, patch->verts[e2.v2].p, v5, v6);
				patch->vecs[newev1].p = v3;
				patch->vecs[newev2].p = v4;
				patch->vecs[newev3].p = v5;
				patch->vecs[newev4].p = v6;
				patch->vecs[newev5].p = v1;
				patch->vecs[newev6].p = v2;
				// Now compute the interior vectors for the new patches if the one we're dividing isn't automatic
				// Must compute vectors for this patch's divided edges
				if(!(p.flags & PATCH_AUTO)) {
					p1.flags &= ~PATCH_AUTO;
					p2.flags &= ~PATCH_AUTO;
					p3.flags &= ~PATCH_AUTO;
					p4.flags &= ~PATCH_AUTO;

					FindNewOuterTriInteriors(patch, p, 1, patch->vecs[p1.interior[1]].p, patch->vecs[p1.interior[2]].p, patch->vecs[p1.interior[0]].p);
					FindNewOuterTriInteriors(patch, p, 2, patch->vecs[p2.interior[1]].p, patch->vecs[p2.interior[2]].p, patch->vecs[p2.interior[0]].p);
					FindNewInnerTriInteriors(patch, p, patch->vecs[p3.interior[0]].p, patch->vecs[p3.interior[1]].p, patch->vecs[p3.interior[2]].p);
					FindNewOuterTriInteriors(patch, p, 0, patch->vecs[p4.interior[0]].p, patch->vecs[p4.interior[1]].p, patch->vecs[p4.interior[2]].p);
					}
				}
			else {		// Quad patch
				// Check division flags to see how many patches we'll need
				if(pi.div02 && pi.div13) {		// Divide both ways
					// Need a new central vertex
					Point3 newc = p.interp(patch, 0.5f, 0.5f);
					patch->verts[vert].p = newc;
					int center = vert++;

					// Need to create four new patches
					int newev1 = vec++;	// edge 0 -> center
					int newev2 = vec++;	// center -> edge 0
					int newev3 = vec++;	// edge 1 -> center
					int newev4 = vec++;	// center -> edge 1
					int newev5 = vec++;	// edge 2 -> center
					int newev6 = vec++;	// center -> edge 2
					int newev7 = vec++;	// edge 3 -> center
					int newev8 = vec++;	// center -> edge 3

					// Get pointers to new edges
					NewEdge &e0 = eMap[p.edge[0]];
					NewEdge &e1 = eMap[p.edge[1]];
					NewEdge &e2 = eMap[p.edge[2]];
					NewEdge &e3 = eMap[p.edge[3]];

					// See if edges need to be flopped
					BOOL flop0 = (e0.v1 == p.v[0]) ? FALSE : TRUE;
					BOOL flop1 = (e1.v1 == p.v[1]) ? FALSE : TRUE;
					BOOL flop2 = (e2.v1 == p.v[2]) ? FALSE : TRUE;
					BOOL flop3 = (e3.v1 == p.v[3]) ? FALSE : TRUE;

					// Compute the new vectors for the dividing line
					Point3 w1,w2,w3,w4;
					w1 = InterpCenter(patch, i, 7, 0, 1, 2);
					w2 = InterpCenter(patch, i, 6, 3, 2, 3);
					w3 = InterpCenter(patch, i, 1, 1, 2, 4);
					w4 = InterpCenter(patch, i, 0, 0, 3, 5);
					Point3 new0 = patch->verts[e0.v2].p;
					Point3 new1 = patch->verts[e1.v2].p;
					Point3 new2 = patch->verts[e2.v2].p;
					Point3 new3 = patch->verts[e3.v2].p;
					InterpCenter(new0, w1, w2, new2, &patch->vecs[newev1].p, &patch->vecs[newev2].p, &patch->vecs[newev6].p, &patch->vecs[newev5].p);
					InterpCenter(new1, w3, w4, new3, &patch->vecs[newev3].p, &patch->vecs[newev4].p, &patch->vecs[newev8].p, &patch->vecs[newev7].p);

					// Create the four new patches
					Patch &p1 = patch->patches[pat++];
					Patch &p2 = patch->patches[pat++];
					Patch &p3 = patch->patches[pat++];
					Patch &p4 = patch->patches[pat++];

					p1.SetType(PATCH_QUAD);
					p1.v[0] = p.v[0];
					p1.v[1] = e0.v2;
					p1.v[2] = center;
					p1.v[3] = e3.v2;
					p1.vec[0] = flop0 ? e0.vec32 : e0.vec12;
					p1.vec[1] = flop0 ? e0.vec23 : e0.vec21;
					p1.vec[2] = newev1;
					p1.vec[3] = newev2;
					p1.vec[4] = newev8;
					p1.vec[5] = newev7;
					p1.vec[6] = flop3 ? e3.vec21 : e3.vec23;
					p1.vec[7] = flop3 ? e3.vec12 : e3.vec32;
					p1.interior[0] = vec++;
					p1.interior[1] = vec++;
					p1.interior[2] = vec++;
					p1.interior[3] = vec++;

					p2.SetType(PATCH_QUAD);
					p2.v[0] = p.v[1];
					p2.v[1] = e1.v2;
					p2.v[2] = center;
					p2.v[3] = e0.v2;
					p2.vec[0] = flop1 ? e1.vec32 : e1.vec12;
					p2.vec[1] = flop1 ? e1.vec23 : e1.vec21;
					p2.vec[2] = newev3;
					p2.vec[3] = newev4;
					p2.vec[4] = newev2;
					p2.vec[5] = newev1;
					p2.vec[6] = flop0 ? e0.vec21 : e0.vec23;
					p2.vec[7] = flop0 ? e0.vec12 : e0.vec32;
					p2.interior[0] = vec++;
					p2.interior[1] = vec++;
					p2.interior[2] = vec++;
					p2.interior[3] = vec++;

					p3.SetType(PATCH_QUAD);
					p3.v[0] = p.v[2];
					p3.v[1] = e2.v2;
					p3.v[2] = center;
					p3.v[3] = e1.v2;
					p3.vec[0] = flop2 ? e2.vec32 : e2.vec12;
					p3.vec[1] = flop2 ? e2.vec23 : e2.vec21;
					p3.vec[2] = newev5;
					p3.vec[3] = newev6;
					p3.vec[4] = newev4;
					p3.vec[5] = newev3;
					p3.vec[6] = flop1 ? e1.vec21 : e1.vec23;
					p3.vec[7] = flop1 ? e1.vec12 : e1.vec32;
					p3.interior[0] = vec++;
					p3.interior[1] = vec++;
					p3.interior[2] = vec++;
					p3.interior[3] = vec++;

					p4.SetType(PATCH_QUAD);
					p4.v[0] = p.v[3];
					p4.v[1] = e3.v2;
					p4.v[2] = center;
					p4.v[3] = e2.v2;
					p4.vec[0] = flop3 ? e3.vec32 : e3.vec12;
					p4.vec[1] = flop3 ? e3.vec23 : e3.vec21;
					p4.vec[2] = newev7;
					p4.vec[3] = newev8;
					p4.vec[4] = newev6;
					p4.vec[5] = newev5;
					p4.vec[6] = flop2 ? e2.vec21 : e2.vec23;
					p4.vec[7] = flop2 ? e2.vec12 : e2.vec32;
					p4.interior[0] = vec++;
					p4.interior[1] = vec++;
					p4.interior[2] = vec++;
					p4.interior[3] = vec++;

					// If this patch is textured, create five new texture verts for it
					for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
						if(patch->tvPatches[chan]) {
							int tva = tvert[chan]++;
							int tvb = tvert[chan]++;
							int tvc = tvert[chan]++;
							int tvd = tvert[chan]++;
							int tve = tvert[chan]++;
							TVPatch &tp = patch->tvPatches[chan][i];
							TVPatch &tp1 = patch->tvPatches[chan][tpat[chan]++];
							TVPatch &tp2 = patch->tvPatches[chan][tpat[chan]++];
							TVPatch &tp3 = patch->tvPatches[chan][tpat[chan]++];
							TVPatch &tp4 = patch->tvPatches[chan][tpat[chan]++];
							tp1.tv[0] = tp.tv[0];
							tp1.tv[1] = tva;
							tp1.tv[2] = tve;
							tp1.tv[3] = tvd;
							tp2.tv[0] = tp.tv[1];
							tp2.tv[1] = tvb;
							tp2.tv[2] = tve;
							tp2.tv[3] = tva;
							tp3.tv[0] = tp.tv[2];
							tp3.tv[1] = tvc;
							tp3.tv[2] = tve;
							tp3.tv[3] = tvb;
							tp4.tv[0] = tp.tv[3];
							tp4.tv[1] = tvd;
							tp4.tv[2] = tve;
							tp4.tv[3] = tvc;
							patch->tVerts[chan][tva] = (patch->tVerts[chan][tp.tv[0]] + patch->tVerts[chan][tp.tv[1]]) / 2.0f;
							patch->tVerts[chan][tvb] = (patch->tVerts[chan][tp.tv[1]] + patch->tVerts[chan][tp.tv[2]]) / 2.0f;
							patch->tVerts[chan][tvc] = (patch->tVerts[chan][tp.tv[3]] + patch->tVerts[chan][tp.tv[0]]) / 2.0f;
							patch->tVerts[chan][tve] = (patch->tVerts[chan][tp.tv[0]] + patch->tVerts[chan][tp.tv[1]] + patch->tVerts[chan][tp.tv[2]] + patch->tVerts[chan][tp.tv[3]]) / 4.0f;
							}
						}

					// If it's not an auto patch, compute the new interior points
					if(!(p.flags & PATCH_AUTO)) {
						p1.flags &= ~PATCH_AUTO;
						p2.flags &= ~PATCH_AUTO;
						p3.flags &= ~PATCH_AUTO;
						p4.flags &= ~PATCH_AUTO;

						Point3 a,b,c,d;
						Point3 a1,b1,c1,d1;
						Point3 a2,b2,c2,d2;
						Point3 a3,b3,c3,d3;
						Point3 a4,b4,c4,d4;
						InterpEdge(patch, i, 0.5f, 0, 0, 1, 1, &a1, &b1, &c1, &d1);
						InterpCenter(patch, i, 7, 0, 1, 2, &a2, &b2, &c2, &d2);
						InterpCenter(patch, i, 6, 3, 2, 3, &a3, &b3, &c3, &d3);
						InterpEdge(patch, i, 0.5f, 3, 5, 4, 2, &a4, &b4, &c4, &d4);

						InterpCenter(a1, a2, a3, a4, &a, &b, &c, &d);
						patch->vecs[p1.interior[0]].p = a;
						patch->vecs[p1.interior[3]].p = b;
						patch->vecs[p4.interior[1]].p = c;
						patch->vecs[p4.interior[0]].p = d;
						InterpCenter(b1, b2, b3, b4, &a, &b, &c, &d);
						patch->vecs[p1.interior[1]].p = a;
						patch->vecs[p1.interior[2]].p = b;
						patch->vecs[p4.interior[2]].p = c;
						patch->vecs[p4.interior[3]].p = d;
						InterpCenter(c1, c2, c3, c4, &a, &b, &c, &d);
						patch->vecs[p2.interior[3]].p = a;
						patch->vecs[p2.interior[2]].p = b;
						patch->vecs[p3.interior[2]].p = c;
						patch->vecs[p3.interior[1]].p = d;
						InterpCenter(d1, d2, d3, d4, &a, &b, &c, &d);
						patch->vecs[p2.interior[0]].p = a;
						patch->vecs[p2.interior[1]].p = b;
						patch->vecs[p3.interior[3]].p = c;
						patch->vecs[p3.interior[0]].p = d;
						}
					}
				else
				if(pi.div02) {					// Divide edges 0 & 2
					// Need to create two new patches
					// TO DO: Compute new edge vectors between new edge verts
					int newev1 = vec++;	// edge 0 -> edge 2
					int newev2 = vec++;	// edge 2 -> edge 0

					// Get pointers to new edges
					NewEdge &e0 = eMap[p.edge[0]];
					NewEdge &e2 = eMap[p.edge[2]];

					// See if edges need to be flopped
					BOOL flop0 = (e0.v1 == p.v[0]) ? FALSE : TRUE;
					BOOL flop2 = (e2.v1 == p.v[2]) ? FALSE : TRUE;

					// Compute the new vectors for the dividing line
					
					patch->vecs[newev1].p = InterpCenter(patch, i, 7, 0, 1, 2);
					patch->vecs[newev2].p = InterpCenter(patch, i, 6, 3, 2, 3);

					// Create the two new patches
					Patch &p1 = patch->patches[pat++];
					Patch &p2 = patch->patches[pat++];

					p1.SetType(PATCH_QUAD);
					p1.v[0] = flop0 ? e0.v3 : e0.v1;
					p1.v[1] = e0.v2;
					p1.v[2] = e2.v2;
					p1.v[3] = flop2 ? e2.v1 : e2.v3;
					p1.vec[0] = flop0 ? e0.vec32 : e0.vec12;
					p1.vec[1] = flop0 ? e0.vec23 : e0.vec21;
					p1.vec[2] = newev1;
					p1.vec[3] = newev2;
					p1.vec[4] = flop2 ? e2.vec21 : e2.vec23;
					p1.vec[5] = flop2 ? e2.vec12 : e2.vec32;
					p1.vec[6] = p.vec[6];
					p1.vec[7] = p.vec[7];
					p1.interior[0] = vec++;
					p1.interior[1] = vec++;
					p1.interior[2] = vec++;
					p1.interior[3] = vec++;

					p2.SetType(PATCH_QUAD);
					p2.v[0] = e0.v2;
					p2.v[1] = flop0 ? e0.v1 : e0.v3;
					p2.v[2] = flop2 ? e2.v3 : e2.v1;
					p2.v[3] = e2.v2;
					p2.vec[0] = flop0 ? e0.vec21 : e0.vec23;
					p2.vec[1] = flop0 ? e0.vec12 : e0.vec32;
					p2.vec[2] = p.vec[2];
					p2.vec[3] = p.vec[3];
					p2.vec[4] = flop2 ? e2.vec32 : e2.vec12;
					p2.vec[5] = flop2 ? e2.vec23 : e2.vec21;
					p2.vec[6] = newev2;
					p2.vec[7] = newev1;
					p2.interior[0] = vec++;
					p2.interior[1] = vec++;
					p2.interior[2] = vec++;
					p2.interior[3] = vec++;

					// If this patch is textured, create two new texture verts for it
					for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
						if(patch->tvPatches[chan]) {
							int tva = tvert[chan]++;
							int tvb = tvert[chan]++;
							TVPatch &tp = patch->tvPatches[chan][i];
							TVPatch &tp1 = patch->tvPatches[chan][tpat[chan]++];
							TVPatch &tp2 = patch->tvPatches[chan][tpat[chan]++];
							tp1.tv[0] = tp.tv[0];
							tp1.tv[1] = tva;
							tp1.tv[2] = tvb;
							tp1.tv[3] = tp.tv[3];
							tp2.tv[0] = tva;
							tp2.tv[1] = tp.tv[1];
							tp2.tv[2] = tp.tv[2];
							tp2.tv[3] = tvb;
							patch->tVerts[chan][tva] = (patch->tVerts[chan][tp.tv[0]] + patch->tVerts[chan][tp.tv[1]]) / 2.0f;
							patch->tVerts[chan][tvb] = (patch->tVerts[chan][tp.tv[2]] + patch->tVerts[chan][tp.tv[3]]) / 2.0f;
							}
						}

					// If it's not an auto patch, compute the new interior points
					if(!(p.flags & PATCH_AUTO)) {
						p1.flags &= ~PATCH_AUTO;
						p2.flags &= ~PATCH_AUTO;

						Point3 a,b,c,d;
						InterpCenter(patch, i, 7, 0, 1, 2, &a, &b, &c, &d);
						patch->vecs[p1.interior[0]].p = a;
						patch->vecs[p1.interior[1]].p = b;
						patch->vecs[p2.interior[0]].p = c;
						patch->vecs[p2.interior[1]].p = d;
						InterpCenter(patch, i, 6, 3, 2, 3, &a, &b, &c, &d);
						patch->vecs[p1.interior[3]].p = a;
						patch->vecs[p1.interior[2]].p = b;
						patch->vecs[p2.interior[3]].p = c;
						patch->vecs[p2.interior[2]].p = d;
						}
					}
				else {							// Divide edges 1 & 3
					// Need to create two new patches
					// TO DO: Compute new edge vectors between new edge verts
					int newev1 = vec++;	// edge 1 -> edge 3
					int newev2 = vec++;	// edge 3 -> edge 1

					// Get pointers to new edges
					NewEdge &e1 = eMap[p.edge[1]];
					NewEdge &e3 = eMap[p.edge[3]];

					// See if edges need to be flopped
					BOOL flop1 = (e1.v1 == p.v[1]) ? FALSE : TRUE;
					BOOL flop3 = (e3.v1 == p.v[3]) ? FALSE : TRUE;

					// Compute the new vectors for the dividing line
					patch->vecs[newev1].p = InterpCenter(patch, i, 1, 1, 2, 4);
					patch->vecs[newev2].p = InterpCenter(patch, i, 0, 0, 3, 5);

					// Create the two new patches
					Patch &p1 = patch->patches[pat++];
					Patch &p2 = patch->patches[pat++];

					p1.SetType(PATCH_QUAD);
					p1.v[0] = p.v[1];
					p1.v[1] = e1.v2;
					p1.v[2] = e3.v2;
					p1.v[3] = p.v[0];
					p1.vec[0] = flop1 ? e1.vec32 : e1.vec12;
					p1.vec[1] = flop1 ? e1.vec23 : e1.vec21;
					p1.vec[2] = newev1;
					p1.vec[3] = newev2;
					p1.vec[4] = flop3 ? e3.vec21 : e3.vec23;
					p1.vec[5] = flop3 ? e3.vec12 : e3.vec32;
					p1.vec[6] = p.vec[0];
					p1.vec[7] = p.vec[1];
					p1.interior[0] = vec++;
					p1.interior[1] = vec++;
					p1.interior[2] = vec++;
					p1.interior[3] = vec++;

					p2.SetType(PATCH_QUAD);
					p2.v[0] = e1.v2;
					p2.v[1] = p.v[2];
					p2.v[2] = p.v[3];
					p2.v[3] = e3.v2;
					p2.vec[0] = flop1 ? e1.vec21 : e1.vec23;
					p2.vec[1] = flop1 ? e1.vec12 : e1.vec32;
					p2.vec[2] = p.vec[4];
					p2.vec[3] = p.vec[5];
					p2.vec[4] = flop3 ? e3.vec32 : e3.vec12;
					p2.vec[5] = flop3 ? e3.vec23 : e3.vec21;
					p2.vec[6] = newev2;
					p2.vec[7] = newev1;
					p2.interior[0] = vec++;
					p2.interior[1] = vec++;
					p2.interior[2] = vec++;
					p2.interior[3] = vec++;

					// If this patch is textured, create two new texture verts for it
					for(chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
						if(patch->tvPatches[chan]) {
							int tva = tvert[chan]++;
							int tvb = tvert[chan]++;
							TVPatch &tp = patch->tvPatches[chan][i];
							TVPatch &tp1 = patch->tvPatches[chan][tpat[chan]++];
							TVPatch &tp2 = patch->tvPatches[chan][tpat[chan]++];
							tp1.tv[0] = tp.tv[1];
							tp1.tv[1] = tva;
							tp1.tv[2] = tvb;
							tp1.tv[3] = tp.tv[0];
							tp2.tv[0] = tva;
							tp2.tv[1] = tp.tv[2];
							tp2.tv[2] = tp.tv[3];
							tp2.tv[3] = tvb;
							patch->tVerts[chan][tva] = (patch->tVerts[chan][tp.tv[1]] + patch->tVerts[chan][tp.tv[2]]) / 2.0f;
							patch->tVerts[chan][tvb] = (patch->tVerts[chan][tp.tv[0]] + patch->tVerts[chan][tp.tv[3]]) / 2.0f;
							}
						}

					// If it's not an auto patch, compute the new interior points
					if(!(p.flags & PATCH_AUTO)) {
						p1.flags &= ~PATCH_AUTO;
						p2.flags &= ~PATCH_AUTO;

						Point3 a,b,c,d;
						InterpCenter(patch, i, 1, 1, 2, 4, &a, &b, &c, &d);
						patch->vecs[p1.interior[0]].p = a;
						patch->vecs[p1.interior[1]].p = b;
						patch->vecs[p2.interior[0]].p = c;
						patch->vecs[p2.interior[1]].p = d;
						InterpCenter(patch, i, 0, 0, 3, 5, &a, &b, &c, &d);
						patch->vecs[p1.interior[3]].p = a;
						patch->vecs[p1.interior[2]].p = b;
						patch->vecs[p2.interior[3]].p = c;
						patch->vecs[p2.interior[2]].p = d;
						}
					}	
				}
			pDivIx++;
			}
		}

	delete [] pInfo;
	delete [] eMap;

	// Now call the DeletePatchParts function to clean it all up
	BitArray dumVerts(patch->getNumVerts());
	dumVerts.ClearAll();
	BitArray dumPatches(patch->getNumPatches());
	dumPatches.ClearAll();
	// Mark the subdivided patches as deleted
	for(i = 0; i < patches; ++i)
		dumPatches.Set(i, pDiv[i]);

#ifdef DUMPING
DebugPrint("Before:\n");
patch->Dump();
#endif

	DeletePatchParts(patch, dumVerts, dumPatches);

#ifdef DUMPING
DebugPrint("After:\n");
patch->Dump();
#endif

	patch->computeInteriors();
	patch->buildLinkages();
	}

BOOL EdgeSubdivideRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		oldPatch = *patch;
	SubdividePatch(SUBDIV_EDGES, propagate, patch);
	return TRUE;
	}

#define ESUBR_PROPAGATE_CHUNK		0x1000
#define ESUBR_PATCH_CHUNK			0x1010

IOResult EdgeSubdivideRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	if(propagate) {
		isave->BeginChunk(ESUBR_PROPAGATE_CHUNK);
		isave->	EndChunk();
		}
	isave->BeginChunk(ESUBR_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult EdgeSubdivideRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	propagate = FALSE;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case ESUBR_PROPAGATE_CHUNK:
				propagate = TRUE;
				break;
			case ESUBR_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PatchSubdivideRecord::PatchSubdivideRecord(BOOL propagate, PatchMesh *patch) : PModRecord() {
	this->propagate = propagate;
	oldPatch = *patch;
	}

BOOL PatchSubdivideRecord::Undo(PatchMesh *patch) {
	*patch = oldPatch;
	return TRUE;
	}

BOOL PatchSubdivideRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		oldPatch = *patch;
	SubdividePatch(SUBDIV_PATCHES, propagate, patch);
	return TRUE;
	}

#define PSUBR_PROPAGATE_CHUNK		0x1000
#define PSUBR_PATCH_CHUNK			0x1010

IOResult PatchSubdivideRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	if(propagate) {
		isave->BeginChunk(PSUBR_PROPAGATE_CHUNK);
		isave->	EndChunk();
		}
	isave->BeginChunk(PSUBR_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PatchSubdivideRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	propagate = FALSE;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case PSUBR_PROPAGATE_CHUNK:
				propagate = TRUE;
				break;
			case PSUBR_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/		

PVertWeldRecord::PVertWeldRecord(float thresh, PatchMesh *patch) : PModRecord() {
	this->thresh = thresh;
	oldPatch = *patch;
	}

BOOL PVertWeldRecord::Undo(PatchMesh *patch) {
	*patch = oldPatch;
	return TRUE;
	}

class OpenEdge {
	public:
		static int count;
		BOOL open;
		int patchNum;
		int v1;
		int vec12;
		int vec21;
		int v2;
		BOOL onSel;
		BOOL average;
		BOOL attachedTo;
		BOOL reVectored;
		OpenEdge();
		OpenEdge &operator=(PatchEdge &from) { v1=from.v1; vec12=from.vec12; vec21=from.vec21; v2=from.v2; patchNum=from.patch1; return *this; }
	};

int OpenEdge::count = 0;

OpenEdge::OpenEdge() {
	patchNum= -1;
	open=TRUE;
	onSel=FALSE;
	attachedTo=FALSE;
	reVectored=FALSE;
	average = FALSE;
	}

class Welds {
	public:
		int v;
		IntTab patches;
		BOOL onSel;
		BOOL welded;
		Welds() { v = -1; onSel = FALSE; welded = FALSE; }
		void AddPatch(PatchMesh *patch, int index);
		BOOL OnPatch(int index);
		BOOL CommonPatch(IntTab &p);
		BOOL WeldTo(PatchMesh *patch, OpenEdge *opens, Welds &target, BOOL averaged);
	};

void Welds::AddPatch(PatchMesh *patch, int index) {
	if(index < 0)
		return;
	int count = patches.Count();
	for(int i = 0; i < count; ++i) {
		if(patches[i] == index)
			return;
		}
	patches.Append(1,&index);
	if(patch->patchSel[index])
		onSel = TRUE;
	}

BOOL Welds::OnPatch(int index) {
	if(index < 0)
		return FALSE;
	int count = patches.Count();
	for(int i = 0; i < count; ++i) {
		if(patches[i] == index)
			return TRUE;
		}
	return FALSE;
	}

BOOL Welds::CommonPatch(IntTab &p) {
	int count = p.Count();
	for(int i = 0; i < count; ++i) {
		if(OnPatch(p[i]))
			return TRUE;
		}
	return FALSE;
	}

BOOL Welds::WeldTo(PatchMesh *patch, OpenEdge *opens, Welds &target, BOOL averaged) {
	int to = target.v;

	if(welded) {
		assert(0);
		return FALSE;
		}
#ifdef DBGWELD_ACTION
DebugPrint("Welding vertex %d - %d\n",v,to);
#endif
	// Make sure that welding these won't result in a singularity
	BOOL OK = FALSE;
	for(int i = 0; i < opens->count; ++i) {
		if(opens[i].open) {
			OK = TRUE;
			if(opens[i].v1 == v && opens[i].v2 == to) {
#ifdef DBGWELD_ACTION
DebugPrint("Weld cancelled -- Edge %d (%d-%d) would collapse!\n",i,v,to);
#endif				
				return FALSE;		// Singularity!
				}
			if(opens[i].v2 == v && opens[i].v1 == to) {
#ifdef DBGWELD_ACTION
DebugPrint("Weld cancelled -- Edge %d (%d-%d) would collapse!\n",i,to,v);
#endif				
				return FALSE;		// Singularity!
				}
			}
		}
	if(!OK)
		return FALSE;

	// Make sure the "from" and "to" vertices are on an open edge
	BOOL fromWasOnOpen = FALSE, toWasOnOpen = FALSE;
	for(i = 0; i < opens->count; ++i) {
		OpenEdge &edge = opens[i];
		if(edge.open) {
			if(edge.v1 == edge.v2) {
				DebugPrint("*** POSSIBLE ERROR IN OPEN EDGES ***\n");
				}
			// Make sure the "to" vertex is on an open edge!
			if(!toWasOnOpen && (edge.v1 == to || edge.v2 == to)) {
				toWasOnOpen = TRUE;
				if(fromWasOnOpen)
					goto both_ok;
				}
			// Make sure the "from" vertex is on an open edge!
			if(!fromWasOnOpen && (edge.v1 == v || edge.v2 == v)) {
				fromWasOnOpen = TRUE;
				if(toWasOnOpen)
					goto both_ok;
				}
			}
		}
#ifdef DBGWELD_ACTION
DebugPrint("Neither (%d - %d) not on open edge -- ignored\n",v, to);
#endif
	return FALSE;

	// Replace this vertex in the open edges list
	both_ok:
	for(i = 0; i < opens->count; ++i) {
		OpenEdge &edge = opens[i];
		if(edge.v1 == v) {
#ifdef DBGWELD_ACTION
DebugPrint("Replaced open edge %d vertex1 %d with %d\n",i,v,to);
#endif
			edge.v1 = to;
			}
		else
		if(edge.v2 == v) {
#ifdef DBGWELD_ACTION
DebugPrint("Replaced open edge %d vertex2 %d with %d\n",i,v,to);
#endif
			edge.v2 = to;
			}
		}

	// Mark any edges that were closed as a result of the weld
	BOOL edgeClosed = FALSE;
	for(i = 0; i < opens->count; ++i) {
		OpenEdge &o1 = opens[i];
		if(!o1.open) continue;
		for(int j = i+1; j < opens->count; ++j) {
			OpenEdge &o2 = opens[j];
			if(!o2.open) continue;
			if(o1.onSel) {
				if(o1.v1 == o2.v1 && o1.v2 == o2.v2) {
#ifdef DBGWELD_ACTION
DebugPrint("Edge %d (%d-%d %d-%d) replaced by edge %d (%d-%d %d-%d) (A)\n",
	j,o2.v1,o2.vec12,o2.vec21,o2.v2,i,o1.v1,o1.vec12,o1.vec21,o1.v2);
#endif
if(!o1.open)
	assert(0);
					o1.open = o2.open = FALSE;
					if(!o1.attachedTo) {
						o2.vec12 = o1.vec12;
						o2.vec21 = o1.vec21;
						o1.attachedTo = TRUE;
						o2.reVectored = TRUE;
						if(o2.onSel)
							o1.average = o2.average = TRUE;
						}
					edgeClosed = TRUE;
					}
				else
				if(o1.v1 == o2.v2 && o1.v2 == o2.v1) {
#ifdef DBGWELD_ACTION
DebugPrint("Edge %d (%d-%d %d-%d) replaced by edge %d (%d-%d %d-%d) (B)\n",
	j,o2.v1,o2.vec12,o2.vec21,o2.v2,i,o1.v1,o1.vec12,o1.vec21,o1.v2);
#endif
if(!o1.open)
	assert(0);
					o1.open = o2.open = FALSE;
					if(!o1.attachedTo) {
						o2.vec12 = o1.vec21;
						o2.vec21 = o1.vec12;
						o1.attachedTo = TRUE;
						o2.reVectored = TRUE;
						if(o2.onSel)
							o1.average = o2.average = TRUE;
						}
					edgeClosed = TRUE;
					}
				}
			else {
				if(o1.v1 == o2.v1 && o1.v2 == o2.v2) {
#ifdef DBGWELD_ACTION
DebugPrint("Edge %d (%d-%d %d-%d) replaced by edge %d (%d-%d %d-%d) (C)\n",
	i,o1.v1,o1.vec12,o1.vec21,o1.v2,j,o2.v1,o2.vec12,o2.vec21,o2.v2);
#endif
if(!o1.open)
	assert(0);
					o1.open = o2.open = FALSE;
					if(!o2.attachedTo) {
						o1.vec12 = o2.vec12;
						o1.vec21 = o2.vec21;
						o2.attachedTo = TRUE;
						o1.reVectored = TRUE;
						if(!o2.onSel)
							o1.average = o2.average = TRUE;
						}
					edgeClosed = TRUE;
					}
				else
				if(o1.v1 == o2.v2 && o1.v2 == o2.v1) {
#ifdef DBGWELD_ACTION
DebugPrint("Edge %d (%d-%d %d-%d) replaced by edge %d (%d-%d %d-%d) (D)\n",
	i,o1.v1,o1.vec12,o1.vec21,o1.v2,j,o2.v1,o2.vec12,o2.vec21,o2.v2);
#endif
if(!o1.open)
	assert(0);
					o1.open = o2.open = FALSE;
					if(!o2.attachedTo) {
						o1.vec12 = o2.vec21;
						o1.vec21 = o2.vec12;
						o2.attachedTo = TRUE;
						o1.reVectored = TRUE;
						if(!o2.onSel)
							o1.average = o2.average = TRUE;
						}
					edgeClosed = TRUE;
					}
				}
			}
		}

	// Replace this vertex in all the patches which use it
	int count = patch->getNumPatches();
	for(i = 0; i < count; ++i) {
		Patch &p = patch->patches[i];
		for(int j = 0; j < p.type; ++j) {
			if(p.v[j] == v) {
#ifdef DBGWELD_ACTION
DebugPrint("Replacing vertex %d in patch %d with %d\n",p.v[j],i,to);
#endif
				p.v[j] = to;
				welded = TRUE;
				}
			}
		}

	// Add the patches that this vertex is on to the target vertex's list
	for(i = 0; i < patches.Count(); ++i)
		target.AddPatch(patch, patches[i]);
	// Add the patches of the target to this one!
	for(i = 0; i < target.patches.Count(); ++i)
		AddPatch(patch, target.patches[i]);

	if(!welded)
		assert(0);	// OOPS!
	
	return TRUE;
	}

static BOOL WeldPatch(PatchMesh *patch, float thresh, BOOL weldIdentical, int startVert) {
	// Build a list of weld candidates
	// These are selected vertices on edges used by only one patch
	int patches = patch->getNumPatches();
	int verts = patch->getNumVerts();
	int vecs = patch->getNumVecs();
	int edges = patch->getNumEdges();
	int candidates = 0;
	int openEdges = 0;
	BitArray vSel = patch->vertSel;
	BitArray vUsed(verts);
	vUsed.ClearAll();
	for(int i = 0; i < edges; ++i) {
		PatchEdge &edge = patch->edges[i];
		if(edge.patch2 < 0) {
			openEdges++;				// Keep track of the open edges
			int v1 = edge.v1;
			int v2 = edge.v2;
			if(startVert > 0) {
				if(!vUsed[v1] && v1 >= startVert)
					candidates++;
				if(!vUsed[v2] && v2 >= startVert)
					candidates++;
				}
			else {
				if(!vUsed[v1] && vSel[v1])
					candidates++;
				if(!vUsed[v2] && vSel[v2])
					candidates++;
				}
			vUsed.Set(v1);
			vUsed.Set(v2);
			}
		}
#ifdef DBGWELD_ACTION
DebugPrint("%d candidates found\n",candidates);
#endif
	if(!candidates)
		return FALSE;

	OpenEdge *opens = new OpenEdge [openEdges];
	opens.count = openEdges;
		
	int *candIx = new int [verts];
	for(i = 0; i < verts; ++i)
		candIx[i] = -1;

	Welds *weldList = new Welds [candidates];
	int candidate = 0;
	int open = 0;
	vUsed.ClearAll();

	// Make a list of the candidates and the edge patches which use them
	for(i = 0; i < edges; ++i) {
		PatchEdge &edge = patch->edges[i];
		if(edge.patch2 < 0) {
			if(patch->patchSel[edge.patch1])
				opens[open].onSel = TRUE;
			opens[open++] = edge;
			int v1 = edge.v1;
			int v2 = edge.v2;
			if(startVert > 0) {
				if(v1 >= startVert) {
					if(!vUsed[v1]) {
						candIx[v1] = candidate;
						weldList[candidate++].v = v1;
						vUsed.Set(v1);
						}
					weldList[candIx[v1]].AddPatch(patch,edge.patch1);
					}
				if(v2 >= startVert) {
					if(!vUsed[v2]) {
						candIx[v2] = candidate;
						weldList[candidate++].v = v2;
						vUsed.Set(v2);
						}
					weldList[candIx[v2]].AddPatch(patch,edge.patch1);
					}
				}
			else {
				if(vSel[v1]) {
					if(!vUsed[v1]) {
						candIx[v1] = candidate;
						weldList[candidate++].v = v1;
						vUsed.Set(v1);
						}
					weldList[candIx[v1]].AddPatch(patch,edge.patch1);
					}
				if(vSel[v2]) {
					if(!vUsed[v2]) {
						candIx[v2] = candidate;
						weldList[candidate++].v = v2;
						vUsed.Set(v2);
						}
					weldList[candIx[v2]].AddPatch(patch,edge.patch1);
					}
				}
			}
		}

#ifdef DBGWELD_ACTION
DebugPrint("%d candidates recorded\n",candidate);
assert(candidate == candidates);
#endif

	// We now have a list of candidate vertices and the patches they're used by.
	// Go thru and weld them together if they're not on the same patch

#ifdef DBGWELD_DUMP
DebugPrint("Before:\n");
if(GetKeyState(VK_CONTROL) & 0x80000000)
	patch->Dump();
#endif
	for(i = 0; i < candidates; ++i) {
		Welds &w1 = weldList[i];
		if(w1.welded) continue;
		Point3 &p1 = patch->verts[w1.v].p;
		for(int j = i+1; j < candidates; ++j) {
			Welds &w2 = weldList[j];
			if(w2.welded) continue;
			Point3 &p2 = patch->verts[w2.v].p;
			// Are they within weld range?
			if((weldIdentical && (p1 == p2)) || (!weldIdentical && (Length(p1-p2) <= thresh))) {
				// Make sure they aren't used by the same patch!
				if(!w1.CommonPatch(w2.patches)) {
					// (maybe) Ok to weld!  If one is on a selected patch, weld the other to it
					if(w1.onSel && !w2.onSel)
						w2.WeldTo(patch, opens, w1, FALSE);
					else
					if(w2.onSel && !w1.onSel) {
						w1.WeldTo(patch, opens, w2, FALSE);
						break;	// w1 is now welded, move on to the next one
						}
					else {	// Neither or both on a selected patch, arbitrarily weld and compute midpoint
						if(w2.WeldTo(patch, opens, w1, TRUE))
							p1 = (p1 + p2) / 2.0f;
						}
					}
				}
			}
		}
#ifdef DBGWELD_DUMP
DebugPrint("Intermediate 1:\n");
if(GetKeyState(VK_CONTROL) & 0x80000000)
	patch->Dump();
#endif

	// Now go thru all edges and fix up edges where both verts were welded
	for(int oe = 0; oe < openEdges; ++oe) {
		OpenEdge &open = opens[oe];
		if(open.reVectored) {
			Patch &p = patch->patches[open.patchNum];
 			for(int j = 0; j < p.type; ++j) {
				int k = (j + 1) % p.type;
				int v1 = p.v[j];
				int v2 = p.v[k];
				int vec12 = j * 2;
				int vec21 = vec12 + 1;
				Point3 &pv12 = patch->vecs[p.vec[vec12]].p;
				Point3 &pv21 = patch->vecs[p.vec[vec21]].p;
				Point3 &ov12 = patch->vecs[open.vec12].p;
				Point3 &ov21 = patch->vecs[open.vec21].p;
				if(v1 == open.v1 && v2 == open.v2) {
					if(open.average) {
						ov12 = (ov12 + pv12) / 2.0f;
						ov21 = (ov21 + pv21) / 2.0f;
						}
					p.vec[vec12] = open.vec12;
					p.vec[vec21] = open.vec21;
					goto next_openedge;
					}
				else
				if(v1 == open.v2 && v2 == open.v1) {
					if(open.average) {
						ov12 = (ov12 + pv21) / 2.0f;
						ov21 = (ov21 + pv12) / 2.0f;
						}
					p.vec[vec12] = open.vec21;
					p.vec[vec21] = open.vec12;
					goto next_openedge;
					}
				}
assert(0);
			}
		next_openedge:;
		}

#ifdef DBGWELD_DUMP
DebugPrint("Intermediate 2:\n");
if(GetKeyState(VK_CONTROL) & 0x80000000)
	patch->Dump();
#endif
	// All the welding is done.  Build a bitarray marking which verts have been welded
	BitArray welded(verts);
	welded.ClearAll();

	BOOL didWeld = FALSE;

	for(i = 0; i < candidates; ++i) {
		if(weldList[i].welded) {
			welded.Set(weldList[i].v);
			didWeld = TRUE;
			}
		}
#ifdef DBGWELD_ACTION
DebugPrint("%d verts welded out of existence\n",welded.NumberSet());
#endif

	BitArray dummy(patches);
	dummy.ClearAll();
	DeletePatchParts(patch, welded, dummy);
	patch->computeInteriors();
#ifdef DBGWELD_DUMP
DebugPrint("After:\n");
if(GetKeyState(VK_CONTROL) & 0x80000000)
	patch->Dump();
#endif
	delete [] opens;
	delete [] candIx;
	delete [] weldList;

	return didWeld;
	}

BOOL PVertWeldRecord::Redo(PatchMesh *patch,int reRecord) {
	if(reRecord)
		oldPatch = *patch;
	WeldPatch(patch, thresh);
	return TRUE;
	}

#define WELDR_THRESH_CHUNK			0x1010
#define WELDR_PATCH_CHUNK			0x1000

IOResult PVertWeldRecord::Save(ISave *isave) {
	ULONG nb;
	isave->BeginChunk(MODREC_GENERAL_CHUNK);
	isave->Write(&groupNumber,sizeof(int),&nb);
	isave->Write(&serialNumber,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(WELDR_THRESH_CHUNK);
	isave->Write(&thresh,sizeof(float),&nb);
	isave->	EndChunk();
	isave->BeginChunk(WELDR_PATCH_CHUNK);
	oldPatch.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PVertWeldRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	propagate = FALSE;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case MODREC_GENERAL_CHUNK:
				res = iload->Read(&groupNumber,sizeof(int),&nb);
				res = iload->Read(&serialNumber,sizeof(int),&nb);
				break;
			case WELDR_THRESH_CHUNK:
				res = iload->Read(&thresh,sizeof(float),&nb);
				break;
			case WELDR_PATCH_CHUNK:
				res = oldPatch.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

/*-------------------------------------------------------------------*/

PatchRestore::PatchRestore(EditPatchData* pd, EditPatchMod* mod, PModRecord *rec)
	{
	patchData = pd;
	this->mod = mod;
	this->rec = rec;
	t = mod->iObjParams->GetTime();
	rec->SetRestore(this);
	iOwn = FALSE;			// Originally, this is owned by the modifier
	}

PatchRestore::~PatchRestore() {
	if(rec) {
		rec->ClearRestore();
		if(iOwn) {
			delete rec;
			rec = NULL;
			}
		}
	}

void PatchRestore::Restore(int isUndo)
	{
	// If no record attached, forget it
	if(!rec)
		return;

	BOOL ok = TRUE;
	if ( patchData->tempData && patchData->TempData(mod)->PatchCached(t) ) {
		PatchMesh *patch = patchData->TempData(mod)->GetPatch(t);
		if(patch)
			ok = rec->Undo(patch);
		patchData->TempData(mod)->Invalidate(rec->Parts());
		Cancel2StepPatchModes(mod->iObjParams);
		}
	else
	if ( patchData->tempData ) {
		patchData->TempData(mod)->Invalidate(rec->Parts(),FALSE);
		}
	if(isUndo) {
		iOwn = TRUE;
		patchData->RemoveChangeRecord();
		if(!ok) {
DebugPrint("Restore incompatible, getting rid of record\n");
			rec->ClearRestore();
			delete rec;
			rec = NULL;
			}
		}
	if(rec)
		mod->NotifyDependents(FOREVER, rec->Parts(), REFMSG_CHANGE);
	}

void PatchRestore::Redo()
	{
	// If no record attached, forget it
	if(!rec)
		return;

	BOOL ok = TRUE;
	if ( patchData->tempData && patchData->TempData(mod)->PatchCached(t) ) {
		PatchMesh *patch = patchData->TempData(mod)->GetPatch(t);
		if(patch)
			ok = rec->Redo(patch,0);
		patchData->TempData(mod)->Invalidate(rec->Parts());
		Cancel2StepPatchModes(mod->iObjParams);
		}
	else
	if ( patchData->tempData ) {
		patchData->TempData(mod)->Invalidate(rec->Parts(),FALSE);
		}
	if(iOwn) {
		iOwn = FALSE;
		if(ok)
			patchData->AdoptChangeRecord(rec);
		else {
DebugPrint("Redo incompatible, getting rid of record\n");
			rec->ClearRestore();
			delete rec;
			rec = NULL;
			}
		}
	if(rec)
		mod->NotifyDependents(FOREVER, rec->Parts(), REFMSG_CHANGE);
	}

/*-------------------------------------------------------------------*/

BOOL PickPatchAttach::Filter(INode *node)
	{
	ModContextList mcList;		
	INodeTab nodes;
	if (node) {
		ObjectState os = node->GetObjectRef()->Eval(ep->iObjParams->GetTime());
		GeomObject *object = (GeomObject *)os.obj;
		// Make sure it isn't one of the nodes we're editing, for heaven's sake!
		ep->iObjParams->GetModContexts(mcList,nodes);
		int numNodes = nodes.Count();
		for(int i = 0; i < numNodes; ++i) {
			if(nodes[i] == node) {
				nodes.DisposeTemporary();
				return FALSE;
				}
			}
		if(object->CanConvertToType(patchObjectClassID)) {
			nodes.DisposeTemporary();
			return TRUE;
			}
		}
	nodes.DisposeTemporary();
	return FALSE;
	}

BOOL PickPatchAttach::HitTest(
		IObjParam *ip,HWND hWnd,ViewExp *vpt,IPoint2 m,int flags)
	{	
	INode *node = ip->PickNode(hWnd,m,this);
	ModContextList mcList;		
	INodeTab nodes;
	
	if (node) {
		ObjectState os = node->GetObjectRef()->Eval(ip->GetTime());
		GeomObject *object = (GeomObject *)os.obj;
		// Make sure it isn't one of the nodes we're editing, for heaven's sake!
		ep->iObjParams->GetModContexts(mcList,nodes);
		int numNodes = nodes.Count();
		for(int i = 0; i < numNodes; ++i) {
			if(nodes[i] == node) {
				nodes.DisposeTemporary();
				return FALSE;
				}
			}
		if(object->CanConvertToType(patchObjectClassID)) {
			nodes.DisposeTemporary();
			return TRUE;
			}
		}

	nodes.DisposeTemporary();
	return FALSE;
	}

BOOL PickPatchAttach::Pick(IObjParam *ip,ViewExp *vpt)
	{
	INode *node = vpt->GetClosestHit();
	assert(node);
	GeomObject *object = (GeomObject *)node->GetObjectRef()->Eval(ip->GetTime()).obj;
	if(object->CanConvertToType(patchObjectClassID)) {
		PatchObject *attPatch = (PatchObject *)object->ConvertToType(ip->GetTime(),patchObjectClassID);
		if(attPatch) {
			PatchMesh patch = attPatch->patch;
			theHold.Begin();
			ep->DoAttach(node, &patch);
			theHold.Accept(GetString(IDS_TH_ATTACH));
			// Discard the copy it made, if it isn't the same as the object itself
			if(attPatch != (PatchObject *)object)
				delete attPatch;
			}
		}
	return FALSE;
	}


void PickPatchAttach::EnterMode(IObjParam *ip)
	{
	if ( ep->hEditPatchParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(ep->hEditPatchParams,IDC_ATTACH));
		but->SetCheck(TRUE);
		ReleaseICustButton(but);
		}
	}

void PickPatchAttach::ExitMode(IObjParam *ip)
	{
	if ( ep->hEditPatchParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(ep->hEditPatchParams,IDC_ATTACH));
		but->SetCheck(FALSE);
		ReleaseICustButton(but);
		}
	}

HCURSOR PickPatchAttach::GetHitCursor(IObjParam *ip) {
	return LoadCursor(hInstance, MAKEINTRESOURCE(IDC_ATTACHCUR));
	}

int EditPatchMod::DoAttach(INode *node, PatchMesh *attPatch) {
	ModContextList mcList;	
	INodeTab nodes;	

	if ( !iObjParams ) return 0;

	iObjParams->GetModContexts(mcList,nodes);

	if(mcList.Count() != 1) {
		nodes.DisposeTemporary();
		return 0;
		}

	EditPatchData *patchData = (EditPatchData*)mcList[0]->localData;
	if ( !patchData ) {
		nodes.DisposeTemporary();
		return 0;
		}
	patchData->BeginEdit(iObjParams->GetTime());

	// If the mesh isn't yet cached, this will cause it to get cached.
	PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
	if(!patch) {
		nodes.DisposeTemporary();
		return 0;
		}

	RecordTopologyTags();
	// Transform the shape for attachment:
	// If reorienting, just translate to align pivots
	// Otherwise, transform to match our transform
	Matrix3 attMat(1);
	if(attachReorient) {
		Matrix3 thisTM = nodes[0]->GetNodeTM(iObjParams->GetTime());
		Matrix3 thisOTMBWSM = nodes[0]->GetObjTMBeforeWSM(iObjParams->GetTime());
		Matrix3 thisPivTM = thisTM * Inverse(thisOTMBWSM);
		Matrix3 otherTM = node->GetNodeTM(iObjParams->GetTime());
		Matrix3 otherOTMBWSM = node->GetObjTMBeforeWSM(iObjParams->GetTime());
		Matrix3 otherPivTM = otherTM * Inverse(otherOTMBWSM);
		Point3 otherObjOffset = node->GetObjOffsetPos();
		attMat = Inverse(otherPivTM) * thisPivTM;
		}
	else {
		attMat = node->GetObjectTM(iObjParams->GetTime()) *
			Inverse(nodes[0]->GetObjectTM(iObjParams->GetTime()));
		}
		
	// RB 3-17-96 : Check for mirroring
	AffineParts parts;
	decomp_affine(attMat,&parts);
	if (parts.f<0.0f) {
		int v[8], ct, ct2, j;
		Point3 p[9];

		for (int i=0; i<attPatch->numPatches; i++) {
			
			// Re-order vertices
			ct = attPatch->patches[i].type==PATCH_QUAD ? 4 : 3;
			for (j=0; j<ct; j++) {
				v[j] = attPatch->patches[i].v[j];
				}
			for (j=0; j<ct; j++) {
				attPatch->patches[i].v[j] = v[ct-j-1];
				}

			// Re-order vecs
			ct  = attPatch->patches[i].type==PATCH_QUAD ? 8 : 6;
			ct2 = attPatch->patches[i].type==PATCH_QUAD ? 5 : 3;
			for (j=0; j<ct; j++) {
				v[j] = attPatch->patches[i].vec[j];
				}
			for (j=0; j<ct; j++,ct2--) {
				if (ct2<0) ct2 = ct-1;
				attPatch->patches[i].vec[j] = v[ct2];
				}

			// Re-order enteriors
			if (attPatch->patches[i].type==PATCH_QUAD) {
				ct = 4;
				for (j=0; j<ct; j++) {
					v[j] = attPatch->patches[i].interior[j];
					}
				for (j=0; j<ct; j++) {
					attPatch->patches[i].interior[j] = v[ct-j-1];
					}
				}

			// Re-order aux
			if (attPatch->patches[i].type==PATCH_TRI) {
				ct = 9;
				for (j=0; j<ct; j++) {
					p[j] = attPatch->patches[i].aux[j];
					}
				for (j=0; j<ct; j++) {
					attPatch->patches[i].aux[j] = p[ct-j-1];
					}
				}

			// Re-order TV faces if present
			for(int chan = 0; chan < PATCH_TEXTURE_CHANNELS; ++chan) {
				if (attPatch->tvPatches[chan]) {
					ct = 4;
					for (j=0; j<ct; j++) {
						v[j] = attPatch->tvPatches[chan][i].tv[j];
						}
					for (j=0; j<ct; j++) {
						attPatch->tvPatches[chan][i].tv[j] = v[ct-j-1];
						}
					}
				}
			}
		}

	for(int i = 0; i < attPatch->numVerts; ++i)
		attPatch->verts[i].p = attPatch->verts[i].p * attMat;
	for(i = 0; i < attPatch->numVecs; ++i)
		attPatch->vecs[i].p = attPatch->vecs[i].p * attMat;
	attPatch->computeInteriors();

	// Combine the materials of the two nodes.
	int c1=0, c2=0;	
	if (nodes[0]->GetMtl() && node->GetMtl()) {
		if (nodes[0]->GetMtl() != node->GetMtl()) {
			Mtl *multi = CombineMaterials(
				nodes[0]->GetMtl(), node->GetMtl(), c1, c2);
			nodes[0]->SetMtl(multi);
			
			theHold.Accept(GetString(IDS_TH_ATTACH));
			GetSystemSetting(SYSSET_CLEAR_UNDO);
			theHold.Begin();
			}
	} else if (node->GetMtl()) {
		nodes[0]->SetMtl(node->GetMtl());
		
		theHold.Accept(GetString(IDS_TH_ATTACH));
		GetSystemSetting(SYSSET_CLEAR_UNDO);
		theHold.Begin();
		}

	patchData->StartChangeGroup();
	PAttachRecord *rec = new PAttachRecord(patch, attPatch, c1);
	patchData->AddChangeRecord(rec);
	// Do the attach
	::DoAttach(patch, attPatch, c1);
	// Start a restore object...
	if ( theHold.Holding() ) {
		theHold.Put(new PatchRestore(patchData,this,rec));
		}
	patchData->TempData(this)->Invalidate(PART_TOPO|PART_GEOM);

	// Get rid of the original node
	iObjParams->DeleteNode(node);

	ResolveTopoChanges();

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO|PART_GEOM, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	return 1;
	}

/*-------------------------------------------------------------------*/

EditPatchMod::EditPatchMod()
	{
	selLevel = EP_VERTEX;
	displayLattice = TRUE;
	displaySurface = TRUE;
	EPropagate = PPropagate = TRUE;
	meshSteps = 5;
//	meshAdaptive = FALSE;	// Future use (Not used now)
	}

EditPatchMod::~EditPatchMod()
	{
	ClearSetNames();
	}

Interval EditPatchMod::LocalValidity(TimeValue t)
	{
	// Force a cache if being edited.
	if (TestAFlag(A_MOD_BEING_EDITED))
		return NEVER;  			   
	return FOREVER;
	}

RefTargetHandle EditPatchMod::Clone(RemapDir& remap) {
	EditPatchMod* newmod = new EditPatchMod();	
	newmod->selLevel = selLevel;
	newmod->displaySurface = displaySurface;
	newmod->displayLattice = displayLattice;
	newmod->meshSteps = meshSteps;
//	newmod->meshAdaptive = meshAdaptive;	// Future use (Not used now)
	newmod->viewTess = viewTess;
	newmod->prodTess = prodTess;
	newmod->EPropagate = EPropagate;
	newmod->PPropagate = PPropagate;
	return(newmod);
	}

void EditPatchMod::ClearPatchDataFlag(ModContextList& mcList,DWORD f)
	{
	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		patchData->SetFlag(f,FALSE);
		}
	}

void EditPatchMod::XFormHandles(XFormProc *xproc, TimeValue t, Matrix3& partm, Matrix3& tmAxis,	int object, int handleIndex)
	{	
	ModContextList mcList;		
	INodeTab nodes;
	Matrix3 mat,imat,theMatrix;
	Interval valid;
	int numAxis;
	Point3 oldpt,newpt,oldin,oldout,rel;
	BOOL shiftPressed = FALSE;
	static BOOL wasBroken;
	Point3 theKnot;
	Point3 oldVector;
	Point3 newVector;
	float oldLen;
	float newLen;
	float lengthRatio;
//DebugPrint("XFormHandles\n");
	shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) ? TRUE : FALSE;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	numAxis = iObjParams->GetNumAxis();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	EditPatchData *patchData = (EditPatchData*)mcList[object]->localData;
	if ( !patchData ) {
		nodes.DisposeTemporary();
		return;
		}
	
	// If the mesh isn't yet cache, this will cause it to get cached.
	PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
	if(!patch) {
		nodes.DisposeTemporary();
		return;
		}
			
	// If this is the first edit, then the delta arrays will be allocated
	patchData->BeginEdit(t);

	// Create a change record for this object and store a pointer to its delta info in this EditPatchData
	if(!TestAFlag(A_HELD)) {
		PVertMoveRecord *rec = new PVertMoveRecord();
		rec->delta.SetSize(*patch,FALSE);
		patchData->vdelta = &rec->delta;
		patchData->StartChangeGroup();
		patchData->AddChangeRecord(rec);
		if ( theHold.Holding() ) {
			theHold.Put(new PatchRestore(patchData,this,rec));
			}
		patchData->vdelta->Zero();		// Reset all deltas
		patchData->ClearHandleFlag();
		wasBroken = FALSE;
		}
	else {
		if(wasBroken && !shiftPressed)
			wasBroken = FALSE;
		if(patchData->DoingHandles())
			patchData->ApplyHandlesAndZero(*patch);		// Reapply the slave handle deltas
		else
			patchData->vdelta->Zero();
		}

	patchData->SetHandleFlag(handleIndex);
	int primaryKnot = patch->vecs[handleIndex].vert;
	Point3Tab &pDeltas = patchData->vdelta->dtab.vtab;

	tmAxis = iObjParams->GetTransformAxis(nodes[object],primaryKnot);
	mat    = nodes[object]->GetObjectTM(t,&valid) * Inverse(tmAxis);
	imat   = Inverse(mat);
	xproc->SetMat(mat);
				
	// XForm the cache vertices
	oldpt = patch->vecs[handleIndex].p;
	newpt = xproc->proc(oldpt,mat,imat);

	// Update the vector being moved
	patch->vecs[handleIndex].p = newpt;

	// Move the delta's vertices.
	patchData->vdelta->SetVec(handleIndex,newpt - oldpt);

	if(primaryKnot >= 0) {
		PatchVert &vert = patch->verts[primaryKnot];
		theKnot = vert.p;
		// If locked handles, turn the movement into a transformation matrix
		// and transform all the handles attached to the owner vertex
		if(lockedHandles) {
			if(!wasBroken && shiftPressed)
				wasBroken = TRUE;
			oldVector = oldpt - theKnot;
			newVector = newpt - theKnot;
			oldLen = Length(oldVector);
			newLen = Length(newVector);
			int allNew = (oldLen == 0.0f) ? 1 : 0;		// All new vector?
			lengthRatio = 1.0f;
			if(!allNew)
				lengthRatio = newLen / oldLen;
			Point3 oldNorm = Normalize(oldVector);
			Point3 newNorm = Normalize(newVector);
			theMatrix.IdentityMatrix();
			if(oldNorm != newNorm) {
				// Get a matrix that will transform the old point to the new one
				// Cross product gives us the normal of the rotational axis
				Point3 axis = Normalize(oldNorm ^ newNorm);
				// Dot product gives us the angle
				float dot = DotProd(oldNorm, newNorm);
				if(dot >= -1.0f && dot <= 1.0f) {
					float angle = (float)-acos(dot);
					// Now let's build a matrix that'll do this for us!
					Quat quat = QFromAngAxis(angle, axis);
					quat.MakeMatrix(theMatrix);

					// If need to break the vector, 
					if(shiftPressed && vert.flags & PVERT_COPLANAR) {
						vert.flags &= ~PVERT_COPLANAR;
						patchData->vdelta->SetVertType(primaryKnot,PVERT_COPLANAR);
						}
					}
				// Process all other handles through the matrix
				int owner = patch->vecs[handleIndex].vert;
				if(owner >= 0) {
					PatchVert &vert = patch->verts[owner];
					int vectors = vert.vectors.Count();
					for(int v = 0; v < vectors; ++v) {
						int theVec = vert.vectors[v];
						// Ignore the vector being moved!
						if(theVec != handleIndex) {
							Point3 oldpt2 = patch->vecs[theVec].p - pDeltas[theVec];
							Point3 newpt2 = (oldpt2 - theKnot) * theMatrix + theKnot;
							patch->vecs[theVec].p = newpt2;
							// Move the delta's vertices.
							patchData->vdelta->SetVec(theVec,newpt2 - oldpt2);
							}
						}
					}
				}
			}
		else {
			if(shiftPressed) {
				wasBroken = TRUE;
				vert.flags &= ~PVERT_COPLANAR;
				// Need to record this for undo!
				patchData->vdelta->SetVertType(primaryKnot,PVERT_COPLANAR);
				}
			// If a coplanar knot, do the other vectors!
			if(vert.flags & PVERT_COPLANAR && vert.vectors.Count() > 2) {
				oldVector = oldpt - theKnot;
				newVector = newpt - theKnot;
				oldLen = Length(oldVector);
				newLen = Length(newVector);
				int allNew = (oldLen == 0.0f) ? 1 : 0;		// All new vector?
				lengthRatio = 1.0f;
				if(!allNew)
					lengthRatio = newLen / oldLen;
				Point3 oldNorm = Normalize(oldVector);
				Point3 newNorm = Normalize(newVector);
				theMatrix.IdentityMatrix();
				if(oldNorm != newNorm) {
					// Get a matrix that will transform the old point to the new one
					// Cross product gives us the normal of the rotational axis
					Point3 axis = Normalize(oldNorm ^ newNorm);
					// Dot product gives us the angle
					float dot = DotProd(oldNorm, newNorm);
					if(dot >= -1.0f && dot <= 1.0f) {
						float angle = (float)-acos(dot);
						// Now let's build a matrix that'll do this for us!
						Quat quat = QFromAngAxis(angle, axis);
						quat.MakeMatrix(theMatrix);
						}
					// Process all other handles through the matrix
					int owner = patch->vecs[handleIndex].vert;
					if(owner >= 0) {
						PatchVert &vert = patch->verts[owner];
						int vectors = vert.vectors.Count();
						for(int v = 0; v < vectors; ++v) {
							int theVec = vert.vectors[v];
							// Ignore the vector being moved!
							if(theVec != handleIndex) {
								Point3 oldpt2 = patch->vecs[theVec].p - pDeltas[theVec];
								Point3 newpt2 = (oldpt2 - theKnot) * theMatrix + theKnot;
								patch->vecs[theVec].p = newpt2;
								// Move the delta's vertices.
								patchData->vdelta->SetVec(theVec,newpt2 - oldpt2);
								}
							}
						}
					}
				}
			}
		}

	// Really only need to do this if neighbor knots are non-bezier
	patch->computeInteriors();

	patchData->TempData(this)->Invalidate(PART_GEOM);
	patchData->SetFlag(EPD_BEENDONE,TRUE);
	
	// Mark all objects in selection set
	SetAFlag(A_HELD);
	
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_GEOM, REFMSG_CHANGE);
	}

void EditPatchMod::XFormVerts( 
		XFormProc *xproc, 
		TimeValue t, 
		Matrix3& partm, 
		Matrix3& tmAxis  ) 
	{	
	ModContextList mcList;		
	INodeTab nodes;
	Matrix3 mat,imat;	
	Interval valid;
	int numAxis;
	Point3 oldpt,newpt,rel,delta;
	int shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) ? 1 : 0;
	static BOOL wasBroken;
	static BOOL handleEdit = FALSE;
	static int handleObject;
	static int handleIndex;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	numAxis = iObjParams->GetNumAxis();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	if(!TestAFlag(A_HELD)) {
		handleEdit = FALSE;
//DebugPrint("Handle edit cleared\n");
		// Check all patches to see if they are altering a bezier vector handle...
		if(selLevel == EP_VERTEX) {
			for ( int i = 0; i < mcList.Count(); i++ ) {
				EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
				if ( !patchData ) continue;
				if ( patchData->GetFlag(EPD_BEENDONE) ) continue;
		
				// If the mesh isn't yet cache, this will cause it to get cached.
				PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
				if(!patch)
					continue;

				if(!iObjParams->SelectionFrozen() && patch->bezVecVert >= 0) {
					// Editing a bezier handle -- Go do it!
					handleEdit = TRUE;
					handleObject = i;
					handleIndex = patch->bezVecVert;
					goto edit_handles;
					}
	 			patchData->SetFlag(EPD_BEENDONE,TRUE);
				}
			}
		}
	
	// If editing the handles, cut to the chase!
	if(handleEdit) {
		edit_handles:
		XFormHandles(xproc, t, partm, tmAxis, handleObject, handleIndex);
		nodes.DisposeTemporary();
		return;
		}

	// Not doing handles, just plain ol' verts
	ClearPatchDataFlag(mcList,EPD_BEENDONE);	// Clear these out again
	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
					
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// Create a change record for this object and store a pointer to its delta info in this EditPatchData
		if(!TestAFlag(A_HELD)) {
			PVertMoveRecord *rec = new PVertMoveRecord();
			rec->delta.SetSize(*patch,FALSE);
			patchData->vdelta = &rec->delta;
			patchData->StartChangeGroup();
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() ) {
				theHold.Put(new PatchRestore(patchData,this,rec));
				}
			patchData->vdelta->Zero();		// Reset all deltas
			patchData->ClearHandleFlag();
			wasBroken = FALSE;
			}
		else {
			if(wasBroken)
				shiftPressed = TRUE;
			if(patchData->DoingHandles())
				patchData->ApplyHandlesAndZero(*patch);		// Reapply the slave handle deltas
			else
				patchData->vdelta->Zero();
			}

		// Compute the transforms
		if (numAxis==NUMAXIS_INDIVIDUAL) {
			switch(selLevel) {
				case EP_VERTEX: {
					// Selected vertices - either directly or indirectly through selected faces or edges.
					BitArray sel = patch->VertexTempSel();
					int verts = patch->numVerts;
					for( int vert = 0; vert < verts; vert++ ) {
						if ( sel[vert] ) {
							tmAxis = iObjParams->GetTransformAxis(nodes[i],vert);
							mat    = nodes[i]->GetObjectTM(t,&valid) * Inverse(tmAxis);
							imat   = Inverse(mat);
							xproc->SetMat(mat);
			
							// XForm the cache vertices
							oldpt = patch->verts[vert].p;
							newpt = xproc->proc(oldpt,mat,imat);
							patch->verts[vert].p = newpt;
							delta = newpt - oldpt;

							// Move the delta's vertices.
							patchData->vdelta->MoveVert(vert,delta);

							// Also affect its vectors
							int vecs = patch->verts[vert].vectors.Count();
							for(int vec = 0; vec < vecs; ++vec) {
								int index = patch->verts[vert].vectors[vec];
								// XForm the cache vertices
								oldpt = patch->vecs[index].p;
								newpt = xproc->proc(oldpt,mat,imat);
								patch->vecs[index].p = newpt;
								delta = newpt - oldpt;

								// Move the delta's vertices.
								patchData->vdelta->MoveVec(index,delta);
								}
							}
						}
					patch->computeInteriors();	// Kind of broad-spectrum -- only need to recompute affected patches
					}
					break;
				case EP_EDGE:
				case EP_PATCH: {
					// Selected vertices - either directly or indirectly through selected faces or edges.
					BitArray sel = patch->VertexTempSel();
					int verts = patch->numVerts;
					for( int vert = 0; vert < verts; vert++ ) {
						if ( sel[vert] ) {
							tmAxis = iObjParams->GetTransformAxis(nodes[i],vert);
							mat    = nodes[i]->GetObjectTM(t,&valid) * Inverse(tmAxis);
							imat   = Inverse(mat);
							xproc->SetMat(mat);
			
							// XForm the cache vertices
							oldpt = patch->verts[vert].p;
							newpt = xproc->proc(oldpt,mat,imat);
							patch->verts[vert].p = newpt;
							delta = newpt - oldpt;

							// Move the delta's vertices.
							patchData->vdelta->MoveVert(vert,delta);

							// Also affect its vectors
							int vecs = patch->verts[vert].vectors.Count();
							for(int vec = 0; vec < vecs; ++vec) {
								int index = patch->verts[vert].vectors[vec];
								// XForm the cache vertices
								oldpt = patch->vecs[index].p;
								newpt = xproc->proc(oldpt,mat,imat);
								patch->vecs[index].p = newpt;
								delta = newpt - oldpt;

								// Move the delta's vertices.
								patchData->vdelta->MoveVec(index,delta);
								}
							}
						}
					patch->computeInteriors();
					}
					break;
				}			
			}
		else {
			mat = nodes[i]->GetObjectTM(t,&valid) * Inverse(tmAxis);
			imat = Inverse(mat);
			xproc->SetMat(mat);

			// Selected vertices - either directly or indirectly through selected faces or edges.
			BitArray sel = patch->VertexTempSel();
			int verts = patch->numVerts;
			for( int vert = 0; vert < verts; vert++ ) {
				if ( sel[vert] ) {
					// XForm the cache vertices
					oldpt = patch->verts[vert].p;
					newpt = xproc->proc(oldpt,mat,imat);
					patch->verts[vert].p = newpt;
					delta = newpt - oldpt;

					// Move the delta's vertices.
					patchData->vdelta->MoveVert(vert,delta);

					// Also affect its vectors
					int vecs = patch->verts[vert].vectors.Count();
					for(int vec = 0; vec < vecs; ++vec) {
						int index = patch->verts[vert].vectors[vec];
						// XForm the cache vertices
						oldpt = patch->vecs[index].p;
						newpt = xproc->proc(oldpt,mat,imat);
						patch->vecs[index].p = newpt;
						delta = newpt - oldpt;

						// Move the delta's vertices.
						patchData->vdelta->MoveVec(index,delta);
						}
					}
				}
			patch->computeInteriors();
			}
		patchData->TempData(this)->Invalidate(PART_GEOM);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	// Mark all objects in selection set
	SetAFlag(A_HELD);
	
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_GEOM, REFMSG_CHANGE);
	}

void EditPatchMod::Move( TimeValue t, Matrix3& partm, Matrix3& tmAxis, Point3& val, BOOL localOrigin )
	{
	MoveXForm proc(val);
	XFormVerts(&proc,t,partm,tmAxis); 	
	}

void EditPatchMod::Rotate( TimeValue t, Matrix3& partm, Matrix3& tmAxis, Quat& val, BOOL localOrigin )
	{
	RotateXForm proc(val);
	XFormVerts(&proc,t,partm,tmAxis); 	
	}

void EditPatchMod::Scale( TimeValue t, Matrix3& partm, Matrix3& tmAxis, Point3& val, BOOL localOrigin )
	{
	ScaleXForm proc(val);
	XFormVerts(&proc,t,partm,tmAxis); 	
	}

void EditPatchMod::TransformStart(TimeValue t)
	{
	if (iObjParams) iObjParams->LockAxisTripods(TRUE);
	}

void EditPatchMod::TransformFinish(TimeValue t)
	{
	if (iObjParams) iObjParams->LockAxisTripods(FALSE);
	}

void EditPatchMod::TransformCancel(TimeValue t)
	{
	if (iObjParams) iObjParams->LockAxisTripods(FALSE);
	}

void EditPatchMod::ModifyObject(TimeValue t, ModContext &mc, ObjectState * os, INode *node) 
	{		
//Alert(_T("in ModifyObject"));
	assert( os->obj->ClassID() == Class_ID(PATCHOBJ_CLASS_ID,0) );
//Alert(_T("ModifyObject class ID is OK"));
	
	PatchObject *patchOb = (PatchObject *)os->obj;
	EditPatchData *patchData;

	if ( !mc.localData ) {
		mc.localData = new EditPatchData(this);
		patchData = (EditPatchData*)mc.localData;
		meshSteps = patchData->meshSteps = patchOb->GetMeshSteps();
//		meshAdaptive = patchData->meshAdaptive = patchOb->GetAdaptive();	// Future use (Not used now)
		viewTess = patchData->viewTess = patchOb->GetViewTess();
		prodTess = patchData->prodTess = patchOb->GetProdTess();
		displayLattice = patchData->displayLattice = patchOb->showLattice;
		displaySurface = patchData->displaySurface = patchOb->showMesh;
	} else {
		patchData = (EditPatchData*)mc.localData;
		}

	PatchMesh &pmesh = patchOb->patch;	
	assert(pmesh.numVerts == pmesh.vertSel.GetSize());
	assert(pmesh.getNumEdges() == pmesh.edgeSel.GetSize());
	assert(pmesh.numPatches == pmesh.patchSel.GetSize());

	patchData->Apply(t,patchOb,selLevel);
	}

void EditPatchMod::NotifyInputChanged(Interval changeInt, PartID partID, RefMessage message, ModContext *mc)
	{
	if ( mc->localData ) {
		EditPatchData *patchData = (EditPatchData*)mc->localData;
		if ( patchData ) {
			// The FALSE parameter indicates the the mesh cache itself is
			// invalid in addition to any other caches that depend on the
			// mesh cache.
			patchData->Invalidate(partID,FALSE);
			}
		}
	}

// Select a subcomponent within our object(s).  WARNING! Because the HitRecord list can
// indicate any of the objects contained within the group of patches being edited, we need
// to watch for control breaks in the patchData pointer within the HitRecord!

void EditPatchMod::SelectSubComponent( HitRecord *hitRec, BOOL selected, BOOL all, BOOL invert )
	{
	// Don't do anything if at vertex level with verts turned off
	if(selLevel == EP_VERTEX && !filterVerts)
		return;

	if ( !iObjParams ) return; 
	TimeValue t = iObjParams->GetTime();

	iObjParams->ClearCurNamedSelSet();

	// Keep processing hit records as long as we have them!
	while(hitRec) {	
		EditPatchData *patchData = (EditPatchData*)hitRec->modContext->localData;
	
		if ( !patchData )
			return;

		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			return;

		patchData->BeginEdit(t);
		patchData->StartChangeGroup();

		switch ( selLevel ) {
			case EP_VERTEX: {
				PVertSelRecord *rec = new PVertSelRecord();
				rec->oldSel = patch->vertSel;
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				if ( all ) {				
					if ( invert ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto vert_done;
							int index = ((PatchHitData *)(hitRec->hitData))->index;
							if(((PatchHitData *)(hitRec->hitData))->type == PATCH_HIT_VERTEX) {
								if(patch->vertSel[index])
									patch->vertSel.Clear(index);
								else
									patch->vertSel.Set(index);
								}
							hitRec = hitRec->Next();
							}
						}
					else
					if ( selected ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto vert_done;
							PatchHitData *hit = (PatchHitData *)(hitRec->hitData);
							if(hit->type == PATCH_HIT_VERTEX)
								patch->vertSel.Set(hit->index);
							hitRec = hitRec->Next();
							}
						}
					else {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto vert_done;
							PatchHitData *hit = (PatchHitData *)(hitRec->hitData);
							if(hit->type == PATCH_HIT_VERTEX)
								patch->vertSel.Clear(hit->index);
							hitRec = hitRec->Next();
							}
						}
					}
				else {
					int index = ((PatchHitData *)(hitRec->hitData))->index;
					if(((PatchHitData *)(hitRec->hitData))->type == PATCH_HIT_VERTEX) {
						if( invert ) {
							if(patch->vertSel[index])
								patch->vertSel.Clear(index);
							else
								patch->vertSel.Set(index);
							}
						else
						if ( selected )
							patch->vertSel.Set(index);
						else
							patch->vertSel.Clear(index);
						}
					hitRec = NULL;	// Reset it so we can exit	
					}
				vert_done:
				rec->newSel = patch->vertSel;
				patchData->AddChangeRecord(rec);
				break;
				}
			case EP_EDGE: {
				PEdgeSelRecord *rec = new PEdgeSelRecord();
				rec->oldSel = patch->edgeSel;
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				if ( all ) {				
					if ( invert ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto edge_done;
							int index = ((PatchHitData *)(hitRec->hitData))->index;
							if(patch->edgeSel[index])
								patch->edgeSel.Clear(index);
							else
								patch->edgeSel.Set(index);
							hitRec = hitRec->Next();
							}
						}
					else
					if ( selected ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto edge_done;
							patch->edgeSel.Set(((PatchHitData *)(hitRec->hitData))->index);
							hitRec = hitRec->Next();
							}
						}
					else {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto edge_done;
							patch->edgeSel.Clear(((PatchHitData *)(hitRec->hitData))->index);
							hitRec = hitRec->Next();
							}
						}
					}
				else {
					int index = ((PatchHitData *)(hitRec->hitData))->index;
					if( invert ) {
						if(patch->edgeSel[index])
							patch->edgeSel.Clear(index);
						else
							patch->edgeSel.Set(index);
						}
					else
					if ( selected ) {
						patch->edgeSel.Set(index);
						}
					else {
						patch->edgeSel.Clear(index);
						}
					hitRec = NULL;	// Reset it so we can exit	
					}
				edge_done:
				rec->newSel = patch->edgeSel;
				patchData->AddChangeRecord(rec);
				break;
				}
			case EP_PATCH: {
				PatchSelRecord *rec = new PatchSelRecord();
				rec->oldSel = patch->patchSel;
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				if ( all ) {				
					if ( invert ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto patch_done;
							int index = ((PatchHitData *)(hitRec->hitData))->index;
							if(patch->patchSel[index])
								patch->patchSel.Clear(index);
							else
								patch->patchSel.Set(index);
							hitRec = hitRec->Next();
							}
						}
					else
					if ( selected ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto patch_done;
							patch->patchSel.Set(((PatchHitData *)(hitRec->hitData))->index);
							hitRec = hitRec->Next();
							}
						}
					else {
						while( hitRec ) {
							// If the object changes, we're done!
							if(patchData != (EditPatchData*)hitRec->modContext->localData)
								goto patch_done;
							patch->patchSel.Clear(((PatchHitData *)(hitRec->hitData))->index);
							hitRec = hitRec->Next();
							}
						}
					}
				else {
					int index = ((PatchHitData *)(hitRec->hitData))->index;
					if( invert ) {
						if(patch->patchSel[index])
							patch->patchSel.Clear(index);
						else
							patch->patchSel.Set(index);
						}
					else
					if ( selected ) {
						patch->patchSel.Set(index);
						}
					else {
						patch->patchSel.Clear(index);
						}
					hitRec = NULL;	// Reset it so we can exit	
					}
				patch_done:
				rec->newSel = patch->patchSel;
				patchData->AddChangeRecord(rec);
				PatchSelChanged();
				break;
				}
			case EP_OBJECT:
			default:
				return;
			}
		if ( patchData->tempData ) {
			patchData->tempData->Invalidate(PART_SELECT);
			}
		}

	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	}

void EditPatchMod::ClearSelection(int selLevel) 
	{
	// Don't do anything if at vertex level with verts turned off
	if(selLevel == EP_VERTEX && !filterVerts)
		return;
	if(selLevel == EP_OBJECT)
		return;

	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	iObjParams->ClearCurNamedSelSet();
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		patchData->StartChangeGroup();
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			continue;

		patchData->BeginEdit(iObjParams->GetTime());

		switch ( selLevel ) {
			case EP_VERTEX: {
				ClearPVertSelRecord *rec = new ClearPVertSelRecord(patch->vertSel);
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->vertSel.ClearAll();
				break;
				}

			case EP_EDGE: {
				ClearPEdgeSelRecord *rec = new ClearPEdgeSelRecord(patch->edgeSel);
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->edgeSel.ClearAll();
				break;
				}
			case EP_PATCH: {
				ClearPatchSelRecord *rec = new ClearPatchSelRecord(patch->patchSel);
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->patchSel.ClearAll();
				PatchSelChanged();
				break;
				}
			}
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	}

void EditPatchMod::SelectAll(int selLevel) 
	{
	// Don't do anything if at vertex level with verts turned off
	if(selLevel == EP_VERTEX && !filterVerts)
		return;
	if(selLevel == EP_OBJECT)
		return;

	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	
	iObjParams->GetModContexts(mcList,nodes);
	iObjParams->ClearCurNamedSelSet();

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		patchData->StartChangeGroup();
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			continue;

		patchData->BeginEdit(iObjParams->GetTime());

		switch ( selLevel ) {
			case EP_VERTEX: {
				SetPVertSelRecord *rec = new SetPVertSelRecord(patch->vertSel);
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->vertSel.SetAll();
				break;
				}

			case EP_EDGE: {
				SetPEdgeSelRecord *rec = new SetPEdgeSelRecord(patch->edgeSel);
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->edgeSel.SetAll();
				break;
				}
			case EP_PATCH: {
				SetPatchSelRecord *rec = new SetPatchSelRecord(patch->patchSel);
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->patchSel.SetAll();
				PatchSelChanged();
				break;
				}
			}
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	}

void EditPatchMod::InvertSelection(int selLevel) 
	{
	// Don't do anything if at vertex level with verts turned off
	if(selLevel == EP_VERTEX && !filterVerts)
		return;
	if(selLevel == EP_OBJECT)
		return;

	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	
	iObjParams->GetModContexts(mcList,nodes);
	iObjParams->ClearCurNamedSelSet();

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		patchData->StartChangeGroup();
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			continue;

		patchData->BeginEdit(iObjParams->GetTime());

		switch ( selLevel ) {
			case EP_VERTEX: {
				InvertPVertSelRecord *rec = new InvertPVertSelRecord();
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->vertSel = ~patch->vertSel;
				break;
				}

			case EP_EDGE: {
				InvertPEdgeSelRecord *rec = new InvertPEdgeSelRecord();
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->edgeSel = ~patch->edgeSel;
				break;
				}
			case EP_PATCH: {
				InvertPatchSelRecord *rec = new InvertPatchSelRecord();
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new PatchRestore(patchData,this,rec));
					}
				patch->patchSel = ~patch->patchSel;
				PatchSelChanged();
				break;
				}
			}
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	}

void EditPatchMod::SetDisplaySurface(BOOL sw) {
	displaySurface = sw;
	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		patchData->displaySurface = sw;

		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			continue;
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_DISPLAY);
			}
		}
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_DISPLAY, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::SetDisplayLattice(BOOL sw) {
	displayLattice = sw;
	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		patchData->displayLattice = sw;

		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			continue;
		if(sw)
			patch->SetDispFlag(DISP_LATTICE);
		else
			patch->ClearDispFlag(DISP_LATTICE);
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_DISPLAY);
			}
		}
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_DISPLAY, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::SetEPropagate(BOOL sw) {
	EPropagate = sw;
	}

void EditPatchMod::SetPPropagate(BOOL sw) {
	PPropagate = sw;
	}

void EditPatchMod::SetMeshSteps(int steps) {
	meshSteps = steps;
	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			continue;

		patch->SetMeshSteps(steps);
		patchData->meshSteps = steps;
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_DISPLAY);
			}
		}
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_DISPLAY, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

/*
// Future use (Not used now)
void EditPatchMod::SetMeshAdaptive(BOOL sw) {
	meshAdaptive = sw;
	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		patch->SetAdaptive(sw);
		patchData->meshAdaptive = sw;
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_DISPLAY);
			}
		}
	NotifyDependents(FOREVER, PART_DISPLAY, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}
*/

void EditPatchMod::SetViewTess(TessApprox &tess) {
	viewTess = tess;
	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			continue;

		patch->SetViewTess(tess);
		patchData->viewTess = tess;
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_DISPLAY);
			}
		}
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_DISPLAY, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::SetProdTess(TessApprox &tess) {
	prodTess = tess;
	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;	
	
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;		
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			continue;

		patch->SetProdTess(tess);
		patchData->prodTess = tess;
		if ( patchData->tempData ) {
			patchData->TempData(this)->Invalidate(PART_DISPLAY);
			}
		}
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_DISPLAY, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::ActivateSubobjSel(int level, XFormModes& modes )
	{	
	ModContextList mcList;
	INodeTab nodes;
	int old = selLevel;

	if ( !iObjParams ) return;
	iObjParams->GetModContexts(mcList,nodes);

	selLevel = level;
	switch ( level ) {
		case EP_OBJECT:
			// Not imp.
			break;

		case EP_PATCH:
		case EP_EDGE:
		case EP_VERTEX:
			modes = XFormModes(moveMode,rotMode,nuscaleMode,uscaleMode,squashMode,selectMode);
			break;
		}

	if ( iObjParams ) {
		SetRollupPage(iObjParams);
		}

	if ( selLevel != old ) {
		// Modify the caches to reflect the new sel level.
		for ( int i = 0; i < mcList.Count(); i++ ) {
			EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
			if ( !patchData ) continue;		
		
			if ( patchData->tempData && patchData->TempData(this)->PatchCached(iObjParams->GetTime()) ) {
				
				PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
				if(patch) {
					if(selLevel == EP_VERTEX)
						patch->dispFlags = DISP_VERTS;
					else
						patch->dispFlags = 0;
					if(displayLattice)
						patch->SetDispFlag(DISP_LATTICE);
					patch->SetDispFlag(patchLevelDispFlags[selLevel]);
					patch->selLevel = patchLevel[selLevel];
					}
				}
			}		

		NotifyDependents(FOREVER, PART_SUBSEL_TYPE|PART_DISPLAY,REFMSG_CHANGE);
		iObjParams->PipeSelLevelChanged();
		}
	
	// Setup named selection sets
	if (selLevel != EP_OBJECT) {
		iObjParams->ClearSubObjectNamedSelSets();
		for (int i=0; i<namedSel[selLevel-1].Count(); i++) {
			iObjParams->AppendSubObjectNamedSelSet(*namedSel[selLevel-1][i]);
			}
		}	
	nodes.DisposeTemporary();
	}


int EditPatchMod::SubObjectIndex(HitRecord *hitRec)
	{	
	EditPatchData *patchData = (EditPatchData*)hitRec->modContext->localData;
	if ( !patchData ) return 0;
	if ( !iObjParams ) return 0;
	TimeValue t = iObjParams->GetTime();
	PatchHitData *hit = (PatchHitData *)(hitRec->hitData);
	switch ( selLevel ) {
		case EP_VERTEX: {
			if(hit->type != PATCH_HIT_VERTEX)
				return 0;
			int hitIndex = hit->index;
			return hitIndex;
			}
		case EP_EDGE: {
			int hitIndex = hit->index;
			return hitIndex;
			}
		case EP_PATCH: {
			int hitIndex = hit->index;
			return hitIndex;
			}
		default:
			return 0;
		}
	}

void EditPatchMod::GetSubObjectTMs(
		SubObjAxisCallback *cb,TimeValue t,INode *node,ModContext *mc)
	{
	Interval valid;
	if ( mc->localData ) {
		EditPatchData *patchData = (EditPatchData*)mc->localData;
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		// Watch out -- The system can call us even if we didn't get a valid patch object
		if(!patch)
			return;

		switch ( selLevel ) {
			case EP_VERTEX: {
				Matrix3 otm = node->GetObjectTM(t,&valid);
				Matrix3 tm = node->GetNodeTM(t,&valid);
				BitArray sel = patch->VertexTempSel();
				int count = sel.GetSize();
				for(int i = 0; i < count; ++i) {
					if(sel[i]) {
						tm.SetTrans(patch->verts[i].p * otm);
						cb->TM(tm, i);
						}
					}
				break;
				}
			case EP_EDGE:
			case EP_PATCH: {
 				Matrix3 otm = node->GetObjectTM(t,&valid);
				Matrix3 tm = node->GetNodeTM(t,&valid);
				Box3 box;
				BitArray sel = patch->VertexTempSel();
				int count = sel.GetSize();
				for ( int i = 0; i < count; i++ ) {
					if ( sel[i] )
						box += patch->verts[i].p;
					}
				tm.SetTrans(otm * box.Center());
				cb->TM(tm, 0);
				break;
				}
			}
		}
	}

void EditPatchMod::GetSubObjectCenters(
		SubObjAxisCallback *cb,TimeValue t,INode *node,ModContext *mc)
	{
	Interval valid;
	Matrix3 tm = node->GetObjectTM(t,&valid);	
	
	assert(iObjParams);
	if ( mc->localData ) {	
		EditPatchData *patchData = (EditPatchData*)mc->localData;		
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		// Watch out -- The system can call us even if we didn't get a valid patch object
		if(!patch)
			return;

		switch ( selLevel ) {
			case EP_VERTEX: {
				Box3 box;
				BitArray sel = patch->VertexTempSel();
				int verts = patch->numVerts;
				for ( int i = 0; i < verts; i++ ) {
					if ( sel[i] )
						cb->Center(patch->verts[i].p * tm, i);
					}
				break;
				}
			case EP_EDGE:
			case EP_PATCH: {
				Box3 box;
				BitArray sel = patch->VertexTempSel();
				int verts = patch->numVerts;
				for ( int i = 0; i < verts; i++ ) {
					if ( sel[i] )
						box += patch->verts[i].p * tm;
					}
				cb->Center(box.Center(), 0);
				break;
				}
			default:
				cb->Center(tm.GetTrans(), 0);
				break;
			}		
		}
	}

BOOL EditPatchMod::DependOnTopology(ModContext &mc)
	{
	EditPatchData *patchData = (EditPatchData*)mc.localData;
	if (patchData) {
		if (patchData->GetFlag(EPD_HASDATA)) {
			return TRUE;
			}
		}
	return FALSE;
	}

void EditPatchMod::DeletePatchDataTempData()
	{
	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;		
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;				
		if ( patchData->tempData ) {
			delete patchData->tempData;
			}
		patchData->tempData = NULL;
		}
	nodes.DisposeTemporary();
	}


void EditPatchMod::CreatePatchDataTempData()
	{
	ModContextList mcList;
	INodeTab nodes;

	if ( !iObjParams ) return;		
	iObjParams->GetModContexts(mcList,nodes);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;				
		if ( !patchData->tempData ) {
			patchData->tempData = new EPTempData(this,patchData);
			}		
		}
	nodes.DisposeTemporary();
	}

//--------------------------------------------------------------

int EditPatchMod::RememberPatchThere(HWND hWnd, IPoint2 m) {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();

	// Initialize so there isn't any remembered patch
	rememberedPatch = NULL;

	if ( !iObjParams ) return 0;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	// See if we're over a patch
	ViewExp *vpt = iObjParams->GetViewport(hWnd);
	GraphicsWindow *gw = vpt->getGW();
	HitRegion hr;
	MakeHitRegion(hr, HITTYPE_POINT, 1, 4, &m);
	gw->setHitRegion(&hr);
	SubPatchHitList hitList;

	int result = 0;

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
		INode *inode = nodes[i];
		Matrix3 mat = inode->GetObjectTM(t);
		gw->setTransform(mat);	
		if(patch->SubObjectHitTest(gw, gw->getMaterial(), &hr, SUBHIT_PATCH_PATCHES | HIT_ABORTONHIT, hitList )) {
			PatchSubHitRec *hit = hitList.First();
			if(patch->patchSel[hit->index]) {
				rememberedPatch = NULL;
				rememberedData = patch->patches[hit->index].flags;
				}
			else {
				if(iObjParams->SelectionFrozen())
					goto finish;
				// Select just this patch
				PatchSelRecord *rec = new PatchSelRecord;
				rec->oldSel = patch->patchSel;
				patch->patchSel.ClearAll();
				patch->patchSel.Set(hit->index);
				rec->newSel = patch->patchSel;
			 	theHold.Begin();
				patchData->StartChangeGroup();
				patchData->AddChangeRecord(rec);
				if ( theHold.Holding() )
					theHold.Put(new PatchRestore(patchData,this,rec));
				theHold.Accept(GetString(IDS_DS_SELECT));
				NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
				iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);

				rememberedPatch = patch;
				rememberedIndex = hit->index;
				rememberedData = patch->patches[rememberedIndex].flags;
				}
			result = 1;
			goto finish;
			}
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
finish:
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	if ( vpt ) iObjParams->ReleaseViewport(vpt);
	return result;
	}

void EditPatchMod::ChangeRememberedPatch(int type) {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
		if(patch == rememberedPatch) {
			// If this is the first edit, then the delta arrays will be allocated
			patchData->BeginEdit(t);

			theHold.Begin();
			PatchChangeRecord *rec = new PatchChangeRecord(patch, rememberedIndex, type);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the patch type change function
			ChangePatchType(patch, rememberedIndex, type);
			patchData->TempData(this)->Invalidate(PART_TOPO);
			theHold.Accept(GetString(IDS_TH_PATCHCHANGE));
			ClearPatchDataFlag(mcList,EPD_BEENDONE);
			NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
			iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
			nodes.DisposeTemporary();
			return;
 			}
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	}

void EditPatchMod::ChangeSelPatches(int type) {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	BOOL holdNeeded = FALSE;
	BOOL hadSelected = FALSE;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		BOOL altered = FALSE;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;		
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any bits are set in the selection set, let's DO IT!!
		if(patch->patchSel.NumberSet()) {
			altered = holdNeeded = TRUE;
			PatchChangeRecord *rec = new PatchChangeRecord(patch, -1, type);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the vertex type change function
			ChangePatchType(patch, -1, type);
			}

		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_PATCHCHANGE));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOPATCHESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}


void EditPatchMod::SetRememberedPatchType(int type) {
	if(rememberedPatch)
		ChangeRememberedPatch(type);
	else
		ChangeSelPatches(type);
	}

//--------------------------------------------------------------

int EditPatchMod::RememberVertThere(HWND hWnd, IPoint2 m) {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();

	// Initialize so there isn't any remembered patch
	rememberedPatch = NULL;

	if ( !iObjParams ) return 0;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	// See if we're over a vertex
	ViewExp *vpt = iObjParams->GetViewport(hWnd);
	GraphicsWindow *gw = vpt->getGW();
	HitRegion hr;
	MakeHitRegion(hr, HITTYPE_POINT, 1, 4, &m);
	gw->setHitRegion(&hr);
	SubPatchHitList hitList;

	int result = 0;
	
	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
		INode *inode = nodes[i];
		Matrix3 mat = inode->GetObjectTM(t);
		gw->setTransform(mat);	
		if(patch->SubObjectHitTest(gw, gw->getMaterial(), &hr, SUBHIT_PATCH_VERTS | HIT_ABORTONHIT, hitList )) {
			PatchSubHitRec *hit = hitList.First();
			if(hit->type == PATCH_HIT_VERTEX) {
				if(patch->vertSel[hit->index]) {
					rememberedPatch = NULL;
					rememberedData = patch->verts[hit->index].flags;
					}
				else {
					if(iObjParams->SelectionFrozen())
						goto finish;
					// Select just this vertex
					PVertSelRecord *rec = new PVertSelRecord;
					rec->oldSel = patch->vertSel;
					patch->vertSel.ClearAll();
					patch->vertSel.Set(hit->index);
					rec->newSel = patch->vertSel;
				 	theHold.Begin();
					patchData->StartChangeGroup();
					patchData->AddChangeRecord(rec);
					if ( theHold.Holding() )
						theHold.Put(new PatchRestore(patchData,this,rec));
					theHold.Accept(GetString(IDS_DS_SELECT));
					NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
					iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);

					rememberedPatch = patch;
					rememberedIndex = hit->index;
					rememberedData = patch->verts[rememberedIndex].flags;
					}
				result = 1;
				goto finish;
				}
			}
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
finish:
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	if ( vpt ) iObjParams->ReleaseViewport(vpt);
	return result;
	}

void EditPatchMod::ChangeRememberedVert(int type) {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
		if(patch == rememberedPatch) {
			// If this is the first edit, then the delta arrays will be allocated
			patchData->BeginEdit(t);

			theHold.Begin();
			PVertChangeRecord *rec = new PVertChangeRecord(patch, rememberedIndex, type);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the vertex type change function
			ChangeVertType(patch, rememberedIndex, type);
			patchData->TempData(this)->Invalidate(PART_TOPO);
			theHold.Accept(GetString(IDS_TH_VERTCHANGE));
			ClearPatchDataFlag(mcList,EPD_BEENDONE);
			NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
			iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
			nodes.DisposeTemporary();
			return;
 			}
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	}

void EditPatchMod::ChangeSelVerts(int type) {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	BOOL holdNeeded = FALSE;
	BOOL hadSelected = FALSE;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		BOOL altered = FALSE;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
					
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any bits are set in the selection set, let's DO IT!!
		if(patch->vertSel.NumberSet()) {
			altered = holdNeeded = TRUE;
			PVertChangeRecord *rec = new PVertChangeRecord(patch, -1, type);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the vertex type change function
			ChangeVertType(patch, -1, type);
			}

		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_VERTCHANGE));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOVERTSSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}


void EditPatchMod::SetRememberedVertType(int type) {
	if(rememberedPatch)
		ChangeRememberedVert(type);
	else
		ChangeSelVerts(type);
	}

//--------------------------------------------------------------
int EditPatchMod::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;
	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);
	gw->clearHitCode();
	
	if ( mc->localData ) {		
		EditPatchData *patchData = (EditPatchData*)mc->localData;
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		if(!patch)
			return FALSE;

		SubPatchHitList hitList;
		PatchSubHitRec *rec;
		res = patch->SubObjectHitTest( gw, gw->getMaterial(), &hr,
			flags|((patchHitOverride) ? patchHitLevel[patchHitOverride] : patchHitLevel[selLevel]), hitList );
	
		rec = hitList.First();
		while( rec ) {
			vpt->LogHit(inode,mc,rec->dist,123456,new PatchHitData(rec->patch, rec->index, rec->type));
			rec = rec->Next();
			}
		}

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

int EditPatchMod::Display(TimeValue t, INode* inode, ViewExp *vpt, int flags, ModContext *mc) {	
	return 0;	
	}

void EditPatchMod::GetWorldBoundBox(TimeValue t,INode* inode, ViewExp *vpt, Box3& box, ModContext *mc) {
	box.Init();
	}



//---------------------------------------------------------------------
// UI stuff

void EditPatchMod::SetRollupPage(IObjParam *ip)
	{
	if ( hEditPatchParams ) {
		RemoveRollupPage(ip);
		}
	hEditPatchParams = ip->AddRollupPage( 
			hInstance, 
			MAKEINTRESOURCE(patchDlg[selLevel]),
			patchDlgProc[selLevel],
			GetString(patchDlgString[selLevel]), 
			(LPARAM)this );		
	
	ip->RegisterDlgWnd( hEditPatchParams );
	}

void EditPatchMod::RemoveRollupPage(IObjParam *ip)
	{
	ip->UnRegisterDlgWnd(hEditPatchParams);		
	ip->DeleteRollupPage(hEditPatchParams);
	hEditPatchParams = NULL;				
	}

void EditPatchMod::RecordTopologyTags() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
		patch->RecordTopologyTags();
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	}

class ChangeNamedSetRestore : public RestoreObj {
	public:
		BitArray oldset,newset;
		int index;
		NamedSelSetList *setList;

		ChangeNamedSetRestore(NamedSelSetList *sl,int ix,BitArray *o) {
			setList = sl; index = ix; oldset = *o;
			}   		
		void Restore(int isUndo) {
			newset = *(setList->sets[index]);
			*(setList->sets[index]) = oldset;
			}
		void Redo() {
			*(setList->sets[index]) = newset;
			}
				
		TSTR Description() {return TSTR(_T("Change Named Sel Set"));}
	};

// Selection set, misc fixup utility function
// This depends on BezierShape::RecordTopologyTags being called prior to the topo changes
void EditPatchMod::ResolveTopoChanges() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
		// First, the vertex selections
		for(int set = 0; set < patchData->vselSet.Count(); ++set) {
			BitArray *oldVS = &patchData->vselSet[set];
			BitArray newVS;
			newVS.SetSize(patch->numVerts);
			for(int vert = 0; vert < patch->numVerts; ++vert) {
				// Get the knot's previous location, then copy that selection into the new set
				int tag = patch->verts[vert].aux2;
				if(tag >= 0)
					newVS.Set(vert, (*oldVS)[tag]);
				else
					newVS.Clear(vert);
				}
			if(theHold.Holding())
				theHold.Put(new ChangeNamedSetRestore(&patchData->vselSet, set, oldVS));
			patchData->vselSet[set] = newVS;
			}
		// Now the edge selections
		for(set = 0; set < patchData->eselSet.Count(); ++set) {
			BitArray *oldES = &patchData->eselSet[set];
			BitArray newES;
			newES.SetSize(patch->numEdges);
			for(int edge = 0; edge < patch->numEdges; ++edge) {
				// Get the knot's previous location, then copy that selection into the new set
				int tag = patch->edges[edge].aux2;
				if(tag >= 0)
					newES.Set(edge, (*oldES)[tag]);
				else
					newES.Clear(edge);
				}
			if(theHold.Holding())
				theHold.Put(new ChangeNamedSetRestore(&patchData->eselSet, set, oldES));
			patchData->eselSet[set] = newES;
			}
		// Now the patch selections
		for(set = 0; set < patchData->pselSet.Count(); ++set) {
			BitArray *oldPS = &patchData->pselSet[set];
			BitArray newPS;
			newPS.SetSize(patch->numPatches);
			for(int p = 0; p < patch->numPatches; ++p) {
				// Get the knot's previous location, then copy that selection into the new set
				int tag = patch->patches[p].aux2;
				if(tag >= 0)
					newPS.Set(p, (*oldPS)[tag]);
				else
					newPS.Clear(p);
				}
			if(theHold.Holding())
				theHold.Put(new ChangeNamedSetRestore(&patchData->pselSet, set, oldPS));
			patchData->pselSet[set] = newPS;
			}
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	}

class EPModContextEnumProc : public ModContextEnumProc {
	float f;
	public:
		EPModContextEnumProc(float f) { this->f = f; }
		BOOL proc(ModContext *mc);  // Return FALSE to stop, TRUE to continue.
	};

BOOL EPModContextEnumProc::proc(ModContext *mc) {
	EditPatchData *patchData = (EditPatchData*)mc->localData;
	if ( patchData )		
		patchData->RescaleWorldUnits(f);
	return TRUE;
	}

// World scaling
void EditPatchMod::RescaleWorldUnits(float f) {
	if (TestAFlag(A_WORK1))
		return;
	SetAFlag(A_WORK1);
	
	// rescale all our references
	for (int i=0; i<NumRefs(); i++) {
		ReferenceMaker *srm = GetReference(i);
		if (srm) 
			srm->RescaleWorldUnits(f);
		}
	
	// Now rescale stuff inside our data structures
	EPModContextEnumProc proc(f);
	EnumModContexts(&proc);
	NotifyDependents(FOREVER, PART_GEOM, REFMSG_CHANGE);
	}

void EditPatchMod::BeginEditParams( IObjParam *ip, ULONG flags, Animatable *prev )
	{
	iObjParams = ip;
	CreatePatchDataTempData();

	if ( !hEditPatchParams ) {
		SetRollupPage(ip);		
	} else {
		SetWindowLong( hEditPatchParams, GWL_USERDATA, (LONG)this );	
		}
	
	// Create sub object editing modes.
	moveMode        = new MoveModBoxCMode(this,ip);
	rotMode         = new RotateModBoxCMode(this,ip);
	uscaleMode      = new UScaleModBoxCMode(this,ip);
	nuscaleMode     = new NUScaleModBoxCMode(this,ip);
	squashMode      = new SquashModBoxCMode(this,ip);
	selectMode      = new SelectModBoxCMode(this,ip);

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

	// Restore the selection level.
	ip->SetSubObjectLevel(selLevel);
	
	// Disable show end result.
	ip->EnableShowEndResult(FALSE);

	TimeValue t = ip->GetTime();
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_BEGIN_EDIT);
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_MOD_DISPLAY_ON);
	SetAFlag(A_MOD_BEING_EDITED);	
	}
		
void EditPatchMod::EndEditParams( IObjParam *ip, ULONG flags, Animatable *next )
	{
	if ( flags&END_EDIT_REMOVEUI ) {
		RemoveRollupPage(ip);
	} else {		
		SetWindowLong( hEditPatchParams, GWL_USERDATA, 0 );		
		}
	// Enable show end result
	ip->EnableShowEndResult(TRUE);

	CancelEditPatchModes(ip);

	TimeValue t = ip->GetTime();
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_END_EDIT);
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_MOD_DISPLAY_OFF);
	ClearAFlag(A_MOD_BEING_EDITED);
	
	DeletePatchDataTempData();
	iObjParams = NULL;
	
	ip->DeleteMode(moveMode);
	ip->DeleteMode(rotMode);
	ip->DeleteMode(uscaleMode);
	ip->DeleteMode(nuscaleMode);
	ip->DeleteMode(squashMode);
	ip->DeleteMode(selectMode);

	if ( moveMode ) delete moveMode;
	moveMode = NULL;
	if ( rotMode ) delete rotMode;
	rotMode = NULL;
	if ( uscaleMode ) delete uscaleMode;
	uscaleMode = NULL;
	if ( nuscaleMode ) delete nuscaleMode;
	nuscaleMode = NULL;
	if ( squashMode ) delete squashMode;
	squashMode = NULL;
	if ( selectMode ) delete selectMode;
	selectMode = NULL;
	}

// Vertex Delete modifier method
void EditPatchMod::DoVertDelete() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	RecordTopologyTags();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
					
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any bits are set in the selection set, let's DO IT!!
		if(patch->vertSel.NumberSet()) {
			altered = holdNeeded = 1;
			PVertDeleteRecord *rec = new PVertDeleteRecord(patch);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the vertex delete function
			DeleteSelVerts(patch);
			}

		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		ResolveTopoChanges();
		theHold.Accept(GetString(IDS_TH_VERTDELETE));
		}
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOVERTSSEL),PROMPT_TIME);
		theHold.End();
		}
	
	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::DoPatchAdd(int type) {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	RecordTopologyTags();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
					
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any bits are set in the selection set, let's DO IT!!
		if(patch->edgeSel.NumberSet()) {
			altered = holdNeeded = 1;
			PatchAddRecord *rec = new PatchAddRecord(type, patch);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the patch add function
			AddPatches(type, patch, TRUE);
			}

		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		ResolveTopoChanges();
		theHold.Accept(GetString(IDS_TH_PATCHADD));
		}
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOVALIDEDGESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::DoPatchDelete() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	RecordTopologyTags();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
					
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any bits are set in the selection set, let's DO IT!!
		if(patch->patchSel.NumberSet()) {
			altered = holdNeeded = 1;
			PatchDeleteRecord *rec = new PatchDeleteRecord(patch);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the patch delete function
			DeleteSelPatches(patch);
			}

		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		ResolveTopoChanges();
		theHold.Accept(GetString(IDS_TH_PATCHDELETE));
		}
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOPATCHESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::DoEdgeSubdivide() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	RecordTopologyTags();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
					
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any bits are set in the selection set, let's DO IT!!
		if(patch->edgeSel.NumberSet()) {
			altered = holdNeeded = 1;
			EdgeSubdivideRecord *rec = new EdgeSubdivideRecord(EPropagate, patch);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the patch add function
			SubdividePatch(SUBDIV_EDGES, EPropagate, patch);
			}

		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		ResolveTopoChanges();
		theHold.Accept(GetString(IDS_TH_EDGESUBDIVIDE));
		}
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOVALIDEDGESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::DoPatchSubdivide() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	RecordTopologyTags();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
					
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any bits are set in the selection set, let's DO IT!!
		if(patch->patchSel.NumberSet()) {
			altered = holdNeeded = 1;
			PatchSubdivideRecord *rec = new PatchSubdivideRecord(PPropagate, patch);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the patch add function
			SubdividePatch(SUBDIV_PATCHES, PPropagate, patch);
			}

		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		ResolveTopoChanges();
		theHold.Accept(GetString(IDS_TH_PATCHSUBDIVIDE));
		}
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOPATCHESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

void EditPatchMod::DoVertWeld() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;
	BOOL hadSel = FALSE;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	RecordTopologyTags();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		BOOL altered = FALSE;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
		
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any bits are set in the selection set, let's DO IT!!
		if(patch->vertSel.NumberSet() > 1) {
			hadSel = TRUE;
			PVertWeldRecord *rec = new PVertWeldRecord(weldThreshold, patch);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new PatchRestore(patchData,this,rec));
			// Call the patch weld function
			if(WeldPatch(patch, weldThreshold))
				altered = holdNeeded = TRUE;
			}

		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		ResolveTopoChanges();
		theHold.Accept(GetString(IDS_TH_VERTWELD));
		}
	else {
		if(!hadSel)
			iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOVERTSSEL),PROMPT_TIME);
		else
			iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOWELDPERFORMED),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

// Detach all selected patches
void EditPatchMod::DoPatchDetach(int copy, int reorient) {
	int dialoged = 0;
	TSTR newName(GetString(IDS_TH_PATCH));
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	theHold.Begin();
	RecordTopologyTags();

	// Create a patch mesh object
	PatchObject *patchOb = new PatchObject;
	PatchMesh &pmesh = patchOb->patch;
	int verts = 0;
	int vecs = 0;
	int patches = 0;

	int multipleObjects = (mcList.Count() > 1) ? 1 : 0;
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if ( !patchData ) continue;
		if ( patchData->GetFlag(EPD_BEENDONE) ) continue;

		patchData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		PatchMesh *patch = patchData->TempData(this)->GetPatch(t);
		if(!patch)
			continue;
		
		// If this is the first edit, then the delta arrays will be allocated
		patchData->BeginEdit(t);

		// If any patches selected, we'll need to process this one
		if(patch->patchSel.NumberSet()) {
			if(!dialoged) {
				dialoged = 1;
				if(!GetDetachOptions(iObjParams, newName))
					goto bail_out;
				}
			// Save the unmodified info.
			PatchDetachRecord *rec = new PatchDetachRecord(patch,copy);
			patchData->AddChangeRecord(rec);
			if ( theHold.Holding() ) {
				theHold.Put(new PatchRestore(patchData,this,rec));
				}
			PatchMesh wpatch = *patch;
			BitArray vdel(wpatch.numVerts);
			vdel.ClearAll();
			BitArray pdel = wpatch.patchSel;	// Get inverse selection set
			// If not copying, delete the patches from this object
			if(!copy)
				DeletePatchParts(patch, vdel, pdel);
			pdel = ~wpatch.patchSel;	// Get inverse selection set
			if(pdel.NumberSet()) {
				vdel.ClearAll();
				DeletePatchParts(&wpatch, vdel, pdel);
				}
			// We've deleted everything that wasn't selected -- Now add this to the patch object accumulator
			int oldVerts = pmesh.numVerts;
			int oldVecs = pmesh.numVecs;
			int oldPatches = pmesh.numPatches;
			int newVerts = oldVerts + wpatch.numVerts;
			int newVecs = oldVecs + wpatch.numVecs;
			int newPatches = oldPatches + wpatch.numPatches;
			pmesh.setNumVerts(newVerts, TRUE);
			pmesh.setNumVecs(newVecs, TRUE);
			pmesh.setNumPatches(newPatches, TRUE);
			altered = holdNeeded = 1;
			Matrix3 tm(1);
			if(multipleObjects && !reorient)
				tm = nodes[i]->GetObjectTM(t);
			for(int i = 0, i2 = oldVerts; i < wpatch.numVerts; ++i, ++i2) {
				pmesh.verts[i2] = wpatch.verts[i];
				pmesh.verts[i2].p = pmesh.verts[i2].p * tm;
				}
			for(i = 0, i2 = oldVecs; i < wpatch.numVecs; ++i, ++i2) {
				pmesh.vecs[i2] = wpatch.vecs[i];
				pmesh.vecs[i2].p = pmesh.vecs[i2].p * tm;
				}
			for(i = 0, i2 = oldPatches; i < wpatch.numPatches; ++i, ++i2) {
				Patch &p = wpatch.patches[i];
				Patch &p2 = pmesh.patches[i2];
				p2 = p;
				for(int j = 0; j < p2.type; ++j) {	// Adjust vertices and interior vectors
					p2.v[j] += oldVerts;
					p2.interior[j] += oldVecs;
					}
				for(j = 0; j < (p2.type * 2); ++j)	// Adjust edge vectors
					p2.vec[j] += oldVecs;
				}
			}

		bail_out:
		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		pmesh.computeInteriors();
		pmesh.buildLinkages();
		INode *newNode = iObjParams->CreateObjectNode(patchOb);
		newNode->SetMtl(nodes[0]->GetMtl());
		newNode->SetName(newName.data());
		patchOb->patch.InvalidateGeomCache();
		if(!multipleObjects) {	// Single input object?
			if(!reorient) {
				Matrix3 tm = nodes[0]->GetObjectTM(t);
				newNode->SetNodeTM(t, tm);	// Use this object's TM.
				}
			}
		else {
			if(!reorient) {
				Matrix3 matrix;
				matrix.IdentityMatrix();
				newNode->SetNodeTM(t, matrix);	// Use identity TM
				}
			}
		newNode->FlagForeground(t);		// WORKAROUND!
		ResolveTopoChanges();
		theHold.Accept(GetString(IDS_TH_DETACHPATCH));
		}
	else {
		delete patchOb;	// Didn't need it after all!
		if(!dialoged)
			iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOPATCHESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(t,REDRAW_NORMAL);
	}

void EditPatchMod::SetTessUI(HWND hDlg, TessApprox *tess)
{
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_U), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_U_SPINNER), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_V), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_V_SPINNER), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_EDGE), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_EDGE_SPINNER), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_DIST), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_DIST_SPINNER), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_ANG), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_ANG_SPINNER), FALSE);
	EnableWindow(GetDlgItem(hDlg, IDC_TESS_VIEW_DEP), FALSE);
	switch (tess->type) {
	case TESS_SET:
		CheckRadioButton( hDlg, IDC_TESS_SET, IDC_TESS_CURV, IDC_TESS_SET);
		break;
	case TESS_PARAM:
		CheckRadioButton( hDlg, IDC_TESS_SET, IDC_TESS_CURV, IDC_TESS_PARAM);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_U), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_U_SPINNER), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_V), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_V_SPINNER), TRUE);
		break;
	case TESS_SPATIAL:
		CheckRadioButton( hDlg, IDC_TESS_SET, IDC_TESS_CURV, IDC_TESS_SPATIAL);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_EDGE), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_EDGE_SPINNER), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_VIEW_DEP), TRUE);
		break;
	case TESS_CURVE:
		CheckRadioButton( hDlg, IDC_TESS_SET, IDC_TESS_CURV, IDC_TESS_CURV);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_DIST), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_DIST_SPINNER), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_ANG), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_ANG_SPINNER), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_TESS_VIEW_DEP), TRUE);
		break;
	}
	uSpin->SetValue(tess->u, FALSE);
	vSpin->SetValue(tess->v, FALSE);
	edgeSpin->SetValue(tess->edge, FALSE);
	distSpin->SetValue(tess->dist, FALSE);
	angSpin->SetValue(tess->ang, FALSE);
	CheckDlgButton( hDlg, IDC_TESS_VIEW_DEP, tess->view);
}

int EditPatchMod::GetSelMatIndex()
	{
	ModContextList mcList;	
	INodeTab nodes;
	BOOL first = 1;
	int mat=-1;

	if (!iObjParams) return -1;
	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);

	for (int i = 0; i < mcList.Count(); i++) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if (!patchData) continue;
		if (patchData->GetFlag(EPD_BEENDONE)) continue;
		patchData->BeginEdit(iObjParams->GetTime());
		PatchMesh *mesh = patchData->TempData(this)->GetPatch(iObjParams->GetTime());
		
		for (int j=0; j<mesh->getNumPatches(); j++) {
			if (mesh->patchSel[j]) {
				if (first) {
					first = FALSE;
					mat   = (int)mesh->getPatchMtlIndex(j);					
				} else {
					if ((int)mesh->getPatchMtlIndex(j) != mat) {
						return -1;
						}
					}
				}
			}
		}
	
	nodes.DisposeTemporary();
	return mat;
	}

void EditPatchMod::SetSelMatIndex(int index)
	{
	ModContextList mcList;	
	INodeTab nodes;
	BOOL holdNeeded = FALSE;

	if (!iObjParams) return;
	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);	

	theHold.Begin();
	for (int i = 0; i < mcList.Count(); i++) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		BOOL altered = FALSE;
		if (!patchData) continue;
		if (patchData->GetFlag(EPD_BEENDONE)) continue;
		patchData->BeginEdit(iObjParams->GetTime());
		patchData->StartChangeGroup();
		PatchMesh *patch = patchData->TempData(this)->GetPatch(iObjParams->GetTime());		

		// Start a restore object...
		PatchMtlRecord *rec = new PatchMtlRecord(patch, index);
		patchData->AddChangeRecord(rec);
		if (theHold.Holding()) {
			theHold.Put(new PatchRestore(patchData,this,rec));
			}

		for (int j=0; j<patch->getNumPatches(); j++) {			
			if (patch->patchSel[j]) {
				altered = holdNeeded = TRUE;
				patch->setPatchMtlIndex(j,(MtlID)index);			
				}
			}
		
		if(altered)
			patchData->TempData(this)->Invalidate(PART_TOPO);
		patchData->SetFlag(EPD_BEENDONE,TRUE);		
		}	
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_PATCHMTLCHANGE));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOPATCHESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
//	InvalidateSurfaceUI();
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime());
	}

void EditPatchMod::SelectByMat(int index,BOOL clear)
	{
	ModContextList mcList;	
	INodeTab nodes;

	if (!iObjParams) return;
	iObjParams->GetModContexts(mcList,nodes);
	ClearPatchDataFlag(mcList,EPD_BEENDONE);
	
	theHold.Begin();

	for (int i = 0; i < mcList.Count(); i++) {
		EditPatchData *patchData = (EditPatchData*)mcList[i]->localData;
		if (!patchData) continue;
		if (patchData->GetFlag(EPD_BEENDONE)) continue;
		patchData->StartChangeGroup();
		patchData->BeginEdit(iObjParams->GetTime());
		PatchMesh *pmesh = patchData->TempData(this)->GetPatch(iObjParams->GetTime());		

		// Start a restore object...
		PatchSelRecord *rec = new PatchSelRecord();
		patchData->AddChangeRecord(rec);
		rec->oldSel = pmesh->patchSel;
		if (theHold.Holding()) {
			theHold.Put(new PatchRestore(patchData,this,rec));
			}
		
		if (clear)
			pmesh->patchSel.ClearAll();

		for (int j=0; j<pmesh->getNumPatches(); j++) {			
			if (pmesh->getPatchMtlIndex(j)==index)
				pmesh->patchSel.Set(j);
			}
		
		rec->newSel = pmesh->patchSel;
		patchData->SetFlag(EPD_BEENDONE,TRUE);
		if (patchData->tempData) {
			patchData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
		
	theHold.Accept(GetString(IDS_RB_SELECTBYMATID));
	
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime());
	}

void EditPatchMod::PatchSelChanged() {
	if (hEditPatchParams && selLevel == EP_PATCH) {
		patchUIValid = FALSE;
		InvalidateRect(hEditPatchParams,NULL,FALSE);
		}
	}

BOOL CALLBACK PatchObjectParamDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{
	EditPatchMod *ep = (EditPatchMod *)GetWindowLong( hDlg, GWL_USERDATA );
	if ( !ep && message != WM_INITDIALOG ) return FALSE;
	
	switch ( message ) {
		case WM_INITDIALOG: {
		 	ep = (EditPatchMod *)lParam;
		 	SetWindowLong( hDlg, GWL_USERDATA, (LONG)ep );		 	
			ICustButton *but = GetICustButton(GetDlgItem(hDlg,IDC_ATTACH));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
			CheckDlgButton( hDlg, IDC_ATTACHREORIENT, attachReorient);
			CheckDlgButton( hDlg, IDC_DISPLATTICE, ep->displayLattice);
			CheckDlgButton( hDlg, IDC_DISPSURFACE, ep->displaySurface);
//			CheckDlgButton( hDlg, IDC_ADAPTIVE, ep->meshAdaptive);	// Future use (Not used now)
		 	ep->stepsSpin = GetISpinner(GetDlgItem(hDlg,IDC_STEPSSPINNER));
			ep->stepsSpin->SetLimits( 0, 100, FALSE );
			ep->stepsSpin->LinkToEdit( GetDlgItem(hDlg,IDC_STEPS), EDITTYPE_POS_INT );
			ep->stepsSpin->SetValue(ep->meshSteps,FALSE);
			
			TessApprox *t;
			if (ep->settingViewportTess)
				t = &ep->viewTess;
			else
				t = &ep->prodTess;
			ep->uSpin = SetupIntSpinner( hDlg, IDC_TESS_U_SPINNER, IDC_TESS_U, 1, 100, t->u);
			ep->vSpin = SetupIntSpinner( hDlg, IDC_TESS_V_SPINNER, IDC_TESS_V, 1, 100, t->v);
#define MAX_F 1000.0f
			ep->edgeSpin = SetupFloatSpinner( hDlg, IDC_TESS_EDGE_SPINNER, IDC_TESS_EDGE, 0.0f, MAX_F, t->edge);
			ep->distSpin = SetupFloatSpinner( hDlg, IDC_TESS_DIST_SPINNER, IDC_TESS_DIST, 0.0f, MAX_F, t->dist);
			ep->angSpin =  SetupFloatSpinner( hDlg, IDC_TESS_ANG_SPINNER,  IDC_TESS_ANG, 0.0f, MAX_F, t->ang);
			if (ep->settingViewportTess) {
				CheckRadioButton( hDlg, IDC_TESS_VIEW, IDC_TESS_RENDERER, IDC_TESS_VIEW);
				EnableWindow(GetDlgItem(hDlg, IDC_TESS_VIEW_DEP), FALSE);
			} else {
				CheckRadioButton( hDlg, IDC_TESS_VIEW, IDC_TESS_RENDERER, IDC_TESS_RENDERER);
				EnableWindow(GetDlgItem(hDlg, IDC_TESS_VIEW_DEP), TRUE);
			}
			ep->SetTessUI(hDlg, t);
		 	return TRUE;
			}

		case WM_DESTROY:
			if( ep->stepsSpin ) {
				ReleaseISpinner(ep->stepsSpin);
				ep->stepsSpin = NULL;
				}
			if( ep->uSpin ) {
				ReleaseISpinner(ep->uSpin);
				ep->uSpin = NULL;
				}
			if( ep->vSpin ) {
				ReleaseISpinner(ep->vSpin);
				ep->vSpin = NULL;
				}
			if( ep->edgeSpin ) {
				ReleaseISpinner(ep->edgeSpin);
				ep->edgeSpin = NULL;
				}
			if( ep->distSpin ) {
				ReleaseISpinner(ep->distSpin);
				ep->distSpin = NULL;
				}
			if( ep->angSpin ) {
				ReleaseISpinner(ep->angSpin);
				ep->angSpin = NULL;
				}
			// Don't leave in one of our modes!
			ep->iObjParams->ClearPickMode();
			CancelEditPatchModes(ep->iObjParams);
			return FALSE;
		
		case CC_SPINNER_CHANGE:
			switch ( LOWORD(wParam) ) {
				case IDC_STEPSSPINNER:
					ep->SetMeshSteps(ep->stepsSpin->GetIVal());
					break;
				case IDC_TESS_U_SPINNER:
				case IDC_TESS_V_SPINNER:
				case IDC_TESS_EDGE_SPINNER:
				case IDC_TESS_DIST_SPINNER:
				case IDC_TESS_ANG_SPINNER:
					{
					TessApprox tess;
					if (ep->settingViewportTess)
						tess = ep->viewTess;
					else
						tess = ep->prodTess;
					switch(LOWORD(wParam) ) {
					case IDC_TESS_U_SPINNER:
						tess.u = ep->uSpin->GetIVal();
						break;
					case IDC_TESS_V_SPINNER:
						tess.v = ep->vSpin->GetIVal();
						break;
					case IDC_TESS_EDGE_SPINNER:
						tess.edge = ep->edgeSpin->GetFVal();
						break;
					case IDC_TESS_DIST_SPINNER:
						tess.dist = ep->distSpin->GetFVal();
						break;
					case IDC_TESS_ANG_SPINNER:
						tess.ang = ep->angSpin->GetFVal();
						break;
					}
					if (ep->settingViewportTess)
						ep->SetViewTess(tess);
					else
						ep->SetProdTess(tess);
					}
					break;
				}
			break;

		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			ep->iObjParams->RollupMouseMessage(hDlg,message,wParam,lParam);
			return FALSE;		
		
		case WM_COMMAND:			
			switch ( LOWORD(wParam) ) {				
				case IDC_DISPLATTICE:
					ep->SetDisplayLattice(IsDlgButtonChecked(hDlg, IDC_DISPLATTICE));
					break;
				case IDC_DISPSURFACE:
					ep->SetDisplaySurface(IsDlgButtonChecked(hDlg, IDC_DISPSURFACE));
					break;
// Future use (Not used now)
//				case IDC_ADAPTIVE:
//					ep->SetMeshAdaptive(IsDlgButtonChecked(hDlg, IDC_ADAPTIVE));
//					break;
				case IDC_TESS_VIEW:
					ep->settingViewportTess = TRUE;
					ep->SetTessUI(hDlg, &ep->viewTess);
					EnableWindow(GetDlgItem(hDlg, IDC_TESS_VIEW_DEP), FALSE); // always off here
					break;
				case IDC_TESS_RENDERER:
					ep->settingViewportTess = FALSE;
					ep->SetTessUI(hDlg, &ep->prodTess);
					break;
				case IDC_TESS_SET:
				case IDC_TESS_PARAM:
				case IDC_TESS_SPATIAL:
				case IDC_TESS_CURV:
					{
					TessApprox tess;
					if (ep->settingViewportTess)
						tess = ep->viewTess;
					else
						tess = ep->prodTess;
					switch (LOWORD(wParam)) {
					case IDC_TESS_SET:
						tess.type = TESS_SET;
						break;
					case IDC_TESS_PARAM:
						tess.type = TESS_PARAM;
						break;
					case IDC_TESS_SPATIAL:
						tess.type = TESS_SPATIAL;
						break;
					case IDC_TESS_CURV:
						tess.type = TESS_CURVE;
						break;
					}
					if (ep->settingViewportTess) {
						ep->SetViewTess(tess);
						ep->SetTessUI(hDlg, &ep->viewTess);
						EnableWindow(GetDlgItem(hDlg, IDC_TESS_VIEW_DEP), FALSE);
					} else {
						ep->SetProdTess(tess);
						ep->SetTessUI(hDlg, &ep->prodTess);
					}
					}
					break;
				case IDC_TESS_VIEW_DEP: {
					TessApprox tess;
					tess = ep->prodTess;
					tess.view = IsDlgButtonChecked(hDlg, IDC_TESS_VIEW_DEP);
					ep->SetProdTess(tess);
					}
					break;
				case IDC_ATTACH: {
					ModContextList mcList;
					INodeTab nodes;
					// If the mode is on, turn it off and bail
					if (ep->iObjParams->GetCommandMode()->ID() == CID_STDPICK) {
						ep->iObjParams->SetStdCommandMode(CID_OBJMOVE);
						return FALSE;
						}
					// Want to turn on the mode.  Make sure we're valid first
					ep->iObjParams->GetModContexts(mcList,nodes);
					if(mcList.Count() > 1) {
						ep->iObjParams->DisplayTempPrompt(GetString(IDS_TH_CANHAVEONLYONEPATCH),PROMPT_TIME);
						ICustButton *but = GetICustButton(GetDlgItem(hDlg,IDC_ATTACH));
						but->SetCheck(FALSE);
						ReleaseICustButton(but);
						nodes.DisposeTemporary();
						return FALSE;
						}
					ep->pickCB.ep = ep;
					ep->iObjParams->SetPickMode(&ep->pickCB);
					nodes.DisposeTemporary();
					break;
					}
				case IDC_ATTACHREORIENT:
					attachReorient = IsDlgButtonChecked( hDlg, IDC_ATTACHREORIENT);
					break;
				}
			break;
		}
	
	return FALSE;
	}

BOOL CALLBACK PatchEdgeParamDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{
	EditPatchMod *ep = (EditPatchMod *)GetWindowLong( hDlg, GWL_USERDATA );
	if ( !ep && message != WM_INITDIALOG ) return FALSE;
	
	switch ( message ) {
		case WM_INITDIALOG: {
		 	ep = (EditPatchMod *)lParam;
		 	SetWindowLong( hDlg, GWL_USERDATA, (LONG)ep );		 	
			CheckDlgButton( hDlg, IDC_DISPSURFACE, ep->displaySurface);
			CheckDlgButton( hDlg, IDC_DISPLATTICE, ep->displayLattice);
			CheckDlgButton( hDlg, IDC_PROPAGATE, ep->EPropagate);
		 	return TRUE;
			}

		case WM_DESTROY:
			// Don't leave in one of our modes!
			CancelEditPatchModes(ep->iObjParams);
			return FALSE;
		
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			ep->iObjParams->RollupMouseMessage(hDlg,message,wParam,lParam);
			return FALSE;		
		
		case WM_COMMAND:			
			switch ( LOWORD(wParam) ) {				
				case IDC_DISPLATTICE:
					ep->SetDisplayLattice(IsDlgButtonChecked(hDlg, IDC_DISPLATTICE));
					break;
				case IDC_DISPSURFACE:
					ep->SetDisplaySurface(IsDlgButtonChecked(hDlg, IDC_DISPSURFACE));
					break;
				case IDC_PROPAGATE:
					ep->SetEPropagate(IsDlgButtonChecked(hDlg, IDC_PROPAGATE));
					break;
				case IDC_ADDTRI:
					ep->DoPatchAdd(PATCH_TRI);
					break;
				case IDC_ADDQUAD:
					ep->DoPatchAdd(PATCH_QUAD);
					break;
				case IDC_SUBDIVIDE:
					ep->DoEdgeSubdivide();
					break;
				}
			break;
		}
	
	return FALSE;
	}


//---------------------------------------------------------------------
// UI stuff

class PatchRightMenu : public RightClickMenu {
	private:
		EditPatchMod *ep;
	public:
		void Init(RightClickMenuManager* manager, HWND hWnd, IPoint2 m);
		void Selected(UINT id);
		void SetMod(EditPatchMod *ep) { this->ep = ep; }
	};

void PatchRightMenu::Init(RightClickMenuManager* manager, HWND hWnd, IPoint2 m) {
	if(ep->RememberPatchThere(hWnd, m)) {
		int oldType = -1;
		int flags1, flags2;
		flags1 = flags2 = MF_STRING;
		switch(ep->rememberedData) {
			case PATCH_AUTO:
				flags1 |= MF_CHECKED;
				break;
			case 0:
				flags2 |= MF_CHECKED;
				break;
			}
		manager->AddMenu(this, MF_SEPARATOR, 0, NULL);
		manager->AddMenu(this, flags1, PATCH_AUTO, GetString(IDS_TH_AUTOINTERIOR));
		manager->AddMenu(this, flags2, 0, GetString(IDS_TH_MANUALINTERIOR));
		}
	}

void PatchRightMenu::Selected(UINT id) {
	ep->SetRememberedPatchType((int)id);
	}

static BOOL CALLBACK SelectByMatDlgProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	static int *param;
	switch (msg) {
		case WM_INITDIALOG:
			param = (int*)lParam;
			SetupIntSpinner(hWnd,IDC_MAT_IDSPIN,IDC_MAT_ID,1,MAX_MATID,param[0]);			
			CheckDlgButton(hWnd,IDC_CLEARSELECTION,param[1]);
			CenterWindow(hWnd,GetParent(hWnd));
			break;

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDOK: {
					ISpinnerControl *spin = GetISpinner(GetDlgItem(hWnd,IDC_MAT_IDSPIN));
					param[0] = spin->GetIVal();
					param[1] = IsDlgButtonChecked(hWnd,IDC_CLEARSELECTION);
					ReleaseISpinner(spin);
					EndDialog(hWnd,1);					
					break;
					}

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

		default:
			return FALSE;
		}
	return TRUE;
	}

PatchRightMenu pMenu;

class PatchDeleteUser : public EventUser {
	private:
		EditPatchMod *ep;
	public:
		void Notify() { ep->DoPatchDelete(); }
		void SetMod(EditPatchMod *ep) { this->ep = ep; }
	};

static PatchDeleteUser pDel;

BOOL CALLBACK PatchPatchParamDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{
	EditPatchMod *ep = (EditPatchMod *)GetWindowLong( hDlg, GWL_USERDATA );
	if ( !ep && message != WM_INITDIALOG ) return FALSE;
	
	switch ( message ) {
		case WM_INITDIALOG: {
		 	ep = (EditPatchMod *)lParam;
			SetupIntSpinner(hDlg,IDC_MAT_IDSPIN,IDC_MAT_ID,1,MAX_MATID,0);
		 	SetWindowLong( hDlg, GWL_USERDATA, (LONG)ep );		 	
			pMenu.SetMod(ep);
			ep->iObjParams->GetRightClickMenuManager()->Register(&pMenu);
			pDel.SetMod(ep);
			ep->iObjParams->RegisterDeleteUser(&pDel);
			CheckDlgButton( hDlg, IDC_DISPSURFACE, ep->displaySurface);
			CheckDlgButton( hDlg, IDC_DISPLATTICE, ep->displayLattice);
			CheckDlgButton( hDlg, IDC_PROPAGATE, ep->PPropagate);
			CheckDlgButton( hDlg, IDC_DETACHCOPY, patchDetachCopy);
			CheckDlgButton( hDlg, IDC_DETACHREORIENT, patchDetachReorient);
		 	return TRUE;
			}

		case WM_PAINT:
			if (!ep->patchUIValid) {
				int mat = ep->GetSelMatIndex();
				ISpinnerControl *spin = GetISpinner(GetDlgItem(hDlg,IDC_MAT_IDSPIN));
				if (mat==-1) {
					spin->SetIndeterminate(TRUE);
				} else {
					spin->SetIndeterminate(FALSE);
					spin->SetValue(mat+1,FALSE);
					}
				ReleaseISpinner(spin);
				ep->patchUIValid = TRUE;
				}
			return FALSE;

		case WM_DESTROY:
			// Don't leave in one of our modes!
			CancelEditPatchModes(ep->iObjParams);
			ep->iObjParams->UnRegisterDeleteUser(&pDel);
			ep->iObjParams->GetRightClickMenuManager()->Unregister(&pMenu);
			return FALSE;

		case CC_SPINNER_BUTTONUP:
			switch( LOWORD(wParam) ) {
				case IDC_MAT_IDSPIN:
					ISpinnerControl *spin = (ISpinnerControl*)lParam;
					ep->SetSelMatIndex(spin->GetIVal()-1);
					ep->iObjParams->RedrawViews(
						ep->iObjParams->GetTime(),REDRAW_END);
					break;
				}
			break;
		case CC_SPINNER_CHANGE:
			if(HIWORD(wParam))
				break;		// No interactive action
			switch (LOWORD(wParam)) {
				case IDC_MAT_IDSPIN: {
					ISpinnerControl *spin = (ISpinnerControl*)lParam;
					ep->SetSelMatIndex(spin->GetIVal()-1);
					break;
					}
				}
			break;

		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			ep->iObjParams->RollupMouseMessage(hDlg,message,wParam,lParam);
			return FALSE;		
		
		case WM_COMMAND:			
			switch ( LOWORD(wParam) ) {				
				case IDC_DELETEPATCH:
					ep->DoPatchDelete();
					break;
				case IDC_DISPLATTICE:
					ep->SetDisplayLattice(IsDlgButtonChecked(hDlg, IDC_DISPLATTICE));
					break;
				case IDC_DISPSURFACE:
					ep->SetDisplaySurface(IsDlgButtonChecked(hDlg, IDC_DISPSURFACE));
					break;
				case IDC_PROPAGATE:
					ep->SetPPropagate(IsDlgButtonChecked(hDlg, IDC_PROPAGATE));
					break;
				case IDC_SUBDIVIDE:
					ep->DoPatchSubdivide();
					break;
				case IDC_DETACH:
					ep->DoPatchDetach(patchDetachCopy, patchDetachReorient);
					break;
				case IDC_DETACHCOPY:
					patchDetachCopy = IsDlgButtonChecked( hDlg, IDC_DETACHCOPY);
					break;
				case IDC_DETACHREORIENT:
					patchDetachReorient = IsDlgButtonChecked( hDlg, IDC_DETACHREORIENT);
					break;
				case IDC_SELECT_BYID: {										
					if (DialogBoxParam(
						hInstance, 
						MAKEINTRESOURCE(IDD_EM_SELECTBYMAT),
						hDlg, 
						SelectByMatDlgProc,
						(LPARAM)sbmParams)) {
					
						ep->SelectByMat(sbmParams[0]-1/*index*/,sbmParams[1]/*clear*/);
						}
					break;
					}
				}
			break;
		}
	
	return FALSE;
	}

class PVertexRightMenu : public RightClickMenu {
	private:
		EditPatchMod *ep;
	public:
		void Init(RightClickMenuManager* manager, HWND hWnd, IPoint2 m);
		void Selected(UINT id);
		void SetMod(EditPatchMod *ep) { this->ep = ep; }
	};

void PVertexRightMenu::Init(RightClickMenuManager* manager, HWND hWnd, IPoint2 m) {
	if(ep->RememberVertThere(hWnd, m)) {
		int oldType = -1;
		int flags1, flags2;
		flags1 = flags2 = MF_STRING;
		switch(ep->rememberedData) {
			case PVERT_COPLANAR:
				flags1 |= MF_CHECKED;
				break;
			case 0:
				flags2 |= MF_CHECKED;
				break;
			}
		manager->AddMenu(this, MF_SEPARATOR, 0, NULL);
		manager->AddMenu(this, flags1, PVERT_COPLANAR, GetString(IDS_TH_COPLANAR));
		manager->AddMenu(this, flags2, 0, GetString(IDS_TH_CORNER));
		}
	}

void PVertexRightMenu::Selected(UINT id) {
	ep->SetRememberedVertType((int)id);
	}

PVertexRightMenu vMenu;

class PatchVertexDeleteUser : public EventUser {
	private:
		EditPatchMod *ep;
	public:
		void Notify() { ep->DoVertDelete(); }
		void SetMod(EditPatchMod *ep) { this->ep = ep; }
	};

static PatchVertexDeleteUser vDel;

BOOL CALLBACK PatchVertexParamDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{
	EditPatchMod *ep = (EditPatchMod *)GetWindowLong( hDlg, GWL_USERDATA );
	if ( !ep && message != WM_INITDIALOG ) return FALSE;
	
	switch ( message ) {
		case WM_INITDIALOG: {
		 	ep = (EditPatchMod *)lParam;
		 	SetWindowLong( hDlg, GWL_USERDATA, (LONG)ep );		 	
			vMenu.SetMod(ep);
			CheckDlgButton( hDlg, IDC_LOCK_HANDLES, lockedHandles);
			ep->iObjParams->GetRightClickMenuManager()->Register(&vMenu);
			CheckDlgButton( hDlg, IDC_DISPLATTICE, ep->displayLattice);
			CheckDlgButton( hDlg, IDC_DISPSURFACE, ep->displaySurface);
			CheckDlgButton( hDlg, IDC_FILTVERTS, filterVerts);
			EnableWindow(GetDlgItem(hDlg,IDC_FILTVERTS), filterVecs ? TRUE : FALSE);
			CheckDlgButton( hDlg, IDC_FILTVECS, filterVecs);
			EnableWindow(GetDlgItem(hDlg,IDC_FILTVECS), filterVerts ? TRUE : FALSE);
			vDel.SetMod(ep);
			ep->iObjParams->RegisterDeleteUser(&vDel);
		 	ep->weldSpin = GetISpinner(GetDlgItem(hDlg,IDC_THRESHSPINNER));
			ep->weldSpin->SetLimits( 0, 999999, FALSE );
			ep->weldSpin->LinkToEdit( GetDlgItem(hDlg,IDC_WELDTHRESH), EDITTYPE_UNIVERSE );
			ep->weldSpin->SetValue(weldThreshold,FALSE);
		 	return TRUE;
			}

		case WM_DESTROY:
			// Don't leave in one of our modes!
			CancelEditPatchModes(ep->iObjParams);
			// Detach our right-menu stuff!
			ep->iObjParams->GetRightClickMenuManager()->Unregister(&vMenu);
			ep->iObjParams->UnRegisterDeleteUser(&vDel);
			if( ep->weldSpin ) {
				ReleaseISpinner(ep->weldSpin);
				ep->weldSpin = NULL;
				}
			return FALSE;

		case CC_SPINNER_CHANGE:
			switch ( LOWORD(wParam) ) {
				case IDC_THRESHSPINNER:
					weldThreshold = ep->weldSpin->GetFVal();
					break;
				}
			break;

		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			ep->iObjParams->RollupMouseMessage(hDlg,message,wParam,lParam);
			return FALSE;		
		
		case WM_COMMAND:			
			switch ( LOWORD(wParam) ) {				
				case IDC_DELETEVERT:
					ep->DoVertDelete();
					break;
				case IDC_DISPLATTICE:
					ep->SetDisplayLattice(IsDlgButtonChecked(hDlg, IDC_DISPLATTICE));
					break;
				case IDC_DISPSURFACE:
					ep->SetDisplaySurface(IsDlgButtonChecked(hDlg, IDC_DISPSURFACE));
					break;
				case IDC_WELD:
					ep->DoVertWeld();
					break;
				case IDC_LOCK_HANDLES:
					lockedHandles = IsDlgButtonChecked( hDlg, IDC_LOCK_HANDLES);
					break;
				case IDC_FILTVERTS:
					filterVerts = IsDlgButtonChecked(hDlg, IDC_FILTVERTS);
					EnableWindow(GetDlgItem(hDlg,IDC_FILTVECS), filterVerts ? TRUE : FALSE);
					SetVertFilter();
					break;
				case IDC_FILTVECS:
					filterVecs = IsDlgButtonChecked(hDlg, IDC_FILTVECS);
					EnableWindow(GetDlgItem(hDlg,IDC_FILTVERTS), filterVecs ? TRUE : FALSE);
					SetVertFilter();
					break;
				}
			break;
		}
	
	return FALSE;
	}

#define OLD_SEL_LEVEL_CHUNK 0x1000	// Original backwards ordering
#define SEL_LEVEL_CHUNK 0x1001
#define DISP_LATTICE_CHUNK 0x1010
#define DISP_SURFACE_CHUNK 0x1020
#define DISP_VERTS_CHUNK 0x1030
#define EPM_MESH_ATTRIB_CHUNK	0x1040
#define EPM_VTESS_ATTRIB_CHUNK	0x1090
#define EPM_PTESS_ATTRIB_CHUNK	0x10a0

// Names of named selection sets
#define NAMEDVSEL_NAMES_CHUNK	0x1050
#define NAMEDESEL_NAMES_CHUNK	0x1060
#define NAMEDPSEL_NAMES_CHUNK	0x1070
#define NAMEDSEL_STRING_CHUNK	0x1080

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


IOResult EditPatchMod::Save(ISave *isave) {
	Modifier::Save(isave);
	Interval valid;
	ULONG nb;
	isave->BeginChunk(SEL_LEVEL_CHUNK);
	isave->Write(&selLevel,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(DISP_LATTICE_CHUNK);
	isave->Write(&displayLattice,sizeof(BOOL),&nb);
	isave->	EndChunk();
	isave->BeginChunk(DISP_SURFACE_CHUNK);
	isave->Write(&displaySurface,sizeof(BOOL),&nb);
	isave->	EndChunk();
	isave->BeginChunk(EPM_MESH_ATTRIB_CHUNK);
	isave->Write(&meshSteps,sizeof(int),&nb);
// Future use (Not used now)
	BOOL fakeAdaptive = FALSE;
	isave->Write(&fakeAdaptive,sizeof(BOOL),&nb);
//	isave->Write(&meshAdaptive,sizeof(BOOL),&nb);	// Future use (Not used now)
	isave->	EndChunk();
	isave->BeginChunk(EPM_VTESS_ATTRIB_CHUNK);
	viewTess.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(EPM_PTESS_ATTRIB_CHUNK);
	prodTess.Save(isave);
	isave->	EndChunk();
	
	// Save names of named selection sets
	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->EndChunk();
			}
		}
	return IO_OK;
	}

IOResult EditPatchMod::LoadNamedSelChunk(ILoad *iload,int level)
	{	
	IOResult res;
	
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case NAMEDSEL_STRING_CHUNK: {
				TCHAR *name;
				res = iload->ReadWStringChunk(&name);
				AddSet(TSTR(name),level+1);
				break;
				}
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

IOResult EditPatchMod::Load(ILoad *iload) {
	Modifier::Load(iload);
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case NAMEDVSEL_NAMES_CHUNK: {				
				res = LoadNamedSelChunk(iload,0);
				break;
				}
			case NAMEDESEL_NAMES_CHUNK: {
				res = LoadNamedSelChunk(iload,1);
				break;
				}
			case NAMEDPSEL_NAMES_CHUNK: {
				res = LoadNamedSelChunk(iload,2);
				break;
				}

			case OLD_SEL_LEVEL_CHUNK:	// Correct backwards ordering
				{
				short sl;
				res = iload->Read(&sl,sizeof(short),&nb);
				selLevel = sl;
				switch(selLevel) {
					case 1:
						selLevel = EP_PATCH;
						break;
					case 3:
						selLevel = EP_VERTEX;
						break;
					}
				}
				break;
			case SEL_LEVEL_CHUNK:
				res = iload->Read(&selLevel,sizeof(int),&nb);
				break;
			case DISP_LATTICE_CHUNK:
				res = iload->Read(&displayLattice,sizeof(BOOL),&nb);
				break;
			case DISP_SURFACE_CHUNK:
				res = iload->Read(&displaySurface,sizeof(BOOL),&nb);
				break;
			case DISP_VERTS_CHUNK:
				iload->SetObsolete();
				break;
			case EPM_MESH_ATTRIB_CHUNK:
				res = iload->Read(&meshSteps,sizeof(int),&nb);
				res = iload->Read(&meshAdaptive,sizeof(BOOL),&nb);
				break;
			case EPM_VTESS_ATTRIB_CHUNK:
				viewTess.Load(iload);
				break;
			case EPM_PTESS_ATTRIB_CHUNK:
				prodTess.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}


#define EDITPATCHDATA_CHUNK 0x1000

IOResult EditPatchMod::SaveLocalData(ISave *isave, LocalModData *ld) {
	EditPatchData *ep = (EditPatchData *)ld;

	isave->BeginChunk(EDITPATCHDATA_CHUNK);
	ep->Save(isave);
	isave->EndChunk();

	return IO_OK;
	}

IOResult EditPatchMod::LoadLocalData(ILoad *iload, LocalModData **pld) {
	IOResult res;
	EditPatchData *ep;
	if (*pld==NULL) {
		*pld =(LocalModData *) new EditPatchData(this);
		}
	ep = (EditPatchData *)*pld;

	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case EDITPATCHDATA_CHUNK:
				res = ep->Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

PModRecord::~PModRecord() {
	// Disconnect from any restore object
	if(restoreObj) {
		restoreObj->RemoveRecord();
		restoreObj = NULL;
		}
//	DebugPrint(_T("ModRecord %d/%d deleting\n"),groupNumber,serialNumber);
	}

