/**********************************************************************
 *<
	FILE: editspl.cpp

	DESCRIPTION:  Edit BezierShape OSM

	CREATED BY: Tom Hudson, Dan Silva & Rolf Berteig

	HISTORY: created 25 April, 1995

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

// Uncomment this for boolean debugging
// (Leaves poly fragments without deleting unused or welding them)
//#define DEBUGBOOL 1

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

// in mods.cpp
extern HINSTANCE hInstance;

HWND                EditSplineMod::hEditSplineParams = NULL;
IObjParam*          EditSplineMod::iObjParams      = NULL;
MoveModBoxCMode*    EditSplineMod::moveMode        = NULL;
RotateModBoxCMode*  EditSplineMod::rotMode 	      = NULL;
UScaleModBoxCMode*  EditSplineMod::uscaleMode      = NULL;
NUScaleModBoxCMode* EditSplineMod::nuscaleMode     = NULL;
SquashModBoxCMode *	EditSplineMod::squashMode      = NULL;
SelectModBoxCMode*  EditSplineMod::selectMode      = NULL;
OutlineCMode*		EditSplineMod::outlineMode     = NULL;
SegBreakCMode*		EditSplineMod::segBreakMode    = NULL;
SegRefineCMode*		EditSplineMod::segRefineMode   = NULL;
VertConnectCMode*	EditSplineMod::vertConnectMode = NULL;
VertInsertCMode*	EditSplineMod::vertInsertMode  = NULL;
CreateLineCMode*	EditSplineMod::createLineMode  = NULL;
BooleanCMode*		EditSplineMod::booleanMode     = NULL;
BOOL               	EditSplineMod::inOutline       = FALSE;
BOOL               	EditSplineMod::inSegBreak      = FALSE;
ISpinnerControl*	EditSplineMod::outlineSpin     = NULL;
ISpinnerControl*	EditSplineMod::weldSpin        = NULL;
ICustButton *		EditSplineMod::iUnion          = NULL;
ICustButton *		EditSplineMod::iSubtraction    = NULL;
ICustButton *		EditSplineMod::iIntersection   = NULL;
ICustButton *		EditSplineMod::iMirrorHorizontal = NULL;
ICustButton *		EditSplineMod::iMirrorVertical   = NULL;
ICustButton *		EditSplineMod::iMirrorBoth     = NULL;
int                 EditSplineMod::boolType        = BOOL_UNION;
int                 EditSplineMod::mirrorType      = MIRROR_HORIZONTAL;
PickSplineAttach			EditSplineMod::pickCB;

// Checkbox items for rollup pages
static int polyMirrorCopy = 0;
static int polyDetachCopy = 0;
static int polyDetachReorient = 0;
static int segDetachCopy = 0;
static int segDetachReorient = 0;
static int attachReorient = 0;
static int centeredOutline = 0;
static int lockedHandles = 0;
static int lockType = IDC_LOCKALIKE;

// The weld threshold
static float weldThreshold = 0.1f;

// The boolean operation
static int boolOperation = BOOL_UNION;

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

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

void SetSplineHitOverride(int value) {
	splineHitOverride = value;
	}

void ClearSplineHitOverride() {
	splineHitOverride = 0;
	}

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

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

static
void CancelEditSplineModes(IObjParam *ip) {
	switch(ip->GetCommandMode()->ID()) {
		case CID_OUTLINE:
		case CID_SEGBREAK:
		case CID_SEGREFINE:
		case CID_VERTCONNECT:
		case CID_VERTINSERT:
		case CID_CREATELINE:
		case CID_BOOLEAN:
		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 Cancel2StepShapeModes(IObjParam *ip) {
	switch(ip->GetCommandMode()->ID()) {
		case CID_BOOLEAN:
			ip->SetStdCommandMode( CID_OBJMOVE );
			break;
		}
	}

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

static
BOOL IsCompatible(BezierShape *shape, int poly, BitArray *VSel, BitArray *SSel) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(VSel) {
		if(shape->splines[poly]->Verts() != VSel->GetSize())
			return FALSE;
		}
	if(SSel) {
		if(shape->splines[poly]->Segments() != SSel->GetSize())
			return FALSE;
		}
	return TRUE;
	}

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

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);
			SendMessage(GetDlgItem(hDlg, IDC_DETACH_NAME), EM_SETSEL, 0, -1);
			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 EditSplineClassDesc editSplineDesc;
extern ClassDesc* GetEditSplineModDesc() { return &editSplineDesc; }

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

ShapePointTab::ShapePointTab() {
	polys = 0;
	pUsed = NULL;
	ptab = NULL;
	ktab = NULL;
	ltab = NULL;
	}

ShapePointTab::~ShapePointTab() {
	if(pUsed)
		delete [] pUsed;
	if(ptab)
		delete [] ptab;
	if(ktab)
		delete [] ktab;
	if(ltab)
		delete [] ltab;
	}

void ShapePointTab::Empty() {
	if(pUsed)
		delete [] pUsed;
	pUsed = NULL;
	if(ptab)
		delete [] ptab;
	ptab = NULL;
	if(ktab)
		delete [] ktab;
	ktab = NULL;
	if(ltab)
		delete [] ltab;
	ltab = NULL;
	polys = 0;
	}

void ShapePointTab::Zero() {
	int i;
	for(int poly = 0; poly < polys; ++poly) {
		pUsed[poly] = 0;

		Point3Tab &pt = ptab[poly];
		IntTab &kt = ktab[poly];
		IntTab &st = ltab[poly];

		int points = pt.Count();
		int knots = kt.Count();
		int segments = st.Count();
		Point3 zero(0, 0, 0);

		for(i = 0; i < points; ++i)
			pt[i] = zero;
		for(i = 0; i < knots; ++i)
			kt[i] = 0;
		for(i = 0; i < segments; ++i)
			st[i] = 0;
		}
	}

void ShapePointTab::MakeCompatible(BezierShape& shape,int clear) {
	Point3 zero(0,0,0);
	int izero = 0;
	if(polys == shape.splineCount) {
		for(int i=0; i<polys; ++i) {
			int size = shape.splines[i]->Verts();
			int knots = shape.splines[i]->KnotCount();
			Point3Tab& tab = ptab[i];
			IntTab& kttab = ktab[i];
			IntTab& lttab = ltab[i];
			if(clear) {
				pUsed[i] = 0;
				tab.Delete(0,tab.Count());
				kttab.Delete(0,kttab.Count());
				lttab.Delete(0,lttab.Count());
				}
			if ( tab.Count() > size ) {
				int diff = tab.Count() - size;
				tab.Delete( tab.Count() - diff, diff );
				diff = kttab.Count() - knots;		
				kttab.Delete( kttab.Count() - diff, diff );		
				lttab.Delete( lttab.Count() - diff, diff );		
				}
			else
			if ( tab.Count() < size ) {
				int diff = size - tab.Count();
				tab.Resize( size );
				for ( int j = 0; j < diff; j++ )
					tab.Append(1,&zero);
				diff = knots - kttab.Count();
				kttab.Resize( knots );
				lttab.Resize( knots );
				for ( j = 0; j < diff; j++ ) {
					kttab.Append(1,&izero);
					lttab.Append(1,&izero);
					}
				}
			}
		}
	else {
		if(pUsed)
			delete [] pUsed;
		if(ptab)
			delete [] ptab;
		if(ktab)
			delete [] ktab;
		if(ltab)
			delete [] ltab;
		polys = shape.splineCount;
		pUsed = new int[polys];
		ptab = new Point3Tab[polys];
		ktab = new IntTab[polys];
		ltab = new IntTab[polys];
//		closures.SetSize(polys);
		for(int i=0; i<polys; ++i) {
			pUsed[i] = 0;
			Point3Tab& tab = ptab[i];
			IntTab& kttab = ktab[i];
			IntTab& lttab = ltab[i];
			int size = shape.splines[i]->Verts();
			for ( int j = 0; j < size; j++ )
				tab.Append(1,&zero);
			int knots = shape.splines[i]->KnotCount();
			tab.Resize( size );
			kttab.Resize( knots );
			lttab.Resize( knots );
			for ( j = 0; j < knots; j++ ) {
				kttab.Append(1,&izero);
				lttab.Append(1,&izero);
				}
			}
		}
	}

ShapePointTab& ShapePointTab::operator=(ShapePointTab& from) {
	if(pUsed)
		delete [] pUsed;
	if(ptab)
		delete [] ptab;
	if(ktab)
		delete [] ktab;
	if(ltab)
		delete [] ltab;
	polys = from.polys;
	pUsed = new int[polys];
	ptab = new Point3Tab[polys];
	ktab = new IntTab[polys];
	ltab = new IntTab[polys];
	for(int poly = 0; poly < polys; ++poly) {
		pUsed[poly] = from.pUsed[poly];
		ptab[poly] = from.ptab[poly];
		ktab[poly] = from.ktab[poly];
		ltab[poly] = from.ltab[poly];
		}
//	closures = from.closures;
	return *this;
	}

BOOL ShapePointTab::IsCompatible(BezierShape &shape) {
	if(polys != shape.splineCount)
		return FALSE;
	for(int poly = 0; poly < polys; ++poly) {
		if(ptab[poly].Count() != shape.splines[poly]->Verts())
			return FALSE;
		if(ktab[poly].Count() != shape.splines[poly]->KnotCount())
			return FALSE;
		}
	return TRUE;
	}

void ShapePointTab::RescaleWorldUnits(float f) {
	Matrix3 stm = ScaleMatrix(Point3(f, f, f));
	int i;
	for(int poly = 0; poly < polys; ++poly) {
		Point3Tab &pt = ptab[poly];
		int points = pt.Count();
		for(i = 0; i < points; ++i)
			pt[i] = pt[i] * stm;
		}
	}

#define SPT_POLYDATA_CHUNK	0x1000

IOResult ShapePointTab::Save(ISave *isave) {	
	int i, n;
	ULONG nb;
	isave->BeginChunk(SPT_POLYDATA_CHUNK);
	isave->Write(&polys,sizeof(int),&nb);
	for(int poly = 0; poly < polys; ++poly) {
		n = pUsed[poly];
		isave->Write(&n,sizeof(int),&nb);
		Point3Tab &pptab = ptab[poly];
		int count = pptab.Count();
		isave->Write(&count,sizeof(int),&nb);
		for(i = 0; i < count; ++i)
			isave->Write(&pptab[i],sizeof(Point3),&nb);
		IntTab &iktab = ktab[poly];
		count = iktab.Count();
		isave->Write(&count,sizeof(int),&nb);
		for(i = 0; i < count; ++i)
			isave->Write(&iktab[i],sizeof(int),&nb);
		IntTab &iltab = ltab[poly];
		count = iltab.Count();
		isave->Write(&count,sizeof(int),&nb);
		for(i = 0; i < count; ++i)
			isave->Write(&iltab[i],sizeof(int),&nb);
		}
	isave->EndChunk();
	return IO_OK;
	}

IOResult ShapePointTab::Load(ILoad *iload) {	
	int i, n;
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case SPT_POLYDATA_CHUNK:
				res = iload->Read(&polys,sizeof(int),&nb);
				pUsed = new int[polys];
				ptab = new Point3Tab[polys];
				ktab = new IntTab[polys];
				ltab = new IntTab[polys];
				for(int poly = 0; poly < polys; ++poly) {
					iload->Read(&n,sizeof(int),&nb);
					pUsed[poly] = n;
					Point3Tab &pptab = ptab[poly];
					int count;
					iload->Read(&count,sizeof(int),&nb);
					Point3 workpt;
					for(i = 0; i < count; ++i) {
						iload->Read(&workpt,sizeof(Point3),&nb);
						pptab.Append(1,&workpt);
						}
					IntTab &iktab = ktab[poly];
					iload->Read(&count,sizeof(int),&nb);
					for(i = 0; i < count; ++i) {
						iload->Read(&n,sizeof(int),&nb);
						iktab.Append(1,&n);
						}
					IntTab &iltab = ltab[poly];
					iload->Read(&count,sizeof(int),&nb);
					for(i = 0; i < count; ++i) {
						iload->Read(&n,sizeof(int),&nb);
						iltab.Append(1,&n);
						}
					}
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

void ShapeVertexDelta::SetSize(BezierShape& shape, BOOL load)
	{
	dtab.MakeCompatible(shape, FALSE);
	
	// Load it if necessary
	if(load) {
		for(int poly = 0; poly < shape.splineCount; ++poly) {
			Spline3D *spline = shape.splines[poly];
			int verts = spline->Verts();
			int knots = spline->KnotCount();
//			dtab.pUsed[poly] = 1;	// ???
			Point3Tab& delta = dtab.ptab[poly];
			IntTab& kdelta = dtab.ktab[poly];
			IntTab& ldelta = dtab.ltab[poly];
			for(int i = 0; i < verts; ++i)
				delta[i] = spline->GetVert(i);
			for(i = 0; i < knots; ++i) {
				kdelta[i] = spline->GetKnotType(i);
				ldelta[i] = spline->GetLineType(i);
				}
//			spline->ComputeBezPoints();
			}
//		shape.GetClosures(dtab.closures);
		}
	}

void ShapeVertexDelta::Apply(BezierShape &shape)
	{
	// This does nothing if the number of verts hasn't changed in the mesh.
	SetSize(shape, FALSE);

	// Apply the deltas
//	shape.SetClosures(dtab.closures);
	for(int poly = 0; poly < shape.splineCount; ++poly) {
//		if(dtab.pUsed[poly]) {
			Spline3D *spline = shape.splines[poly];
			int verts = spline->Verts();
			int knots = spline->KnotCount();
			Point3Tab& delta = dtab.ptab[poly];
			IntTab& kdelta = dtab.ktab[poly];
			IntTab& ldelta = dtab.ltab[poly];
			for(int i = 0; i < verts; ++i)
				spline->SetVert(i,spline->GetVert(i) + delta[i]);
			for(i = 0; i < knots; ++i) {
				spline->SetKnotType(i,spline->GetKnotType(i) ^ kdelta[i]);
				spline->SetLineType(i,spline->GetLineType(i) ^ ldelta[i]);
				}
			spline->ComputeBezPoints();
//			DebugPrint(_T("Poly %d used"),poly);
//			}
//		else
//			DebugPrint(_T("Poly %d not used"),poly);
		}
	shape.InvalidateGeomCache();
	}

void ShapeVertexDelta::UnApply(BezierShape &shape)
	{
	// This does nothing if the number of verts hasn't changed in the mesh.
	SetSize(shape, FALSE);

	// Apply the deltas
//	shape.SetClosures(dtab.closures);
	for(int poly = 0; poly < shape.splineCount; ++poly) {
//		if(dtab.pUsed[poly]) {
			Spline3D *spline = shape.splines[poly];
			int verts = spline->Verts();
			int knots = spline->KnotCount();
			Point3Tab& delta = dtab.ptab[poly];
			IntTab& kdelta = dtab.ktab[poly];
			IntTab& ldelta = dtab.ltab[poly];
			for(int i = 0; i < verts; ++i)
				spline->SetVert(i,spline->GetVert(i) - delta[i]);
			for(i = 0; i < knots; ++i) {
				spline->SetKnotType(i,spline->GetKnotType(i) ^ kdelta[i]);
				spline->SetLineType(i,spline->GetLineType(i) ^ ldelta[i]);
				}
			spline->ComputeBezPoints();
//			DebugPrint(_T("Poly %d used"),poly);
//			}
//		else
//			DebugPrint(_T("Poly %d not used"),poly);
		}
	shape.InvalidateGeomCache();
	}

// This function applies the current changes to slave handles and their knots, and zeroes everything else
void ShapeVertexDelta::ApplyHandlesAndZero(BezierShape &shape, int handlePoly, int handleVert) {
	// This does nothing if the number of verts hasn't changed in the mesh.
	SetSize(shape, FALSE);

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

	// Apply the deltas	to just the slave handles
	for(int poly = 0; poly < shape.splineCount; ++poly) {
//		if(dtab.pUsed[poly]) {
			Spline3D *spline = shape.splines[poly];
			int verts = spline->Verts();
			int knots = spline->KnotCount();
			Point3Tab& delta = dtab.ptab[poly];
			IntTab& kdelta = dtab.ktab[poly];
			IntTab& ldelta = dtab.ltab[poly];
			for(int i = 0; i < verts; ++i) {
				if(delta[i] != zeroPt) {
					if(!((poly == handlePoly) && (i == handleVert)))
						spline->SetVert(i,spline->GetVert(i) + delta[i]);
					else
						delta[i] = zeroPt;
					}
				}

			for(i = 0; i < knots; ++i) {
				if(kdelta[i])
					spline->SetKnotType(i,spline->GetKnotType(i) ^ kdelta[i]);
				if(ldelta[i])
					spline->SetLineType(i,spline->GetLineType(i) ^ ldelta[i]);
				}
//			spline->ComputeBezPoints();
//			DebugPrint(_T("Poly %d used"),poly);
//			}
//		else
//			DebugPrint(_T("Poly %d not used"),poly);
		}
	shape.InvalidateGeomCache();
	}


#define SVD_POINTTAB_CHUNK		0x1000

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

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

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

EditSplineData::EditSplineData()
	{
	flags = 0;
	tempData = NULL;
	changeGroup = -1;
	changeSerial = -1;
	}

EditSplineData::EditSplineData(EditSplineData& esc)
	{
	flags = esc.flags;
	tempData = NULL;
	changeGroup = esc.changeGroup;
	changeSerial = esc.changeSerial;
	// Now we must copy all the change data from the source EditSplineData object
	for(int i = 0; i < esc.changes.Count(); ++i) {
		ModRecord *ptr = esc.changes[i]->Clone();
		changes.Append(1, &ptr);
		}
	}


void EditSplineData::Apply(TimeValue t,SplineShape *splShape,int selLevel)
	{
	// Either just copy it from the existing cache or rebuild from previous level!
	if ( !GetFlag(ESD_UPDATING_CACHE) && tempData && tempData->ShapeCached(t) ) {
		splShape->shape.DeepCopy( 
			tempData->GetShape(t),
			PART_GEOM|SELECT_CHANNEL|PART_SUBSEL_TYPE|PART_DISPLAY|PART_TOPO );		
		splShape->PointsWereChanged();
		}	
	else if ( GetFlag(ESD_HASDATA) ) {
		// We have to (Ugh!) replay all our editing events!  Potential performance problem!
		int count = changes.Count();
		for(int i = 0; i < count; ++i) {
			ModRecord *rec = changes[i];
			// If we hit one that didn't play back OK, we need to flush the remainder
			if(!rec->Redo(&splShape->shape,1)) {
				for(int j = i; j < count; ++j)
					delete changes[j];
				changes.Delete(i, count - i);
				break;
				}
			}
		splShape->PointsWereChanged();

		// Kind of a waste when there's no animation...		
		splShape->UpdateValidity(GEOM_CHAN_NUM,FOREVER);
		splShape->UpdateValidity(TOPO_CHAN_NUM,FOREVER);
		splShape->UpdateValidity(SELECT_CHAN_NUM,FOREVER);
		splShape->UpdateValidity(SUBSEL_TYPE_CHAN_NUM,FOREVER);
		splShape->UpdateValidity(DISP_ATTRIB_CHAN_NUM,FOREVER);		
		}
	
	splShape->shape.dispFlags = 0;
	switch ( selLevel ) {
		case ES_SPLINE:
			splShape->shape.SetDispFlag(DISP_SELPOLYS);
			break;
		case ES_VERTEX:
			splShape->shape.SetDispFlag(DISP_VERTTICKS|DISP_SELVERTS);
			break;
		case ES_SEGMENT:
			splShape->shape.SetDispFlag(DISP_VERTTICKS|DISP_SELSEGMENTS);			
			break;
		}
	splShape->shape.selLevel = shapeLevel[selLevel];
	
	if ( GetFlag(ESD_UPDATING_CACHE) ) {
		assert(tempData);
		tempData->UpdateCache(splShape);
		SetFlag(ESD_UPDATING_CACHE,FALSE);
		}		
	}

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

void EditSplineData::BeginEdit(TimeValue t)
	{
	assert(tempData);
	if ( !GetFlag(ESD_HASDATA) ) {
		BezierShape *shape = tempData->GetShape(t);

//		tdelta.SetSize( shape->GetNumVerts(), TRUE );
//		vdelta.SetSize(	*shape, TRUE );
//		closures.SetSize( shape->splineCount );
//		shape->GetClosures(closures);
		SetFlag(ESD_HASDATA,TRUE);
		}
	}

ESTempData *EditSplineData::TempData(EditSplineMod *mod)
	{
	if ( !tempData ) {
		assert(mod->iObjParams);
		tempData = new ESTempData(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 EditSplineData::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 EditSplineData::AddChangeRecord(ModRecord *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 EditSplineData::AdoptChangeRecord(ModRecord *ptr) {
	changes.Append(1,&ptr);
	return 1;
	}

// Remove the last change record -- Returns 1 if more remain in this group
int EditSplineData::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 EditSplineData::RemoveChangeGroup() {
	while(RemoveChangeRecord());
	return 1;
	}

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

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

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

int EditSplineData::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 EditSplineData::RescaleWorldUnits(float f) {
	int count = changes.Count();
	for(int i = 0; i < count; ++i)
		changes[i]->RescaleWorldUnits(f);
	}

#define ESD_GENERAL_CHUNK			0x1000
#define CHANGE_CHUNK		0x1010

IOResult EditSplineData::Save(ISave *isave) {	
	ULONG nb;
	isave->BeginChunk(ESD_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();
	for(int i = 0; i < chCount; ++i) {
		isave->BeginChunk(changes[i]->ChunkID());
		changes[i]->Save(isave);
		isave->	EndChunk();
		}

	return IO_OK;
	}

IOResult EditSplineData::Load(ILoad *iload) {	
	static int chCount;
	IOResult res;
	ULONG nb;
	ModRecord *theChange;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case ESD_GENERAL_CHUNK:
				res = iload->Read(&flags,sizeof(DWORD),&nb);
				res = iload->Read(&changeGroup,sizeof(int),&nb);
				res = iload->Read(&changeSerial,sizeof(int),&nb);
				res = iload->Read(&chCount,sizeof(int),&nb);
				break;
			case CLEARVERTSELRECORD_CHUNK:
				theChange = new ClearVertSelRecord;
				goto load_change;
			case SETVERTSELRECORD_CHUNK:
				theChange = new SetVertSelRecord;
				goto load_change;
			case INVERTVERTSELRECORD_CHUNK:
				theChange = new InvertVertSelRecord;
				goto load_change;
			case CLEARSEGSELRECORD_CHUNK:
				theChange = new ClearSegSelRecord;
				goto load_change;
			case SETSEGSELRECORD_CHUNK:
				theChange = new SetSegSelRecord;
				goto load_change;
			case INVERTSEGSELRECORD_CHUNK:
				theChange = new InvertSegSelRecord;
				goto load_change;
			case CLEARPOLYSELRECORD_CHUNK:
				theChange = new ClearPolySelRecord;
				goto load_change;
			case SETPOLYSELRECORD_CHUNK:
				theChange = new SetPolySelRecord;
				goto load_change;
			case INVERTPOLYSELRECORD_CHUNK:
				theChange = new InvertPolySelRecord;
				goto load_change;
			case VERTSELRECORD_CHUNK:
				theChange = new VertSelRecord;
				goto load_change;
			case SEGSELRECORD_CHUNK:
				theChange = new SegSelRecord;
				goto load_change;
			case POLYSELRECORD_CHUNK:
				theChange = new PolySelRecord;
				goto load_change;
			case POLYCLOSERECORD_CHUNK:
				theChange = new PolyCloseRecord;
				goto load_change;
			case POLYREVERSERECORD_CHUNK:
				theChange = new PolyReverseRecord;
				goto load_change;
			case POLYMIRRORRECORD_CHUNK:
				theChange = new PolyMirrorRecord;
				goto load_change;
			case POLYENDATTACHRECORD_CHUNK:
				theChange = new PolyEndAttachRecord;
				goto load_change;
			case OUTLINERECORD_CHUNK:
				theChange = new OutlineRecord;
				goto load_change;
			case POLYDETACHRECORD_CHUNK:
				theChange = new PolyDetachRecord;
				goto load_change;
			case POLYDELETERECORD_CHUNK:
				theChange = new PolyDeleteRecord;
				goto load_change;
			case VERTMOVERECORD_CHUNK:
				theChange = new VertMoveRecord;
				goto load_change;
			case SEGDELETERECORD_CHUNK:
				theChange = new SegDeleteRecord;
				goto load_change;
			case SEGDETACHRECORD_CHUNK:
				theChange = new SegDetachRecord;
				goto load_change;
			case POLYFIRSTRECORD_CHUNK:
				theChange = new PolyFirstRecord;
				goto load_change;
			case SEGBREAKRECORD_CHUNK:
				theChange = new SegBreakRecord;
				goto load_change;
			case SEGREFINERECORD_CHUNK:
				theChange = new SegRefineRecord;
				goto load_change;
			case VERTBREAKRECORD_CHUNK:
				theChange = new VertBreakRecord;
				goto load_change;
			case VERTCONNECTRECORD_CHUNK:
				theChange = new VertConnectRecord;
				goto load_change;
			case VERTINSERTRECORD_CHUNK:
				theChange = new VertInsertRecord;
				goto load_change;
			case VERTWELDRECORD_CHUNK:
				theChange = new VertWeldRecord;
				goto load_change;
			case BOOLEANRECORD_CHUNK:
				theChange = new BooleanRecord;
				goto load_change;
			case ATTACHRECORD_CHUNK:
				theChange = new AttachRecord;
				goto load_change;
			case VERTCHANGERECORD_CHUNK:
				theChange = new VertChangeRecord;
				goto load_change;
			case SEGCHANGERECORD_CHUNK:
				theChange = new SegChangeRecord;
				goto load_change;
			case POLYCHANGERECORD_CHUNK:
				theChange = new PolyChangeRecord;
				goto load_change;
			case CREATELINERECORD_CHUNK:
				theChange = new CreateLineRecord;
				goto load_change;
			case POLYCOPYRECORD_CHUNK:
				theChange = new PolyCopyRecord;
				goto load_change;
			case SEGCOPYRECORD_CHUNK:
				theChange = new SegCopyRecord;
				goto load_change;
			case VERTDELETERECORD_CHUNK:
				theChange = new VertDeleteRecord;
				// 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 ClearVertSelRecord::Undo(BezierShape *shape) {
	if(!sel.IsCompatible(*shape))
		return FALSE;
	shape->vertSel = sel;
	return TRUE;
	}

BOOL ClearVertSelRecord::Redo(BezierShape *shape,int reRecord) {
	if(reRecord)
		sel = shape->vertSel;
	shape->vertSel.ClearAll();
	return TRUE;
	}

#define CVSR_SEL_CHUNK 0x1000

IOResult ClearVertSelRecord::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 ClearVertSelRecord::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 SetVertSelRecord::Undo(BezierShape *shape) {
	if(!sel.IsCompatible(*shape))
		return FALSE;
	shape->vertSel = sel;
	return TRUE;
	}

BOOL SetVertSelRecord::Redo(BezierShape *shape,int reRecord) {
	if(reRecord)
		sel = shape->vertSel;
	shape->vertSel.SetAll();
	return TRUE;
	}

#define SVSR_SEL_CHUNK 0x1000

IOResult SetVertSelRecord::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 SetVertSelRecord::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 InvertVertSelRecord::Undo(BezierShape *shape) {
	shape->vertSel.Toggle();
	return TRUE;
	}

BOOL InvertVertSelRecord::Redo(BezierShape *shape,int reRecord) {
	shape->vertSel.Toggle();
	return TRUE;
	}

IOResult InvertVertSelRecord::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 InvertVertSelRecord::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 ClearSegSelRecord::Undo(BezierShape *shape) {
	if(!sel.IsCompatible(*shape))
		return FALSE;
	shape->segSel = sel;
	return TRUE;
	}

BOOL ClearSegSelRecord::Redo(BezierShape *shape,int reRecord) {
	if(reRecord)
		sel = shape->segSel;
	shape->segSel.ClearAll();
	return TRUE;
	}

#define CSSR_SEL_CHUNK 0x1000

IOResult ClearSegSelRecord::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(CSSR_SEL_CHUNK);
	sel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult ClearSegSelRecord::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 CSSR_SEL_CHUNK:
				res = sel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

BOOL SetSegSelRecord::Undo(BezierShape *shape) {
	if(!sel.IsCompatible(*shape))
		return FALSE;
	shape->segSel = sel;
	return TRUE;
	}

BOOL SetSegSelRecord::Redo(BezierShape *shape,int reRecord) {
	if(reRecord)
		sel = shape->segSel;
	shape->segSel.SetAll();
	return TRUE;
	}

#define SSSR_SEL_CHUNK 0x1000

IOResult SetSegSelRecord::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(SSSR_SEL_CHUNK);
	sel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SetSegSelRecord::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 SSSR_SEL_CHUNK:
				res = sel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

BOOL InvertSegSelRecord::Undo(BezierShape *shape) {
	shape->segSel.Toggle();
	return TRUE;
	}

BOOL InvertSegSelRecord::Redo(BezierShape *shape,int reRecord) {
	shape->segSel.Toggle();
	return TRUE;
	}

IOResult InvertSegSelRecord::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 InvertSegSelRecord::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 ClearPolySelRecord::Undo(BezierShape *shape) {
	if(!sel.IsCompatible(*shape))
		return FALSE;
	shape->polySel = sel;
	return TRUE;
	}

BOOL ClearPolySelRecord::Redo(BezierShape *shape,int reRecord) {
	if(reRecord)
		sel = shape->polySel;
	shape->polySel.ClearAll();
	return TRUE;
	}

#define CPSR_SEL_CHUNK 0x1000

IOResult ClearPolySelRecord::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 ClearPolySelRecord::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 SetPolySelRecord::Undo(BezierShape *shape) {
	if(!sel.IsCompatible(*shape))
		return FALSE;
	shape->polySel = sel;
	return TRUE;
	}

BOOL SetPolySelRecord::Redo(BezierShape *shape,int reRecord) {
	if(reRecord)
		sel = shape->polySel;
	shape->polySel.SetAll();
	return TRUE;
	}

#define SPSR_SEL_CHUNK 0x1000

IOResult SetPolySelRecord::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 SetPolySelRecord::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 InvertPolySelRecord::Undo(BezierShape *shape) {
	shape->polySel.Toggle();
	return TRUE;
	}

BOOL InvertPolySelRecord::Redo(BezierShape *shape,int reRecord) {
	shape->polySel.Toggle();
	return TRUE;
	}

IOResult InvertPolySelRecord::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 InvertPolySelRecord::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 VertSelRecord::Undo(BezierShape *shape) {
	if(!oldSel.IsCompatible(*shape))
		return FALSE;
	shape->vertSel = oldSel;
	return TRUE;
	}

BOOL VertSelRecord::Redo(BezierShape *shape,int reRecord) {
	if(!newSel.IsCompatible(*shape))
		return FALSE;
	shape->vertSel = newSel;
	return TRUE;
	}

#define VSR_OLDSEL_CHUNK 0x1000
#define VSR_NEWSEL_CHUNK 0x1010

IOResult VertSelRecord::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 VertSelRecord::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 SegSelRecord::Undo(BezierShape *shape) {
	if(!oldSel.IsCompatible(*shape))
		return FALSE;
	shape->segSel = oldSel;
	return TRUE;
	}

BOOL SegSelRecord::Redo(BezierShape *shape,int reRecord) {
	if(!newSel.IsCompatible(*shape))
		return FALSE;
	shape->segSel = newSel;
	return TRUE;
	}

#define SSR_OLDSEL_CHUNK 0x1000
#define SSR_NEWSEL_CHUNK 0x1010

IOResult SegSelRecord::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(SSR_OLDSEL_CHUNK);
	oldSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SSR_NEWSEL_CHUNK);
	newSel.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SegSelRecord::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 SSR_OLDSEL_CHUNK:
				res = oldSel.Load(iload);
				break;
			case SSR_NEWSEL_CHUNK:
				res = newSel.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

BOOL PolySelRecord::Undo(BezierShape *shape) {
	if(!oldSel.IsCompatible(*shape))
		return FALSE;
	shape->polySel = oldSel;
	return TRUE;
	}

BOOL PolySelRecord::Redo(BezierShape *shape,int reRecord) {
	if(!newSel.IsCompatible(*shape))
		return FALSE;
	shape->polySel = newSel;
	return TRUE;
	}

#define PSR_OLDSEL_CHUNK 0x1000
#define PSR_NEWSEL_CHUNK 0x1010

IOResult PolySelRecord::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 PolySelRecord::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;
	}

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

BOOL PolyCloseRecord::Undo(BezierShape *shape) {
	if(poly >= shape->splineCount)
		return FALSE;
	Spline3D *spline = shape->splines[poly];
	spline->SetOpen();
	spline->ComputeBezPoints();
	shape->InvalidateGeomCache();
	shape->vertSel[poly].SetSize(spline->Verts(),1);
	shape->segSel[poly].SetSize(spline->Segments(),1);
	shape->polySel.Set(poly);
	return TRUE;
	}

BOOL PolyCloseRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	Spline3D *spline = shape->splines[poly];
	spline->SetClosed();
	spline->ComputeBezPoints();
	shape->InvalidateGeomCache();
	shape->vertSel[poly].SetSize(spline->Verts(),1);
	shape->segSel[poly].SetSize(spline->Segments(),1);
	shape->segSel[poly].Clear(spline->Segments()-1);
	shape->polySel.Clear(poly);
	return TRUE;
	}

#define PCR_POLY_CHUNK 0x1000

IOResult PolyCloseRecord::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(PCR_POLY_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PolyCloseRecord::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 PCR_POLY_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

BOOL PolyReverseRecord::Undo(BezierShape *shape) {
	if(poly >= shape->splineCount)
		return FALSE;
	shape->Reverse(poly);
	shape->splines[poly]->ComputeBezPoints();
	shape->InvalidateGeomCache();
	return TRUE;
	}

BOOL PolyReverseRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	shape->Reverse(poly);
	shape->splines[poly]->ComputeBezPoints();
	shape->InvalidateGeomCache();
	return TRUE;
	}

#define PRR_POLY_CHUNK 0x1000

IOResult PolyReverseRecord::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(PRR_POLY_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PolyReverseRecord::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 PRR_POLY_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

static void MirrorPoly(BezierShape *shape, int poly, int type, BOOL copy) {
	if(copy) {
		Spline3D *newSpline = shape->NewSpline();
		*newSpline = *shape->splines[poly];
		shape->polySel.Clear(poly);		// Unselect the old one
		poly = shape->SplineCount() - 1;
		// Don't forget to create a new selection record for this new spline!
		shape->vertSel.Insert(poly, newSpline->KnotCount() * 3);
		shape->segSel.Insert(poly, newSpline->Segments());
		shape->polySel.Insert(poly);
		shape->polySel.Set(poly);	// Select the new one!
		}
	// Now mirror it!
	Spline3D *spline = shape->splines[poly];
	// Find its center
	Box3 bbox;
	bbox.Init();
	for(int k = 0; k < spline->KnotCount(); ++k)
		bbox += spline->GetKnotPoint(k);

	Point3 center = bbox.Center();
	for(k = 0; k < spline->KnotCount(); ++k) {
		Point3 knot = spline->GetKnotPoint(k);
		Point3 in = spline->GetInVec(k);
		Point3 out = spline->GetOutVec(k);
		if(type == MIRROR_BOTH || type == MIRROR_HORIZONTAL) {
			knot.x = center.x - (knot.x - center.x);
			in.x = center.x - (in.x - center.x);
			out.x = center.x - (out.x - center.x);
			}
		if(type == MIRROR_BOTH || type == MIRROR_VERTICAL) {
			knot.y = center.y - (knot.y - center.y);
			in.y = center.y - (in.y - center.y);
			out.y = center.y - (out.y - center.y);
			}
		spline->SetKnotPoint(k, knot);
		spline->SetInVec(k, in);
		spline->SetOutVec(k, out);
		}
	spline->ComputeBezPoints();
	shape->InvalidateGeomCache();
	}

BOOL PolyMirrorRecord::Undo(BezierShape *shape) {
	if(copy) {
		if(poly >= (shape->splineCount - 1))
			return FALSE;
		shape->DeleteSpline(shape->SplineCount() - 1);
		shape->polySel.Set(poly);
		}
	else {
		if(poly >= shape->splineCount)
			return FALSE;
		MirrorPoly(shape, poly, type, FALSE);
		}
	return TRUE;
	}

BOOL PolyMirrorRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	MirrorPoly(shape, poly, type, copy);
	return TRUE;
	}

#define PMR_POLY_CHUNK 0x1000
#define PMR_TYPE_CHUNK 0x1010
#define PMR_COPY_CHUNK 0x1020

IOResult PolyMirrorRecord::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(PMR_POLY_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PMR_TYPE_CHUNK);
	isave->Write(&type,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PMR_COPY_CHUNK);
	isave->Write(&copy,sizeof(BOOL),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PolyMirrorRecord::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 PMR_POLY_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				break;
			case PMR_TYPE_CHUNK:
				res = iload->Read(&type,sizeof(int),&nb);
				break;
			case PMR_COPY_CHUNK:
				res = iload->Read(&copy,sizeof(BOOL),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

static void DoPolyEndAttach(BezierShape *shape, int poly1, int vert1, int poly2, int vert2) {
	Spline3D *spline = shape->splines[poly1];
	int knots = spline->KnotCount();
	int verts = spline->Verts();
	int segs = spline->Segments();

	// If connecting endpoints of the same polygon, close it and get rid of vertex 1
	if(poly1 == poly2) {
		spline->SetClosed();
		BitArray& vsel = shape->vertSel[poly1];
		BitArray& ssel = shape->segSel[poly1];
		BOOL bothAuto = (spline->GetKnotType(vert1) == KTYPE_AUTO && spline->GetKnotType(vert2) == KTYPE_AUTO) ? TRUE : FALSE;
		int lastVert = knots - 1;
		Point3 p1 = spline->GetKnotPoint(0);
		Point3 p2 = spline->GetKnotPoint(lastVert);
		Point3 midpoint = (p1 + p2) / 2.0f;
		Point3 p1Delta = midpoint - p1;
		Point3 p2Delta = midpoint - p2;
		// Repoint the knot
		spline->SetKnotPoint(0, midpoint);
		if(!bothAuto) {
			spline->SetKnotType(0, KTYPE_BEZIER_CORNER);
			spline->SetInVec(0, spline->GetInVec(lastVert) + p2Delta);
			spline->SetOutVec(0, spline->GetOutVec(0) + p1Delta);
			}
		spline->DeleteKnot(lastVert);
		// Make the selection sets the right size
		vsel.SetSize(spline->Verts(), 1);
		ssel.SetSize(spline->Segments(), 1);
		spline->ComputeBezPoints();
		shape->InvalidateGeomCache();
		}
	else { 		// Connecting two different splines -- Splice 'em together!
		Spline3D *spline2 = shape->splines[poly2];
		BOOL bothAuto = (spline->GetKnotType(vert1) == KTYPE_AUTO && spline2->GetKnotType(vert2) == KTYPE_AUTO) ? TRUE : FALSE;
		int knots2 = spline2->KnotCount();
		int verts2 = spline2->Verts();
		int segs2 = spline2->Segments();
		// Enlarge the selection sets for the first spline
 		BitArray& vsel = shape->vertSel[poly1];
		BitArray& ssel = shape->segSel[poly1];
 		BitArray& vsel2 = shape->vertSel[poly2];
		BitArray& ssel2 = shape->segSel[poly2];
		
		// Reorder the splines if necessary -- We set them up so that poly 1 is first,
		// ordered so that its connection point is the last knot.  Then we order poly
		// 2 so that its connection point is the first knot.  We then copy the invec
		// of poly 1's last knot to the invec of poly 2's first knot, delete poly 1's
		// last knot and append poly 2 to poly 1.  We then delete poly 2.

		if(vert1 == 0) {
			spline->Reverse();
			vsel.Reverse();
			ssel.Reverse();
			}
		if(vert2 != 0) {
			spline2->Reverse();
			vsel2.Reverse();
			ssel2.Reverse();
			}
		
		int lastVert = knots - 1;
		Point3 p1 = spline->GetKnotPoint(lastVert);
		Point3 p2 = spline2->GetKnotPoint(0);
		Point3 midpoint = (p1 + p2) / 2.0f;
		Point3 p1Delta = midpoint - p1;
		Point3 p2Delta = midpoint - p2;
		spline->SetKnotPoint(lastVert, midpoint);
		if(!bothAuto) {
			spline2->SetKnotType(0, KTYPE_BEZIER_CORNER);
			spline2->SetInVec(0, spline->GetInVec(lastVert) + p1Delta);
			spline2->SetOutVec(0, spline2->GetOutVec(0) + p2Delta);
			}
		spline->DeleteKnot(lastVert);
		BOOL welded = spline->Append(spline2);

		// Fix up the selection sets
		int base = verts - 3;
		vsel.SetSize(spline->Verts(), 1);
		vsel.Set(base+1);
		for(int i = welded ? 6 : 3; i < verts2; ++i)
			vsel.Set(base+i, vsel2[i]);
		base = segs;
		ssel.SetSize(spline->Segments(), 1);
		for(i = welded ? 1 : 0; i < segs2; ++i)
			ssel.Set(base+i, ssel2[i]);

		// Compute bezier handles and get rid of the attachee
		spline->ComputeBezPoints();
		shape->DeleteSpline(poly2);
		}
	}

PolyEndAttachRecord::PolyEndAttachRecord(BezierShape *shape, int poly1, int vert1, int poly2, int vert2) {
	this->poly1 = poly1;
	this->vert1 = vert1;
	this->poly2 = poly2;
	this->vert2 = vert2;
	oldSpline1 = *(shape->splines[poly1]);
	oldVSel1 = shape->vertSel[poly1];
	oldSSel1 = shape->segSel[poly1];
	if(poly1 != poly2) {
		oldSpline2 = *(shape->splines[poly2]);
		oldVSel2 = shape->vertSel[poly2];
		oldSSel2 = shape->segSel[poly2];
		selected2 = shape->polySel[poly2];
		}
	}

BOOL PolyEndAttachRecord::Undo(BezierShape *shape) {
	if(poly1 != poly2) {
		if(poly2 > shape->splineCount)
			return FALSE;
		shape->InsertSpline(&oldSpline2, poly2);
		if(!IsCompatible(shape, poly2, &oldVSel2, &oldSSel2))
			return FALSE;
		shape->vertSel[poly2] = oldVSel2;
		shape->segSel[poly2] = oldSSel2;
		shape->polySel.Set(poly2,selected2);
		}
	if(poly1 >= shape->splineCount)
		return FALSE;
	shape->DeleteSpline(poly1);
	shape->InsertSpline(&oldSpline1, poly1);
	if(!IsCompatible(shape, poly1, &oldVSel1, &oldSSel1))
		return FALSE;
	shape->vertSel[poly1] = oldVSel1;
	shape->segSel[poly1] = oldSSel1;
	return TRUE;
	}

BOOL PolyEndAttachRecord::Redo(BezierShape *shape,int reRecord) {
	if(!IsCompatible(shape, poly1, &oldVSel1, &oldSSel1))
		return FALSE;
	if(reRecord) {
		oldSpline1 = *(shape->splines[poly1]);
		oldVSel1 = shape->vertSel[poly1];
		oldSSel1 = shape->segSel[poly1];
		if(poly1 != poly2) {
			oldSpline2 = *(shape->splines[poly2]);
			oldVSel2 = shape->vertSel[poly2];
			oldSSel2 = shape->segSel[poly2];
			selected2 = shape->polySel[poly2];
			}
		}
	DoPolyEndAttach(shape, poly1, vert1, poly2, vert2);
	return TRUE;
	}

#define PEAR_GENERAL_CHUNK		0x1000
#define PEAR_OLDVSEL1_CHUNK	0x1040
#define PEAR_OLDSSEL1_CHUNK	0x1050
#define PEAR_SPLINE1_CHUNK		0x1060
#define PEAR_OLDVSEL2_CHUNK	0x1070
#define PEAR_OLDSSEL2_CHUNK	0x1080
#define PEAR_SPLINE2_CHUNK		0x1090

IOResult PolyEndAttachRecord::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(PEAR_GENERAL_CHUNK);
	isave->Write(&poly1,sizeof(int),&nb);
	isave->Write(&vert1,sizeof(int),&nb);
	isave->Write(&poly2,sizeof(int),&nb);
	isave->Write(&vert2,sizeof(int),&nb);
	isave->Write(&selected2,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PEAR_OLDVSEL1_CHUNK);
	oldVSel1.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PEAR_OLDSSEL1_CHUNK);
	oldSSel1.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PEAR_SPLINE1_CHUNK);
	oldSpline1.Save(isave);
	isave->	EndChunk();
	if(poly1 != poly2) {
		isave->BeginChunk(PEAR_OLDVSEL2_CHUNK);
		oldVSel2.Save(isave);
		isave->	EndChunk();
		isave->BeginChunk(PEAR_OLDSSEL2_CHUNK);
		oldSSel2.Save(isave);
		isave->	EndChunk();
		isave->BeginChunk(PEAR_SPLINE2_CHUNK);
		oldSpline2.Save(isave);
		isave->	EndChunk();
		}
	return IO_OK;
	}

IOResult PolyEndAttachRecord::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 PEAR_GENERAL_CHUNK:
				res = iload->Read(&poly1,sizeof(int),&nb);
				res = iload->Read(&vert1,sizeof(int),&nb);
				res = iload->Read(&poly2,sizeof(int),&nb);
				res = iload->Read(&vert2,sizeof(int),&nb);
				res = iload->Read(&selected2,sizeof(int),&nb);
				break;
			case PEAR_OLDVSEL1_CHUNK:
				res = oldVSel1.Load(iload);
				break;
			case PEAR_OLDSSEL1_CHUNK:
				res = oldSSel1.Load(iload);
				break;
			case PEAR_SPLINE1_CHUNK:
				res = oldSpline1.Load(iload);
				break;
			case PEAR_OLDVSEL2_CHUNK:
				res = oldVSel2.Load(iload);
				break;
			case PEAR_OLDSSEL2_CHUNK:
				res = oldSSel2.Load(iload);
				break;
			case PEAR_SPLINE2_CHUNK:
				res = oldSpline2.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

OutlineRecord::OutlineRecord(BezierShape *shape, int poly, int centered) : ModRecord() {
	newType = TRUE;
	this->poly = poly;
	this->centered = centered;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	oldSplineCount = shape->splineCount;
	}

BOOL OutlineRecord::Undo(BezierShape *shape) {
	// Get rid of any new polys we created...
	while(shape->splineCount > oldSplineCount)
		shape->DeleteSpline(shape->splineCount - 1);
	if(oldSplineCount != shape->splineCount)
		return FALSE;
	// Get rid of the one we changed
	shape->DeleteSpline(poly);
	// Put back the original poly we changed...
	shape->InsertSpline(&oldSpline,poly);
	if(!IsCompatible(shape, poly, &oldVSel, &oldSSel))
		return FALSE;;
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	shape->polySel.Set(poly);
	return TRUE;
	}

#define CURVELENGTHSTEPS 5
static float CurveLength(Spline3D *spline, int knot, float v1, float v2, float size = 0.0f) {
	float len = 0.0f;
	if(size == 0.0f) {	// Simple curve length
		Point3 p1,p2;
		p1 = spline->InterpBezier3D(knot, v1);
		float step = (v2 - v1) / CURVELENGTHSTEPS;
		float pos;
		int i;
		for(i = 1, pos = step; i < CURVELENGTHSTEPS; ++i, pos += step) {
			p2 = spline->InterpBezier3D(knot, pos);
			len += Length(p2 - p1);
			p1 = p2;
			}
		len += Length(spline->InterpBezier3D(knot, v2) - p1);
		}
	else {	// Need to figure based on displaced location
		int knots = spline->KnotCount();
		int prev = (knot + knots - 1) % knots;
		int next = (knot + 1) % knots;
		float pv = v1 - 0.01f;
		int pk = knot;
		if(pv < 0.0f) {
			if(spline->Closed()) {
				pv += 1.0f;
				pk = prev;
				}
			else
				pv = 0.0f;
			}
		float nv = v1 + 0.01f;
		Point3 direction = Normalize(spline->InterpBezier3D(knot, nv) - spline->InterpBezier3D(pk, pv));
		direction.z = 0.0f;	// Keep it in the XY plane
		Point3 perp = Point3(direction.y * size, -direction.x * size, 0.0f);

		Point3 p1,p2;
		p1 = spline->InterpBezier3D(knot, v1) + perp;	// Got 1st displaced point

		float step = (v2 - v1) / CURVELENGTHSTEPS;
		float pos;
		int i;
		for(i = 1, pos = step; i < CURVELENGTHSTEPS; ++i, pos += step) {
			pv = pos - 0.01f;
			nv = pos + 0.01f;
			direction = Normalize(spline->InterpBezier3D(knot, nv) - spline->InterpBezier3D(knot, pv));
			direction.z = 0.0f;	// Keep it in the XY plane
			perp = Point3(direction.y * size, -direction.x * size, 0.0f);

			p2 = spline->InterpBezier3D(knot, pos) + perp;
			len += Length(p2 - p1);
			p1 = p2;
			}
		pv = v2 - 0.01f;
		int nk = knot;
		nv = v2 + 0.01f;
		if(pv > 1.0f) {
			if(spline->Closed()) {
				nv -= 1.0f;
				nk = next;
				}
			else
				nv = 1.0f;
			}
		direction = Normalize(spline->InterpBezier3D(nk, nv) - spline->InterpBezier3D(knot, pv));
		direction.z = 0.0f;	// Keep it in the XY plane
		perp = Point3(direction.y * size, -direction.x * size, 0.0f);

		len += Length((spline->InterpBezier3D(knot, v2) + perp) - p1);
		}
	return len;
	}

static
void OutlineSpline(BezierShape *shape, int poly, float size, int centered, BOOL newType) {
	// IMPORTANT: The 'else' case to 'if(newType)' must be left as is!  This is code
	// which operates only on modification records brought in from release 1.x of MAX.
	// The 'newType' code operates on MAX 2.0 and later.
	if(newType) {
		Spline3D *inSpline = shape->splines[poly];
		Spline3D outSpline;
		// Do some basic calculations that we'll need regardless
		float size1 = (centered) ? size / 2.0f : 0.0f;	// First phase offset
		float size2 = (centered) ? -size / 2.0f : -size;	// Second phase offset
		int knots = inSpline->KnotCount();
		Point3 knot, in, out;
		int i;
		Matrix3 theMatrix;

		// If the input spline is closed, we wind up with two polygons
		if(inSpline->Closed()) {
			Spline3D *outSpline2 = shape->NewSpline();
			// Generate the outline polygons...
			for(i = 0; i < knots; ++i) {
				int prevKnot = (i + knots - 1) % knots;
				float oldInLength = CurveLength(inSpline, prevKnot, 0.5f, 1.0f);
				float oldOutLength = CurveLength(inSpline, i, 0.0f, 0.5f);
				int knotType = inSpline->GetKnotType(i);
				// Determine the angle of the curve at this knot
				// Get vector from interp before knot to interp after knot
				Point3 ko = inSpline->GetKnotPoint(i);
				Point3 bVec = Normalize(inSpline->InterpBezier3D(prevKnot, 0.99f) - ko);
				Point3 fVec = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
				Point3 direction = Normalize(fVec - bVec);
				direction.z = 0.0f;	// Keep it in the XY plane
				// Figure the size multiplier for the crotch angle
				float dot = DotProd(bVec, fVec);
				float angle, wsize1, wsize2;
				if(dot >= -0.9999939f)
					angle = (float)-acos(dot) / 2.0f;
				else
					angle = HALFPI;
				float base1 = size1 / (float)tan(angle);
				float sign1 = (size1 < 0.0f) ? -1.0f : 1.0f;
				wsize1 = (float)sqrt(base1 * base1 + size1 * size1) * sign1;
				float base2 = size2 / (float)tan(angle);
				float sign2 = (size2 < 0.0f) ? -1.0f : 1.0f;
				wsize2 = (float)sqrt(base2 * base2 + size2 * size2) * sign2;

				Point3 perp(direction.y * wsize1, -direction.x * wsize1, 0.0f);
				float newInLength = CurveLength(inSpline, prevKnot, 0.5f, 1.0f, size1);
				float newOutLength = CurveLength(inSpline, i, 0.0f, 0.5f, size1);
				Point3 kn = ko + perp;
				float inMult = newInLength / oldInLength;
				float outMult = newOutLength / oldOutLength;
				SplineKnot k(knotType, LTYPE_CURVE,
					kn, kn + (inSpline->GetInVec(i) - ko) * inMult, kn + (inSpline->GetOutVec(i) - ko) * outMult);
				outSpline.AddKnot(k);
				perp = Point3(direction.y * wsize2, -direction.x * wsize2, 0.0f);
				newInLength = CurveLength(inSpline, prevKnot, 0.5f, 1.0f, size2);
				newOutLength = CurveLength(inSpline, i, 0.0f, 0.5f, size2);
				kn = ko + perp;
				inMult = newInLength / oldInLength;
				outMult = newOutLength / oldOutLength;
				k = SplineKnot(knotType, LTYPE_CURVE,
					kn, kn + (inSpline->GetInVec(i) - ko) * inMult, kn + (inSpline->GetOutVec(i) - ko) * outMult);
				outSpline2->AddKnot(k);
				}
			outSpline.SetClosed();
			outSpline.ComputeBezPoints();
			*inSpline = outSpline;
			outSpline2->SetClosed();
			outSpline2->ComputeBezPoints();
			shape->vertSel.Insert(shape->SplineCount() - 1, knots * 3);
			shape->segSel.Insert(shape->SplineCount() - 1, knots);
			shape->polySel.Insert(shape->SplineCount() - 1);
			}
		else {	// Otherwise, we get one closed polygon
			// Generate the outline polygon...
			for(i = 0; i < knots; ++i) {
				// Determine the angle of the curve at this knot
				// Get vector from interp before knot to interp after knot
				Point3 direction;
				Point3 ko = inSpline->GetKnotPoint(i);
				float oldInLength = (i == 0) ? 1.0f : CurveLength(inSpline, i - 1, 0.5f, 1.0f);
				float oldOutLength = (i == (knots - 1)) ? 1.0f : CurveLength(inSpline, i, 0.0f, 0.5f);
				float wsize1;
				if(i == 0) {
					direction = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
					wsize1 = size1;
					}
				else
				if(i == (knots - 1)) {
					direction = Normalize(ko - inSpline->InterpBezier3D(i-1, 0.99f));
					wsize1=size1;
					}
				else {
					Point3 bVec = Normalize(inSpline->InterpBezier3D(i-1, 0.99f) - ko);
					Point3 fVec = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
					direction = Normalize(fVec - bVec);
					// Figure the size multiplier for the crotch angle
					float dot = DotProd(bVec, fVec);
					if(dot >= -0.9999939f) {
						float angle = (float)-acos(dot) / 2.0f;
						float base1 = size1 / (float)tan(angle);
						float sign1 = (size1 < 0.0f) ? -1.0f : 1.0f;
						wsize1 = (float)sqrt(base1 * base1 + size1 * size1) * sign1;
						}
					}
				direction.z = 0.0f;	// Keep it in the XY plane
				Point3 perp(direction.y * wsize1, -direction.x * wsize1, 0.0f);
				float newInLength = (i == 0) ? 1.0f : CurveLength(inSpline, i - 1, 0.5f, 1.0f, size1);
				float newOutLength = (i == (knots - 1)) ? 1.0f : CurveLength(inSpline, i, 0.0f, 0.5f, size1);
				float inMult = newInLength / oldInLength;
				float outMult = newOutLength / oldOutLength;
				int knotType = inSpline->GetKnotType(i);
				Point3 kn = ko + perp;
				SplineKnot k((i==0 || i==(knots-1)) ? KTYPE_BEZIER_CORNER : knotType, LTYPE_CURVE,
					kn, kn + (inSpline->GetInVec(i) - ko) * inMult, kn + (inSpline->GetOutVec(i) - ko) * outMult);
				outSpline.AddKnot(k);
				}
			for(i = knots - 1; i >= 0; --i) {
				// Determine the angle of the curve at this knot
				// Get vector from interp before knot to interp after knot
				Point3 direction;
				Point3 ko = inSpline->GetKnotPoint(i);
				float oldInLength = (i == 0) ? 1.0f : CurveLength(inSpline, i - 1, 0.5f, 1.0f);
				float oldOutLength = (i == (knots - 1)) ? 1.0f : CurveLength(inSpline, i, 0.0f, 0.5f);
				float wsize2;
				if(i == 0) {
					direction = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
					wsize2 = size2;
					}
				else
				if(i == (knots - 1)) {
					direction = Normalize(ko - inSpline->InterpBezier3D(i-1, 0.99f));
					wsize2 = size2;
					}
				else {
					Point3 bVec = Normalize(inSpline->InterpBezier3D(i-1, 0.99f) - ko);
					Point3 fVec = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
					direction = Normalize(fVec - bVec);
					// Figure the size multiplier for the crotch angle
					float dot = DotProd(bVec, fVec);
					if(dot >= -0.9999939f) {
						float angle = (float)-acos(dot) / 2.0f;
						float base2 = size2 / (float)tan(angle);
						float sign2 = (size2 < 0.0f) ? -1.0f : 1.0f;
						wsize2 = (float)sqrt(base2 * base2 + size2 * size2) * sign2;
						}
					}
				direction.z = 0.0f;	// Keep it in the XY plane
				Point3 perp(direction.y * wsize2, -direction.x * wsize2, 0.0f);
				float newInLength = (i == 0) ? 1.0f : CurveLength(inSpline, i - 1, 0.5f, 1.0f, size2);
				float newOutLength = (i == (knots - 1)) ? 1.0f : CurveLength(inSpline, i, 0.0f, 0.5f, size2);
				float inMult = newInLength / oldInLength;
				float outMult = newOutLength / oldOutLength;
				int knotType = inSpline->GetKnotType(i);
				Point3 kn = ko + perp;
				SplineKnot k((i==0 || i==(knots-1)) ? KTYPE_BEZIER_CORNER : knotType, LTYPE_CURVE,
					kn, kn + (inSpline->GetOutVec(i) - ko) * outMult, kn + (inSpline->GetInVec(i) - ko) * inMult);
				outSpline.AddKnot(k);
				}
			int lastPt = outSpline.KnotCount() - 1;
			outSpline.SetInVec(0, outSpline.GetKnotPoint(0));
			outSpline.SetOutVec(lastPt, outSpline.GetKnotPoint(lastPt));
			outSpline.SetInVec(knots, outSpline.GetKnotPoint(knots));
			outSpline.SetOutVec(knots - 1, outSpline.GetKnotPoint(knots - 1));
			outSpline.SetClosed();
			outSpline.ComputeBezPoints();
			*inSpline = outSpline;
			// Adjust selection data for this spline
			shape->vertSel[poly].SetSize(outSpline.Verts(),1);
			shape->segSel[poly].SetSize(outSpline.Segments(),1);
			}
		}
	else {
		Spline3D *inSpline = shape->splines[poly];
		Spline3D outSpline;
		// Do some basic calculations that we'll need regardless
		float size1 = (centered) ? size / 2.0f : 0.0f;	// First phase offset
		float size2 = (centered) ? -size / 2.0f : -size;	// Second phase offset
		int knots = inSpline->KnotCount();
		Point3 knot, in, out;
		int i;
		Matrix3 theMatrix;

		// If the input spline is closed, we wind up with two polygons
		if(inSpline->Closed()) {
			Spline3D *outSpline2 = shape->NewSpline();
			// Generate the outline polygons...
			for(i = 0; i < knots; ++i) {
				int prevKnot = (i + knots - 1) % knots;
				float oldInLength = CurveLength(inSpline, prevKnot, 0.5f, 1.0f);
				float oldOutLength = CurveLength(inSpline, i, 0.0f, 0.5f);
				int knotType = inSpline->GetKnotType(i);
				// Determine the angle of the curve at this knot
				// Get vector from interp before knot to interp after knot
				Point3 ko = inSpline->GetKnotPoint(i);
				Point3 bVec = Normalize(inSpline->InterpBezier3D(prevKnot, 0.99f) - ko);
				Point3 fVec = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
				Point3 direction = Normalize(fVec - bVec);
				direction.z = 0.0f;	// Keep it in the XY plane
				Point3 perp;
				perp = Point3(direction.y * size1, -direction.x * size1, 0.0f);
				float newInLength = CurveLength(inSpline, prevKnot, 0.5f, 1.0f, size1);
				float newOutLength = CurveLength(inSpline, i, 0.0f, 0.5f, size1);
				Point3 kn = ko + perp;
				float inMult = newInLength / oldInLength;
				float outMult = newOutLength / oldOutLength;
				SplineKnot k(knotType, LTYPE_CURVE,
					kn, kn + (inSpline->GetInVec(i) - ko) * inMult, kn + (inSpline->GetOutVec(i) - ko) * outMult);
				outSpline.AddKnot(k);
				perp = Point3(direction.y * size2, -direction.x * size2, 0.0f);
				newInLength = CurveLength(inSpline, prevKnot, 0.5f, 1.0f, size2);
				newOutLength = CurveLength(inSpline, i, 0.0f, 0.5f, size2);
				kn = ko + perp;
				inMult = newInLength / oldInLength;
				outMult = newOutLength / oldOutLength;
				k = SplineKnot(knotType, LTYPE_CURVE,
					kn, kn + (inSpline->GetInVec(i) - ko) * inMult, kn + (inSpline->GetOutVec(i) - ko) * outMult);
				outSpline2->AddKnot(k);
				}
			outSpline.SetClosed();
			outSpline.ComputeBezPoints();
			*inSpline = outSpline;
			outSpline2->SetClosed();
			outSpline2->ComputeBezPoints();
			shape->vertSel.Insert(shape->splineCount - 1, knots * 3);
			shape->segSel.Insert(shape->splineCount - 1, knots);
			shape->polySel.Insert(shape->splineCount - 1);
			}
		else {	// Otherwise, we get one closed polygon
			// Now get the spline selection sets
			BitArray& vsel = shape->vertSel[poly];
			BitArray& ssel = shape->segSel[poly];
			
			// Generate the outline polygon...
			for(i = 0; i < knots; ++i) {
				// Determine the angle of the curve at this knot
				// Get vector from interp before knot to interp after knot
				Point3 direction;
				Point3 ko = inSpline->GetKnotPoint(i);
				float oldInLength = (i == 0) ? 1.0f : CurveLength(inSpline, i - 1, 0.5f, 1.0f);
				float oldOutLength = (i == (knots - 1)) ? 1.0f : CurveLength(inSpline, i, 0.0f, 0.5f);
				if(i == 0)
					direction = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
				else
				if(i == (knots - 1))
					direction = Normalize(ko - inSpline->InterpBezier3D(i-1, 0.99f));
				else {
					Point3 bVec = Normalize(inSpline->InterpBezier3D(i-1, 0.99f) - ko);
					Point3 fVec = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
					direction = Normalize(fVec - bVec);
					}
				direction.z = 0.0f;	// Keep it in the XY plane
				Point3 perp(direction.y * size1, -direction.x * size1, 0.0f);
				float newInLength = (i == 0) ? 1.0f : CurveLength(inSpline, i - 1, 0.5f, 1.0f, size1);
				float newOutLength = (i == (knots - 1)) ? 1.0f : CurveLength(inSpline, i, 0.0f, 0.5f, size1);
				float inMult = newInLength / oldInLength;
				float outMult = newOutLength / oldOutLength;
				int knotType = inSpline->GetKnotType(i);
				Point3 kn = ko + perp;
				SplineKnot k((i==0 || i==(knots-1)) ? KTYPE_BEZIER_CORNER : knotType, LTYPE_CURVE,
					kn, kn + (inSpline->GetInVec(i) - ko) * inMult, kn + (inSpline->GetOutVec(i) - ko) * outMult);
				outSpline.AddKnot(k);
				}
			for(i = knots - 1; i >= 0; --i) {
				// Determine the angle of the curve at this knot
				// Get vector from interp before knot to interp after knot
				Point3 direction;
				Point3 ko = inSpline->GetKnotPoint(i);
				float oldInLength = (i == 0) ? 1.0f : CurveLength(inSpline, i - 1, 0.5f, 1.0f);
				float oldOutLength = (i == (knots - 1)) ? 1.0f : CurveLength(inSpline, i, 0.0f, 0.5f);
				if(i == 0)
					direction = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
				else
				if(i == (knots - 1))
					direction = Normalize(ko - inSpline->InterpBezier3D(i-1, 0.99f));
				else {
					Point3 bVec = Normalize(inSpline->InterpBezier3D(i-1, 0.99f) - ko);
					Point3 fVec = Normalize(inSpline->InterpBezier3D(i, 0.01f) - ko);
					direction = Normalize(fVec - bVec);
					}
				direction.z = 0.0f;	// Keep it in the XY plane
				Point3 perp(direction.y * size2, -direction.x * size2, 0.0f);
				float newInLength = (i == 0) ? 1.0f : CurveLength(inSpline, i - 1, 0.5f, 1.0f, size2);
				float newOutLength = (i == (knots - 1)) ? 1.0f : CurveLength(inSpline, i, 0.0f, 0.5f, size2);
				float inMult = newInLength / oldInLength;
				float outMult = newOutLength / oldOutLength;
				int knotType = inSpline->GetKnotType(i);
				Point3 kn = ko + perp;
				SplineKnot k((i==0 || i==(knots-1)) ? KTYPE_BEZIER_CORNER : knotType, LTYPE_CURVE,
					kn, kn + (inSpline->GetOutVec(i) - ko) * outMult, kn + (inSpline->GetInVec(i) - ko) * inMult);
				outSpline.AddKnot(k);
				}
			int lastPt = outSpline.KnotCount() - 1;
			outSpline.SetInVec(0, outSpline.GetKnotPoint(0));
			outSpline.SetOutVec(lastPt, outSpline.GetKnotPoint(lastPt));
			outSpline.SetInVec(knots, outSpline.GetKnotPoint(knots));
			outSpline.SetOutVec(knots - 1, outSpline.GetKnotPoint(knots - 1));
			outSpline.SetClosed();
			outSpline.ComputeBezPoints();
			*inSpline = outSpline;
			// Adjust selection data for this spline
			vsel.SetSize(inSpline->Verts(),1);
			ssel.SetSize(inSpline->Segments(),1);
			}
		}
	shape->InvalidateGeomCache();
	}

BOOL OutlineRecord::Redo(BezierShape *shape, int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		oldSplineCount = shape->splineCount;
		}
	OutlineSpline(shape, poly, size, centered, newType);
	return TRUE;
	}

#define POR_GENERAL_CHUNK		0x1000
#define POR_OLDVSEL_CHUNK		0x1040
#define POR_OLDSSEL_CHUNK		0x1050
#define POR_SPLINE_CHUNK		0x1060
#define POR_NEWTYPE_CHUNK		0x1070

IOResult OutlineRecord::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(POR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&centered,sizeof(int),&nb);
	isave->Write(&size,sizeof(float),&nb);
	isave->Write(&oldSplineCount,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(POR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(POR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(POR_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	if(newType) {
		isave->BeginChunk(POR_NEWTYPE_CHUNK);	// Only in MAX 2.0 and up
		isave->	EndChunk();
		}
	return IO_OK;
	}

IOResult OutlineRecord::Load(ILoad *iload) {
	IOResult res;
	ULONG nb;
	newType = 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 POR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&centered,sizeof(int),&nb);
				res = iload->Read(&size,sizeof(float),&nb);
				res = iload->Read(&oldSplineCount,sizeof(int),&nb);
				break;
			case POR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case POR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case POR_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			// If the following chunk is present, it's a MAX 2.0 file and the outlining 
			// is done using a fixed algorithm.  Otherwise, it uses the R1.x code, preserving
			// the shape as it was.
			case POR_NEWTYPE_CHUNK:
				newType = TRUE;
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

PolyDetachRecord::PolyDetachRecord(BezierShape *shape, int poly, int copy) : ModRecord() {
	this->poly = poly;
	this->copy = copy;
	if(!copy) {
		spline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		}
	}

BOOL PolyDetachRecord::Undo(BezierShape *shape) {
	if(!copy) {
		if(poly > shape->splineCount)
			return FALSE;
		shape->InsertSpline(&spline,poly);
		if(!IsCompatible(shape, poly, &oldVSel, &oldSSel))
			return FALSE;
		shape->vertSel[poly] = oldVSel;
		shape->segSel[poly] = oldSSel;
		shape->polySel.Set(poly,1);
		}
	return TRUE;
	}

BOOL PolyDetachRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord && !copy) {
		spline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		}
	if(!copy)
		shape->DeleteSpline(poly);
	return TRUE;
	}

#define PDETR_GENERAL_CHUNK		0x1000
#define PDETR_OLDVSEL_CHUNK		0x1040
#define PDETR_OLDSSEL_CHUNK		0x1050
#define PDETR_SPLINE_CHUNK		0x1060

IOResult PolyDetachRecord::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(&poly,sizeof(int),&nb);
	isave->Write(&copy,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PDETR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PDETR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PDETR_SPLINE_CHUNK);
	spline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PolyDetachRecord::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(&poly,sizeof(int),&nb);
				res = iload->Read(&copy,sizeof(int),&nb);
				break;
			case PDETR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case PDETR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case PDETR_SPLINE_CHUNK:
				res = spline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

PolyDeleteRecord::PolyDeleteRecord(BezierShape *shape, int poly) : ModRecord() {
	this->poly = poly;
	spline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	}

BOOL PolyDeleteRecord::Undo(BezierShape *shape) {
	if(poly > shape->splineCount)
		return FALSE;
	shape->InsertSpline(&spline,poly);
	if(!IsCompatible(shape, poly, &oldVSel, &oldSSel))
		return FALSE;
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	shape->polySel.Set(poly);
	return TRUE;
	}

BOOL PolyDeleteRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		spline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		}
	shape->DeleteSpline(poly);
	return TRUE;
	}

#define PDELR_GENERAL_CHUNK		0x1000
#define PDELR_OLDVSEL_CHUNK		0x1040
#define PDELR_OLDSSEL_CHUNK		0x1050
#define PDELR_SPLINE_CHUNK		0x1060

IOResult PolyDeleteRecord::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_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PDELR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PDELR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PDELR_SPLINE_CHUNK);
	spline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PolyDeleteRecord::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_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				break;
			case PDELR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case PDELR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case PDELR_SPLINE_CHUNK:
				res = spline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

BOOL VertMoveRecord::Undo(BezierShape *shape) {
	if(!delta.IsCompatible(*shape))
		return FALSE;
	delta.UnApply(*shape);
	return TRUE;
	}

BOOL VertMoveRecord::Redo(BezierShape *shape,int reRecord) {
	if(!delta.IsCompatible(*shape))
		return FALSE;
	delta.Apply(*shape);
	return TRUE;
	}

#define VMR_DELTA_CHUNK		0x1000

IOResult VertMoveRecord::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 VertMoveRecord::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;
	}

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

SegDeleteRecord::SegDeleteRecord(BezierShape *shape, int poly) : ModRecord() {
	this->poly = poly;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	selected = shape->polySel[poly];
	deleted = FALSE;
	oldSplineCount = shape->splineCount;
	}

BOOL SegDeleteRecord::Undo(BezierShape *shape) {
	// Get rid of all the new polys we created...
	while(shape->splineCount > oldSplineCount)
		shape->DeleteSpline(shape->splineCount - 1);
	// Get rid of the one we changed (if it's not deleted already!)
	if(!deleted) {
		if(poly >= shape->splineCount)
			return FALSE;
		shape->DeleteSpline(poly);
		}
	// Put back the original poly we changed...
	if(poly > shape->splineCount)
		return FALSE;
	shape->InsertSpline(&oldSpline,poly);
	// Restore the selection information
	if(!IsCompatible(shape, poly, &oldVSel, &oldSSel))
		return FALSE;
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	shape->polySel.Set(poly,selected);
	return TRUE;
	}

// Delete all selected segments
// Returns TRUE if the operation results in the polygon being deleted
int DeleteSelSegs(BezierShape *shape, int poly) {
	Spline3D *spline = shape->splines[poly];
//	BitArray& vsel = shape->vertSel[poly];
//	BitArray& ssel = shape->segSel[poly];
	int segs = spline->Segments();
	int verts = spline->Verts();
	int altered = 0;
	for(int s = segs-1; s >= 0; --s) {
		BitArray& vsel = shape->vertSel[poly];
		BitArray& ssel = shape->segSel[poly];
		if(ssel[s]) {
			altered = 1;
			if(spline->Closed()) {
				if(s == (spline->KnotCount()-1)) {	// deletion at end segment -- Just open the spline!
					delete_at_last_segment:
					altered = 1;
					spline->SetOpen();
					ssel.SetSize(spline->Segments(),1);
					}
				else
				if(s == 0) {			// Delete at first segment
					SplineKnot dupKnot(spline->GetKnotType(0),spline->GetLineType(0),
						spline->GetKnotPoint(0),spline->GetInVec(0),spline->GetOutVec(0));
					spline->AddKnot(dupKnot, -1);
					spline->DeleteKnot(0);
					spline->SetOpen();
					vsel.Rotate(LEFT_BITSHIFT, 3);
					ssel.Rotate(LEFT_BITSHIFT, 1);
					ssel.SetSize(spline->Segments(),1);
					}
				else {					// Deletion somewhere in the middle
					// First, rotate the spline so that the deletion point is the last one!
					int rotations = 0;
					int lastSeg = spline->KnotCount() - 1;
					while(s < (lastSeg)) {
						SplineKnot dupKnot(spline->GetKnotType(lastSeg),spline->GetLineType(lastSeg),
							spline->GetKnotPoint(lastSeg),spline->GetInVec(lastSeg),spline->GetOutVec(lastSeg));
						spline->DeleteKnot(lastSeg);
						spline->AddKnot(dupKnot, 0);
						rotations++;
						s++;
						}
					vsel.Rotate(RIGHT_BITSHIFT, rotations*3);
					ssel.Rotate(RIGHT_BITSHIFT, rotations);
					s = lastSeg;
					goto delete_at_last_segment;
					}
				}
			else {
				// If it's the only segment, blow the polygon away!
				if(spline->Segments() == 1) {
					shape->DeleteSpline(poly);
					return TRUE;				// It's TRUE -- We deleted the spline!
					}
				if(s==0) {
					spline->DeleteKnot(0);
					vsel.Shift(LEFT_BITSHIFT, 3);
					vsel.SetSize(spline->Verts(),1);
					ssel.Shift(LEFT_BITSHIFT, 1);
					ssel.SetSize(spline->Segments(),1);
					}
				else
				if(s == (spline->KnotCount()-2)) {
					spline->DeleteKnot(s+1);
					vsel.SetSize(spline->Verts(),1);
					ssel.SetSize(spline->Segments(),1);
					}
				else {
					int i;
					int newPolySize = spline->KnotCount() - s - 1;
					// OK, We're deleting at a point in the middle -- Copy end points off to a new spline, then
					// delete them from the original!
					Spline3D *newSpline = shape->NewSpline();
					int knots = spline->KnotCount();
					for(i = s + 1; i < knots; ++i) {
						SplineKnot dupKnot(spline->GetKnotType(i),spline->GetLineType(i),
							spline->GetKnotPoint(i),spline->GetInVec(i),spline->GetOutVec(i));
						newSpline->AddKnot(dupKnot, -1);
						}
					for(i = knots-1; i > s; --i)
						spline->DeleteKnot(i);
					// Adjust selection data for this spline
					vsel.SetSize(spline->Verts(),1);
					ssel.SetSize(spline->Segments(),1);
					// Don't forget to create a new selection record for this new spline!
					shape->vertSel.Insert(shape->splineCount - 1, newPolySize * 3);
					shape->segSel.Insert(shape->splineCount - 1, newPolySize - 1);
					shape->polySel.Insert(shape->splineCount - 1);
					// WATCH OUT! Need to retrieve these references because they can move on an insert!
	//				vsel = (BitArray &)shape->vertSel[poly];
	//				ssel = (BitArray &)shape->segSel[poly];
					}
				}
			}
		}
	if(altered)	{
		spline->ComputeBezPoints();
		shape->InvalidateGeomCache();
		}
	return FALSE;	// The poly's still there
	}

BOOL SegDeleteRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		selected = shape->polySel[poly];
		}
	DeleteSelSegs(shape, poly);
	return TRUE;
	}

#define SDELR_GENERAL_CHUNK		0x1000
#define SDELR_OLDVSEL_CHUNK		0x1040
#define SDELR_OLDSSEL_CHUNK		0x1050
#define SDELR_SPLINE_CHUNK		0x1060

IOResult SegDeleteRecord::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(SDELR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&selected,sizeof(int),&nb);
	isave->Write(&deleted,sizeof(int),&nb);
	isave->Write(&oldSplineCount,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(SDELR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SDELR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SDELR_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SegDeleteRecord::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 SDELR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&selected,sizeof(int),&nb);
				res = iload->Read(&deleted,sizeof(int),&nb);
				res = iload->Read(&oldSplineCount,sizeof(int),&nb);
				break;
			case SDELR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case SDELR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case SDELR_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

SegDetachRecord::SegDetachRecord(BezierShape *shape, int poly, int copy) : ModRecord() {
	this->poly = poly;
	this->copy = copy;
	if(!copy) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		selected = shape->polySel[poly];
		deleted = FALSE;
		oldSplineCount = shape->splineCount;
		}
	}

BOOL SegDetachRecord::Undo(BezierShape *shape) {
	if(!copy) {
		// Get rid of all the new polys we created...
		while(shape->splineCount > oldSplineCount)
			shape->DeleteSpline(shape->splineCount - 1);
		// Get rid of the one we changed (if it's not deleted already!)
		if(!deleted) {
			if(poly >= shape->splineCount)
				return FALSE;
			shape->DeleteSpline(poly);
			}
		// Put back the original poly we changed...
		if(poly > shape->splineCount)
			return FALSE;
		shape->InsertSpline(&oldSpline,poly);
		// Restore the selection information
		if(!IsCompatible(shape, poly, &oldVSel, &oldSSel))
			return FALSE;
		shape->vertSel[poly] = oldVSel;
		shape->segSel[poly] = oldSSel;
		shape->polySel.Set(poly,selected);
		}
	return TRUE;
	}

BOOL SegDetachRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord && !copy) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		selected = shape->polySel[poly];
		}
	if(!copy)
		DeleteSelSegs(shape, poly);
	return TRUE;
	}

#define SDETR_GENERAL_CHUNK		0x1000
#define SDETR_OLDVSEL_CHUNK		0x1040
#define SDETR_OLDSSEL_CHUNK		0x1050
#define SDETR_SPLINE_CHUNK		0x1060

IOResult SegDetachRecord::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(SDETR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&copy,sizeof(int),&nb);
	isave->Write(&selected,sizeof(int),&nb);
	isave->Write(&deleted,sizeof(int),&nb);
	isave->Write(&oldSplineCount,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(SDETR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SDETR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SDETR_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SegDetachRecord::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 SDETR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&copy,sizeof(int),&nb);
				res = iload->Read(&selected,sizeof(int),&nb);
				res = iload->Read(&deleted,sizeof(int),&nb);
				res = iload->Read(&oldSplineCount,sizeof(int),&nb);
				break;
			case SDETR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case SDETR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case SDETR_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

SegBreakRecord::SegBreakRecord(BezierShape *shape, int poly, int segment, float param) : ModRecord() {
	this->poly = poly;
	this->segment = segment;
	this->param = param;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	selected = shape->polySel[poly];
	oldSplineCount = shape->splineCount;
	}

BOOL SegBreakRecord::Undo(BezierShape *shape) {
	// Get rid of all the new polys we created...
	while(shape->splineCount > oldSplineCount)
		shape->DeleteSpline(shape->splineCount - 1);
	// Get rid of the one we changed
	if(poly >= shape->splineCount)
		return FALSE;
	shape->DeleteSpline(poly);
	// Put back the original poly we changed...
	if(poly > shape->splineCount)
		return FALSE;
	shape->InsertSpline(&oldSpline,poly);
	// Restore the selection information
	if(!IsCompatible(shape, poly, &oldVSel, &oldSSel))
		return FALSE;
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	shape->polySel.Set(poly,selected);
	return TRUE;
	}

static void BreakSegment(BezierShape *shape, int poly, int segment, float param) {
	Spline3D *spline = shape->splines[poly];
	int knots = spline->KnotCount();
	int verts = spline->Verts();
	int segs = spline->Segments();
	int nextSeg = (segment + 1) % knots;

	Point3 point = spline->InterpBezier3D(segment, param);
	
	Point3 v00 = spline->GetKnotPoint(segment);
	Point3 v30 = spline->GetKnotPoint(nextSeg);
	Point3 v10 = spline->GetOutVec(segment);
	Point3 v20 = spline->GetInVec(nextSeg);
	Point3 v01 = v00 + (v10 - v00) * param;
	Point3 v21 = v20 + (v30 - v20) * param;
	Point3 v11 = v10 + (v20 - v10) * param;
	Point3 v02 = v01 + (v11 - v01) * param;
	Point3 v12 = v11 + (v21 - v11) * param;
	Point3 v03 = v02 + (v12 - v02) * param;

	spline->SetOutVec(segment, v01);
	spline->SetInVec(nextSeg, v21);

	SplineKnot newKnot(KTYPE_BEZIER, LTYPE_CURVE, v03, v02, v12);
	spline->AddKnot(newKnot, nextSeg);
	spline->ComputeBezPoints();

	// Now adjust the spline selection sets
	BitArray& vsel = shape->vertSel[poly];
	BitArray& ssel = shape->segSel[poly];
	vsel.SetSize(verts + 3, 1);
	int where = (segment + 1) * 3;
	vsel.Shift(RIGHT_BITSHIFT, 3, where);
	vsel.Clear(where);
	vsel.Clear(where+1);
	vsel.Clear(where+2);
	ssel.SetSize(segs + 1, 1);
	ssel.Shift(RIGHT_BITSHIFT, 1, segment + 1);
	ssel.Set(segment+1,ssel[segment]);

	// Now break the spline at that vertex!

	knots = spline->KnotCount();
	verts = spline->Verts();

	int k = nextSeg;
	int altered = 0;

	if(1) {
		BitArray& vsel = shape->vertSel[poly];
		BitArray& ssel = shape->segSel[poly];
		int vert = k*3+1;
		if(spline->Closed()) {
			if(k == (knots-1)) {	// Break at end knot
				break_at_last_knot:
				altered = 1;
				SplineKnot dupKnot(spline->GetKnotType(k),spline->GetLineType(k),
					spline->GetKnotPoint(k),spline->GetInVec(k),spline->GetOutVec(k));
				spline->AddKnot(dupKnot, 0);
				knots++;
				verts += 3;
				spline->SetOpen();
				vsel.SetSize(spline->Verts(),1);
				vsel.Shift(RIGHT_BITSHIFT,3);
				vsel.Clear(0);	// New point not selected
				vsel.Clear(1);
				vsel.Clear(2);
				ssel.SetSize(spline->Segments(),1);
				ssel.Shift(RIGHT_BITSHIFT,1);
				ssel.Clear(0);
				}
			else
			if(k == 0) {			// Break at first knot
				altered = 1;
				SplineKnot dupKnot(spline->GetKnotType(0),spline->GetLineType(0),
					spline->GetKnotPoint(0),spline->GetInVec(0),spline->GetOutVec(0));
				spline->AddKnot(dupKnot, -1);
				knots++;
				verts += 3;
				spline->SetOpen();
				vsel.SetSize(spline->Verts(),1);
				vsel.Clear(verts-3);		// New point not selected
				vsel.Clear(verts-2);
				vsel.Clear(verts-1);
				ssel.SetSize(spline->Segments(),1);
				ssel.Clear(knots-1);
				}
			else {					// Break somewhere in the middle
				// First, rotate the spline so that the break point is the last one!
				int rotations = 0;
				int lastKnot = knots - 1;
				while(k < (lastKnot)) {
					SplineKnot dupKnot(spline->GetKnotType(lastKnot),spline->GetLineType(lastKnot),
						spline->GetKnotPoint(lastKnot),spline->GetInVec(lastKnot),spline->GetOutVec(lastKnot));
					spline->DeleteKnot(lastKnot);
					spline->AddKnot(dupKnot, 0);
					rotations++;
					k++;
					}
				vsel.Rotate(RIGHT_BITSHIFT, rotations*3);
				ssel.Rotate(RIGHT_BITSHIFT, rotations);
				k = lastKnot;
				vert = k*3+1;
				goto break_at_last_knot;
				}
			}
		else {
			// Don't do anything if the spline's open and we're breaking at an end vertex!
			if (k == 0 || k == (knots-1)) {
				vsel.Clear(vert);	// Just turn off the selection bit
				goto done;
				}
			int i;
			int newPolySize = knots - k;
			// OK, We're breaking at a point in the middle -- Copy end points off to a new spline, then
			// delete them from the original!
			Spline3D *newSpline = shape->NewSpline();
			for(i = k; i < knots; ++i) {
				SplineKnot dupKnot(spline->GetKnotType(i),spline->GetLineType(i),
					spline->GetKnotPoint(i),spline->GetInVec(i),spline->GetOutVec(i));
				newSpline->AddKnot(dupKnot, -1);
				}
			for(i = knots-1; i > k; --i)
				spline->DeleteKnot(i);
			// Adjust selection data for this spline
			vsel.SetSize(spline->Verts(),1);
			ssel.SetSize(spline->Segments(),1);
			// Don't forget to create a new selection record for this new spline!
			shape->vertSel.Insert(shape->splineCount - 1, newPolySize * 3);
			shape->segSel.Insert(shape->splineCount - 1, newPolySize - 1);
			shape->polySel.Insert(shape->splineCount - 1);
			}
		}
	done:
	if(altered) {
		spline->ComputeBezPoints();
		shape->InvalidateGeomCache();
		}
	}

BOOL SegBreakRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		selected = shape->polySel[poly];
		oldSplineCount = shape->splineCount;
		}
	BreakSegment(shape, poly, segment, param);
	return TRUE;
	}

#define SBRKR_GENERAL_CHUNK		0x1000
#define SBRKR_OLDVSEL_CHUNK		0x1040
#define SBRKR_OLDSSEL_CHUNK		0x1050
#define SBRKR_SPLINE_CHUNK		0x1060

IOResult SegBreakRecord::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(SBRKR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&segment,sizeof(int),&nb);
	isave->Write(&param,sizeof(float),&nb);
	isave->Write(&selected,sizeof(int),&nb);
	isave->Write(&oldSplineCount,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(SBRKR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SBRKR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SBRKR_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SegBreakRecord::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 SBRKR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&segment,sizeof(int),&nb);
				res = iload->Read(&param,sizeof(float),&nb);
				res = iload->Read(&selected,sizeof(int),&nb);
				res = iload->Read(&oldSplineCount,sizeof(int),&nb);
				break;
			case SBRKR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case SBRKR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case SBRKR_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

SegRefineRecord::SegRefineRecord(BezierShape *shape, int poly, int segment, float param) : ModRecord() {
	this->poly = poly;
	this->segment = segment;
	this->param = param;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	}

BOOL SegRefineRecord::Undo(BezierShape *shape) {
	// Get rid of the one we changed
	if(poly >= shape->splineCount)
		return FALSE;
	shape->DeleteSpline(poly);
	// Put back the original poly we changed...
	shape->InsertSpline(&oldSpline,poly);
	// Restore the selection information
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	return TRUE;
	}

static void RefineSegment(BezierShape *shape, int poly, int segment, float param) {
	Spline3D *spline = shape->splines[poly];
	int knots = spline->KnotCount();
	int verts = spline->Verts();
	int segs = spline->Segments();
	int nextSeg = (segment + 1) % knots;
	int insertSeg = (nextSeg == 0) ? -1 : nextSeg;

	// Get the knot points
	Point3 v00 = spline->GetKnotPoint(segment);
	Point3 v30 = spline->GetKnotPoint(nextSeg);

	// Special: If they're refining a line-type segment, force it to be a bezier curve again
	if(spline->GetLineType(segment) == LTYPE_LINE) {
		spline->SetKnotType(segment, KTYPE_BEZIER_CORNER);
		spline->SetKnotType(nextSeg, KTYPE_BEZIER_CORNER);
		spline->SetLineType(segment, LTYPE_CURVE);
		spline->SetOutVec(segment, v00 + (v30 - v00) / 3.0f);
		spline->SetInVec(nextSeg, v30 - (v30 - v00) / 3.0f);
		}

	Point3 point = spline->InterpBezier3D(segment, param);
	
	Point3 v10 = spline->GetOutVec(segment);
	Point3 v20 = spline->GetInVec(nextSeg);
	Point3 v01 = v00 + (v10 - v00) * param;
	Point3 v21 = v20 + (v30 - v20) * param;
	Point3 v11 = v10 + (v20 - v10) * param;
	Point3 v02 = v01 + (v11 - v01) * param;
	Point3 v12 = v11 + (v21 - v11) * param;
	Point3 v03 = v02 + (v12 - v02) * param;

	spline->SetOutVec(segment, v01);
	spline->SetInVec(nextSeg, v21);

	SplineKnot newKnot(KTYPE_BEZIER, LTYPE_CURVE, v03, v02, v12);
	spline->AddKnot(newKnot, insertSeg);
	spline->ComputeBezPoints();
	shape->InvalidateGeomCache();

	// Now adjust the spline selection sets
	BitArray& vsel = shape->vertSel[poly];
	BitArray& ssel = shape->segSel[poly];
	vsel.SetSize(spline->Verts(), 1);
	int where = (segment + 1) * 3;
	vsel.Shift(RIGHT_BITSHIFT, 3, where);
	vsel.Clear(where);
	vsel.Clear(where+1);
	vsel.Clear(where+2);
	ssel.SetSize(spline->Segments(), 1);
	ssel.Shift(RIGHT_BITSHIFT, 1, segment + 1);
	ssel.Set(segment+1,ssel[segment]);
	}

BOOL SegRefineRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		}
	if(segment >= shape->splines[poly]->Segments())
		return FALSE;
	RefineSegment(shape, poly, segment, param);
	return TRUE;
	}

#define SREFR_GENERAL_CHUNK		0x1000
#define SREFR_OLDVSEL_CHUNK		0x1040
#define SREFR_OLDSSEL_CHUNK		0x1050
#define SREFR_SPLINE_CHUNK		0x1060

IOResult SegRefineRecord::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(SREFR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&segment,sizeof(int),&nb);
	isave->Write(&param,sizeof(float),&nb);
	isave->	EndChunk();
	isave->BeginChunk(SREFR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SREFR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(SREFR_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SegRefineRecord::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 SREFR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&segment,sizeof(int),&nb);
				res = iload->Read(&param,sizeof(float),&nb);
				break;
			case SREFR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case SREFR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case SREFR_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

VertConnectRecord::VertConnectRecord(BezierShape *shape, int poly1, int vert1, int poly2, int vert2) : ModRecord() {
	this->poly1 = poly1;
	this->vert1 = vert1;
	this->poly2 = poly2;
	this->vert2 = vert2;
	oldSpline1 = *(shape->splines[poly1]);
	oldVSel1 = shape->vertSel[poly1];
	oldSSel1 = shape->segSel[poly1];
	if(poly1 != poly2) {
		oldSpline2 = *(shape->splines[poly2]);
		oldVSel2 = shape->vertSel[poly2];
		oldSSel2 = shape->segSel[poly2];
		selected2 = shape->polySel[poly2];
		}
	}

BOOL VertConnectRecord::Undo(BezierShape *shape) {
	if(poly1 != poly2) {
		if(poly2 > shape->splineCount)
			return FALSE;
		shape->InsertSpline(&oldSpline2, poly2);
		shape->vertSel[poly2] = oldVSel2;
		shape->segSel[poly2] = oldSSel2;
		shape->polySel.Set(poly2,selected2);
		}
	if(poly1 >= shape->splineCount)
		return FALSE;
	shape->DeleteSpline(poly1);
	shape->InsertSpline(&oldSpline1, poly1);
	shape->vertSel[poly1] = oldVSel1;
	shape->segSel[poly1] = oldSSel1;
	return TRUE;
	}

#define BEGINNING 0
#define END 1
#define FORWARD 1
#define REVERSE -1

static void ConnectVerts(BezierShape *shape, int poly1, int vert1, int poly2, int vert2) {
	Spline3D *spline = shape->splines[poly1];
	int knots = spline->KnotCount();
	int verts = spline->Verts();
	int segs = spline->Segments();

	// If connecting endpoints of the same polygon, close it and make the connecting line linear
	if(poly1 == poly2) {
		spline->SetClosed();
		// Make first and last knots beziers
		spline->SetKnotType(0, KTYPE_BEZIER_CORNER);
		int lastKnot = spline->KnotCount() - 1;
		spline->SetKnotType(lastKnot, KTYPE_BEZIER_CORNER);
		spline->SetLineType(lastKnot, LTYPE_CURVE);
		spline->SetInVec(0, spline->GetKnotPoint(0));
		spline->SetOutVec(lastKnot, spline->GetKnotPoint(lastKnot));
		BitArray& ssel = shape->segSel[poly1];
		ssel.SetSize(spline->Segments(), 1);
		ssel.Set(spline->Segments() - 1, 0);
		}
	else { 		// Connecting two different splines -- Splice 'em together!
		Spline3D *spline2 = shape->splines[poly2];
		int knots2 = spline2->KnotCount();
		int verts2 = spline2->Verts();
		int segs2 = spline2->Segments();
		// Enlarge the selection sets for the first spline
 		BitArray& vsel = shape->vertSel[poly1];
		BitArray& ssel = shape->segSel[poly1];
 		BitArray& vsel2 = shape->vertSel[poly2];
		BitArray& ssel2 = shape->segSel[poly2];

		// Ready the endpoints
		if(vert2 == 1) {
			spline2->SetKnotType(0, KTYPE_BEZIER_CORNER);
			spline2->SetInVec(0, spline2->GetKnotPoint(0));
			}
		else {
			spline2->SetKnotType(knots2 - 1, KTYPE_BEZIER_CORNER);
			spline2->SetOutVec(knots2 - 1, spline2->GetKnotPoint(knots2 - 1));
			}
		if(vert1 == 1) {
			spline->SetKnotType(0, KTYPE_BEZIER_CORNER);
			spline->SetInVec(0, spline->GetKnotPoint(0));
			}
		else {
			spline->SetKnotType(spline->KnotCount() - 1, KTYPE_BEZIER_CORNER);
			spline->SetOutVec(knots - 1, spline->GetKnotPoint(knots - 1));
			}
		// Now copy the knots over!
		if(vert2 == 1) {
			if(vert1 == 1) {
				// Forward copy, reversing vectors
				for(int i = 0, first = 1; i < spline2->KnotCount(); ++i, first = 0) {
					SplineKnot k(spline2->GetKnotType(i), (first) ? LTYPE_CURVE : spline2->GetLineType(i - 1),
						spline2->GetKnotPoint(i),spline2->GetOutVec(i), spline2->GetInVec(i));
					spline->AddKnot(k, 0);
					vsel.SetSize(spline->Verts(), 1);
					ssel.SetSize(spline->Segments(), 1);
					vsel.Shift(RIGHT_BITSHIFT, 3);
					ssel.Shift(RIGHT_BITSHIFT, 1);
					vsel.Set(1, vsel2[i*3+1]);
					ssel.Set(0, (first) ? 0 : ssel2[i - 1]);
					}
				}
			else {
				// Simple forward copy
				for(int i = 0, first = 1; i < spline2->KnotCount(); ++i, first = 0) {
					SplineKnot k(spline2->GetKnotType(i), spline2->GetLineType(i),
						spline2->GetKnotPoint(i),spline2->GetInVec(i), spline2->GetOutVec(i));
					spline->AddKnot(k);
					vsel.SetSize(spline->Verts(), 1);
					ssel.SetSize(spline->Segments(), 1);
					vsel.Set((spline->KnotCount() - 1) * 3 + 1, vsel2[i*3+1]);
					ssel.Set(spline->Segments() - 1, (first) ? 0 : ssel2[i - 1]);
					}
				}
			}
		else {
			if(vert1 == 1) {
				// Backward copy
				for(int i = spline2->KnotCount() - 1, first = 1; i >= 0; --i, first = 0) {
					SplineKnot k(spline2->GetKnotType(i), (first) ? LTYPE_CURVE : spline2->GetLineType(i),
						spline2->GetKnotPoint(i),spline2->GetInVec(i), spline2->GetOutVec(i));
					spline->AddKnot(k, 0);
					vsel.SetSize(spline->Verts(), 1);
					ssel.SetSize(spline->Segments(), 1);
					vsel.Shift(RIGHT_BITSHIFT, 3);
					ssel.Shift(RIGHT_BITSHIFT, 1);
					vsel.Set(1, vsel2[i*3+1]);
					ssel.Set(0, (first) ? 0 : ssel2[i]);
					}
				}
			else {
				// Backward copy, reversing vectors
				for(int i = spline2->KnotCount() - 1, first = 1; i >= 0; --i,first = 0) {
					SplineKnot k(spline2->GetKnotType(i), (i == 0) ? LTYPE_CURVE : spline2->GetLineType(i - 1),
						spline2->GetKnotPoint(i),spline2->GetOutVec(i), spline2->GetInVec(i));
					spline->AddKnot(k, -1);
					vsel.SetSize(spline->Verts(), 1);
					ssel.SetSize(spline->Segments(), 1);
					vsel.Set((spline->KnotCount() - 1) * 3 + 1, vsel2[i*3+1]);
					ssel.Set(spline->Segments() - 1, (first) ? 0 : ssel2[i]);
					}
				}
			}
		spline->ComputeBezPoints();
		shape->DeleteSpline(poly2);
		}
	}

BOOL VertConnectRecord::Redo(BezierShape *shape,int reRecord) {
	if(!IsCompatible(shape, poly1, &oldVSel1, &oldSSel1))
		return FALSE;
	if(poly1 != poly2 && !IsCompatible(shape, poly2, &oldVSel2, &oldSSel2))
		return FALSE;
	if(reRecord) {
		oldSpline1 = *(shape->splines[poly1]);
		oldVSel1 = shape->vertSel[poly1];
		oldSSel1 = shape->segSel[poly1];
		if(poly1 != poly2) {
			oldSpline2 = *(shape->splines[poly2]);
			oldVSel2 = shape->vertSel[poly2];
			oldSSel2 = shape->segSel[poly2];
			selected2 = shape->polySel[poly2];
			}
		}
	ConnectVerts(shape, poly1, vert1, poly2, vert2);
	return TRUE;
	}

#define VCONR_GENERAL_CHUNK		0x1000
#define VCONR_OLDVSEL1_CHUNK	0x1040
#define VCONR_OLDSSEL1_CHUNK	0x1050
#define VCONR_SPLINE1_CHUNK		0x1060
#define VCONR_OLDVSEL2_CHUNK	0x1070
#define VCONR_OLDSSEL2_CHUNK	0x1080
#define VCONR_SPLINE2_CHUNK		0x1090

IOResult VertConnectRecord::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(VCONR_GENERAL_CHUNK);
	isave->Write(&poly1,sizeof(int),&nb);
	isave->Write(&vert1,sizeof(int),&nb);
	isave->Write(&poly2,sizeof(int),&nb);
	isave->Write(&vert2,sizeof(int),&nb);
	isave->Write(&selected2,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(VCONR_OLDVSEL1_CHUNK);
	oldVSel1.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VCONR_OLDSSEL1_CHUNK);
	oldSSel1.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VCONR_SPLINE1_CHUNK);
	oldSpline1.Save(isave);
	isave->	EndChunk();
	if(poly1 != poly2) {
		isave->BeginChunk(VCONR_OLDVSEL2_CHUNK);
		oldVSel2.Save(isave);
		isave->	EndChunk();
		isave->BeginChunk(VCONR_OLDSSEL2_CHUNK);
		oldSSel2.Save(isave);
		isave->	EndChunk();
		isave->BeginChunk(VCONR_SPLINE2_CHUNK);
		oldSpline2.Save(isave);
		isave->	EndChunk();
		}
	return IO_OK;
	}

IOResult VertConnectRecord::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 VCONR_GENERAL_CHUNK:
				res = iload->Read(&poly1,sizeof(int),&nb);
				res = iload->Read(&vert1,sizeof(int),&nb);
				res = iload->Read(&poly2,sizeof(int),&nb);
				res = iload->Read(&vert2,sizeof(int),&nb);
				res = iload->Read(&selected2,sizeof(int),&nb);
				break;
			case VCONR_OLDVSEL1_CHUNK:
				res = oldVSel1.Load(iload);
				break;
			case VCONR_OLDSSEL1_CHUNK:
				res = oldSSel1.Load(iload);
				break;
			case VCONR_SPLINE1_CHUNK:
				res = oldSpline1.Load(iload);
				break;
			case VCONR_OLDVSEL2_CHUNK:
				res = oldVSel2.Load(iload);
				break;
			case VCONR_OLDSSEL2_CHUNK:
				res = oldSSel2.Load(iload);
				break;
			case VCONR_SPLINE2_CHUNK:
				res = oldSpline2.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

VertInsertRecord::VertInsertRecord(BezierShape *shape, int poly) : ModRecord() {
	this->poly = poly;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	}

BOOL VertInsertRecord::Undo(BezierShape *shape) {
	if(poly >= shape->splineCount)
		return FALSE;
	shape->DeleteSpline(poly);
	shape->InsertSpline(&oldSpline, poly);
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	return TRUE;
	}

BOOL VertInsertRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		}
	shape->DeleteSpline(poly);
	shape->InsertSpline(&newSpline, poly);
	shape->vertSel[poly] = newVSel;
	shape->segSel[poly] = newSSel;
	return TRUE;
	}

#define VINSR_GENERAL_CHUNK		0x1000
#define VINSR_OLDVSEL_CHUNK		0x1010
#define VINSR_OLDSSEL_CHUNK		0x1050
#define VINSR_OLDSPLINE_CHUNK	0x1060
#define VINSR_NEWVSEL_CHUNK		0x1070
#define VINSR_NEWSSEL_CHUNK		0x1080
#define VINSR_NEWSPLINE_CHUNK	0x1090

IOResult VertInsertRecord::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(VINSR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(VINSR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VINSR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VINSR_OLDSPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VINSR_NEWVSEL_CHUNK);
	newVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VINSR_NEWSSEL_CHUNK);
	newSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VINSR_NEWSPLINE_CHUNK);
	newSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult VertInsertRecord::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 VINSR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				break;
			case VINSR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case VINSR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case VINSR_OLDSPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			case VINSR_NEWVSEL_CHUNK:
				res = newVSel.Load(iload);
				break;
			case VINSR_NEWSSEL_CHUNK:
				res = newSSel.Load(iload);
				break;
			case VINSR_NEWSPLINE_CHUNK:
				res = newSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

PolyFirstRecord::PolyFirstRecord(BezierShape *shape, int poly, int vertex) : ModRecord() {
	this->poly = poly;
	this->vertex = vertex;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	}

BOOL PolyFirstRecord::Undo(BezierShape *shape) {
	if(poly >= shape->splineCount)
		return FALSE;
	shape->DeleteSpline(poly);
	shape->InsertSpline(&oldSpline, poly);
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	return TRUE;
	}

BOOL PolyFirstRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount || !IsCompatible(shape, poly, &oldVSel, &oldSSel))
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		}
	shape->MakeFirst(poly, vertex);
	return TRUE;
	}

#define PFR_GENERAL_CHUNK		0x1000
#define PFR_OLDVSEL_CHUNK		0x1010
#define PFR_OLDSSEL_CHUNK		0x1050
#define PFR_OLDSPLINE_CHUNK		0x1060

IOResult PolyFirstRecord::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(PFR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&vertex,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(PFR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PFR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(PFR_OLDSPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PolyFirstRecord::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 PFR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&vertex,sizeof(int),&nb);
				break;
			case PFR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case PFR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case PFR_OLDSPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

VertBreakRecord::VertBreakRecord(BezierShape *shape, int poly) : ModRecord() {
	this->poly = poly;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	selected = shape->polySel[poly];
	oldSplineCount = shape->splineCount;
	}

BOOL VertBreakRecord::Undo(BezierShape *shape) {
	// Get rid of all the new polys we created...
	while(shape->splineCount > oldSplineCount)
		shape->DeleteSpline(shape->splineCount - 1);
	if(poly >= shape->splineCount)
		return FALSE;
	// Get rid of the one we changed...
	shape->DeleteSpline(poly);
	// Put back the original poly we broke...
	shape->InsertSpline(&oldSpline,poly);
	// Restore the selection information
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	shape->polySel.Set(poly,selected);
	return TRUE;
	}

// Break a polygon at all selected vertices
void BreakSplineAtSelVerts(BezierShape *shape, int poly) {
	Spline3D *spline = shape->splines[poly];
	int altered = 0;
	for(int k = spline->KnotCount()-1; k >= 0; --k) {
		BitArray& vsel = shape->vertSel[poly];
		BitArray& ssel = shape->segSel[poly];
		int vert = k*3+1;
		if(vsel[vert]) {
			if(spline->Closed()) {
				if(k == (spline->KnotCount()-1)) {	// Break at end knot
					break_at_last_knot:
					altered = 1;
					SplineKnot dupKnot(spline->GetKnotType(k),spline->GetLineType(k),
						spline->GetKnotPoint(k),spline->GetInVec(k),spline->GetOutVec(k));
					spline->AddKnot(dupKnot, 0);
					spline->SetOpen();
					vsel.Clear(vert);
					vsel.SetSize(spline->Verts(),1);
					vsel.Shift(RIGHT_BITSHIFT,3);
					vsel.Clear(0);	// New point not selected
					vsel.Clear(1);
					vsel.Clear(2);
					ssel.SetSize(spline->Segments(),1);
					ssel.Shift(RIGHT_BITSHIFT,1);
					ssel.Clear(0);
					k++;	// Increment pointer so we don't miss a knot!
					}
				else
				if(k == 0) {			// Break at first knot
					altered = 1;
					SplineKnot dupKnot(spline->GetKnotType(0),spline->GetLineType(0),
						spline->GetKnotPoint(0),spline->GetInVec(0),spline->GetOutVec(0));
					spline->AddKnot(dupKnot, -1);
					spline->SetOpen();
					vsel.Clear(vert);
					vsel.SetSize(spline->Verts(),1);
					vsel.Clear(spline->Verts()-3);		// New point not selected
					vsel.Clear(spline->Verts()-2);
					vsel.Clear(spline->Verts()-1);
					ssel.SetSize(spline->Segments(),1);
					ssel.Clear(spline->KnotCount()-1);
					}
				else {					// Break somewhere in the middle
					// First, rotate the spline so that the break point is the last one!
					int rotations = 0;
					int lastKnot = spline->KnotCount() - 1;
					while(k < (lastKnot)) {
						SplineKnot dupKnot(spline->GetKnotType(lastKnot),spline->GetLineType(lastKnot),
							spline->GetKnotPoint(lastKnot),spline->GetInVec(lastKnot),spline->GetOutVec(lastKnot));
						spline->DeleteKnot(lastKnot);
						spline->AddKnot(dupKnot, 0);
						rotations++;
						k++;
						}
					vsel.Rotate(RIGHT_BITSHIFT, rotations*3);
					ssel.Rotate(RIGHT_BITSHIFT, rotations);
					k = lastKnot;
					vert = k*3+1;
					goto break_at_last_knot;
					}
				}
			else {
				// Don't do anything if the spline's open and we're breaking at an end vertex!
				if (k == 0 || k == (spline->KnotCount()-1)) {
					vsel.Clear(vert);	// Just turn off the selection bit
					continue;
					}
				int i;
				int newPolySize = spline->KnotCount() - k;
				// OK, We're breaking at a point in the middle -- Copy end points off to a new spline, then
				// delete them from the original!
				Spline3D *newSpline = shape->NewSpline();
				for(i = k; i < spline->KnotCount(); ++i) {
					SplineKnot dupKnot(spline->GetKnotType(i),spline->GetLineType(i),
						spline->GetKnotPoint(i),spline->GetInVec(i),spline->GetOutVec(i));
					newSpline->AddKnot(dupKnot, -1);
					}
				for(i = spline->KnotCount()-1; i > k; --i)
					spline->DeleteKnot(i);
				vsel.Clear(vert);	// Deselect the knot
				// Adjust selection data for this spline
				vsel.SetSize(spline->Verts(),1);
				ssel.SetSize(spline->Segments(),1);
				// Don't forget to create a new selection record for this new spline!
				shape->vertSel.Insert(shape->splineCount - 1, newPolySize * 3);
				shape->segSel.Insert(shape->splineCount - 1, newPolySize - 1);
				shape->polySel.Insert(shape->splineCount - 1);
				}
			}
		}
	if(altered) {
		spline->ComputeBezPoints();
		shape->InvalidateGeomCache();
		}
	}

BOOL VertBreakRecord::Redo(BezierShape *shape,int reRecord) {
	if(!IsCompatible(shape, poly, &oldVSel, &oldSSel))
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		selected = shape->polySel[poly];
		}
	BreakSplineAtSelVerts(shape, poly);
	return TRUE;
	}

#define VBRKR_GENERAL_CHUNK		0x1000
#define VBRKR_OLDVSEL_CHUNK		0x1040
#define VBRKR_OLDSSEL_CHUNK		0x1050
#define VBRKR_SPLINE_CHUNK		0x1060

IOResult VertBreakRecord::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(VBRKR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&selected,sizeof(int),&nb);
	isave->Write(&oldSplineCount,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(VBRKR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VBRKR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VBRKR_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult VertBreakRecord::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 VBRKR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&selected,sizeof(int),&nb);
				res = iload->Read(&oldSplineCount,sizeof(int),&nb);
				break;
			case VBRKR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case VBRKR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case VBRKR_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

VertWeldRecord::VertWeldRecord(BezierShape *shape, int poly, float thresh) : ModRecord() {
	this->poly = poly;
	this->thresh = thresh;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	selected = shape->polySel[poly];
	deleted = FALSE;
	}

BOOL VertWeldRecord::Undo(BezierShape *shape) {
	// Get rid of the one we changed...
	if(!deleted) {
		if(poly >= shape->splineCount)
			return FALSE;
		shape->DeleteSpline(poly);
		}
	if(poly > shape->splineCount)
		return FALSE;
	// Put back the original poly we broke...
	shape->InsertSpline(&oldSpline,poly);
	// Restore the selection information
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	shape->polySel.Set(poly,selected);
	return TRUE;
	}

// Weld a polygon at all selected vertices
// Returns TRUE if weld results in entire spline deletion (degenerate, single vertex poly)

BOOL WeldSplineAtSelVerts(BezierShape *shape, int poly, float thresh) {
	int i;
	Spline3D *spline = shape->splines[poly];
	BitArray& vsel = shape->vertSel[poly];
	BitArray& ssel = shape->segSel[poly];
	int knots = spline->KnotCount();
	BitArray toPrev(knots);
	BitArray toNext(knots);
	toNext.ClearAll();

	// Create weld attachment flag arrays
	for(i = 0; i < knots; ++i) {
		if(vsel[i*3+1]) {
			int next = (i + 1) % knots;
			if(vsel[next*3+1] && Length(spline->GetKnotPoint(i) - spline->GetKnotPoint(next)) <= thresh)
				toNext.Set(i);
			}
		}

	// Now process 'em!
	for(i = knots - 1; i >= 0; --i) {
		if(toNext[i]) {
			int next = (i + 1) % spline->KnotCount();
			if(i == (spline->KnotCount() - 1))
				spline->SetClosed();
			Point3 midpoint = (spline->GetKnotPoint(i) + spline->GetKnotPoint(next)) / 2.0f;
			Point3 nextDelta = midpoint - spline->GetKnotPoint(next);
			Point3 thisDelta = midpoint - spline->GetKnotPoint(i);
			spline->SetKnotPoint(next, spline->GetKnotPoint(next) + nextDelta);
			spline->SetOutVec(next, spline->GetOutVec(next) + nextDelta);
			spline->SetInVec(next, spline->GetInVec(i) + thisDelta);
			if(spline->IsBezierPt(i) || spline->IsBezierPt(next))
				spline->SetKnotType(next, KTYPE_BEZIER_CORNER);
			else
			if(spline->IsCorner(i) || spline->IsCorner(next))
				spline->SetKnotType(next, KTYPE_CORNER);
			spline->DeleteKnot(i);
			}
		}

	// If the polygon is degenerate, blow it away!
	if(spline->KnotCount() < 2) {
		spline->NewSpline();
		shape->DeleteSpline(poly);
		return TRUE;
		}

	// Update the auto points
	spline->ComputeBezPoints();
	shape->InvalidateGeomCache();

	// Clear out the selection sets for verts and segments
	vsel.SetSize(spline->Verts(),0);
	vsel.ClearAll();
	ssel.SetSize(spline->Segments(),0);
	ssel.ClearAll();
	return FALSE;
	}

BOOL VertWeldRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		selected = shape->polySel[poly];
		}
	deleted = WeldSplineAtSelVerts(shape, poly, thresh);
	return TRUE;
	}

#define VWELDR_GENERAL_CHUNK		0x1000
#define VWELDR_OLDVSEL_CHUNK		0x1040
#define VWELDR_OLDSSEL_CHUNK		0x1050
#define VWELDR_SPLINE_CHUNK			0x1060

IOResult VertWeldRecord::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(VWELDR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&thresh,sizeof(float),&nb);
	isave->Write(&selected,sizeof(int),&nb);
	isave->Write(&deleted,sizeof(BOOL),&nb);
	isave->	EndChunk();
	isave->BeginChunk(VWELDR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VWELDR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VWELDR_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult VertWeldRecord::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 VWELDR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&thresh,sizeof(float),&nb);
				res = iload->Read(&selected,sizeof(int),&nb);
				res = iload->Read(&deleted,sizeof(BOOL),&nb);
				break;
			case VWELDR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case VWELDR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case VWELDR_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

BooleanRecord::BooleanRecord(BezierShape *shape, int poly1, int poly2, int op) : ModRecord() {
	this->poly1 = poly1;
	this->poly2 = poly2;
	oldSplineCount = shape->splineCount;
	operation = op;
	oldSpline1 = *(shape->splines[poly1]);
	oldSpline2 = *(shape->splines[poly2]);
	oldVSel1 = shape->vertSel[poly1];
	oldSSel1 = shape->segSel[poly1];
	oldVSel2 = shape->vertSel[poly2];
	oldSSel2 = shape->segSel[poly2];
	}

BOOL BooleanRecord::Undo(BezierShape *shape) {
	// Get rid of the new polygons, adjusting for the two originals
	int targetPolys = oldSplineCount - 2;
	// Get rid of all the new polys we created...
	while(shape->splineCount > targetPolys)
		shape->DeleteSpline(shape->splineCount - 1);
	// Must insert polys back in proper order!
	if(poly1 < poly2) {
		if(poly1 > shape->splineCount)
			return FALSE;
		shape->InsertSpline(&oldSpline1, poly1);
		if(poly2 > shape->splineCount)
			return FALSE;
		shape->InsertSpline(&oldSpline2, poly2);
		}
	else {
		if(poly2 > shape->splineCount)
			return FALSE;
		shape->InsertSpline(&oldSpline2, poly2);
		if(poly1 > shape->splineCount)
			return FALSE;
		shape->InsertSpline(&oldSpline1, poly1);
		}
	shape->vertSel[poly1] = oldVSel1;
	shape->segSel[poly1] = oldSSel1;
	shape->polySel.Set(poly1,1);
	shape->vertSel[poly2] = oldVSel2;
	shape->segSel[poly2] = oldSSel2;
	shape->polySel.Set(poly2,0);
	return TRUE;
	}

// A handy 2D floating-point box class

class Box2D {
	public:
		BOOL empty;
		Point2 min, max;
		Box2D() { empty = TRUE; }
		void SetEmpty() { empty = TRUE; }
		Box2D& operator+=(const Point2& p);	// expand this box to include p
	};

Box2D& Box2D::operator+=(const Point2& p) {
	if(empty) {
		min = max = p;
		empty = FALSE;
		}
	else {
		if(p.x < min.x) min.x = p.x;
		if(p.x > max.x) max.x = p.x;
		if(p.y < min.y) min.y = p.y;
		if(p.y > max.y) max.y = p.y;
		}
	return *this;
	}

#define FSGN(x) (x < 0.0f ? -1 : 1)

// Determine if the line segments p1-p2 and p3-p4 intersect
// Returns: 0 (no hit) 1 (hit) 2 (parallel)

static int IntSeg(Point2& p1, Point2& p2, Point2& p3, Point2& p4, Point2& icpt) {
	float C1,C2,DENOM;
	float test1,test2;
	int sgnx1,sgny1,sgnx2,sgny2,hitend;

	/* First, simple minmax test */

	Box2D line1, line2;
	line1.SetEmpty();
	line1 += p1;
	line1 += p2;
	line2.SetEmpty();
	line2 += p3;
	line2 += p4;

	if(line1.max.x < line2.min.x || line1.min.x > line2.max.x || line1.max.y < line2.min.y || line1.min.y > line2.max.y)
		return 0;

	/* A1=-dy1 B1=dx1 A2=-dy2 B2=dx2 */

	Point2 d1 = p2 - p1;
	C1= -(p1.y * d1.x - p1.x * d1.y);

	Point2 d2 = p4 - p3;
	C2= -(p3.y * d2.x - p3.x * d2.y);

	DENOM= -d1.y * d2.x + d2.y * d1.x;

	if(DENOM==0.0)		/* Lines parallel!!! */
	 {
	 test1= -p1.x * d2.y + p1.y * d2.x + C2;
	 test2= -p3.x * d1.y + p3.y * d1.x + C1;
	 if(test1==test2)	/* Lines collinear! */
	  {
	  if(p1 == p3)
	   {
	   icpt = p1;
	   hitend=1;
	   sgnx1=FSGN(p2.x - p1.x);
	   sgny1=FSGN(p2.y - p1.y);
	   sgnx2=FSGN(p3.x - p4.x);
	   sgny2=FSGN(p3.y - p4.y);
	   }
	  else
	  if(p1 == p4)
	   {
	   icpt = p1;
	   hitend=1;
	   sgnx1=FSGN(p2.x - p1.x);
	   sgny1=FSGN(p2.y - p1.y);
	   sgnx2=FSGN(p4.x - p3.x);
	   sgny2=FSGN(p4.y - p3.y);
	   }
	  else
	  if(p2 == p3)
	   {
	   icpt = p2;
	   hitend=1;
	   sgnx1=FSGN(p1.x - p2.x);
	   sgny1=FSGN(p1.y - p2.y);
	   sgnx2=FSGN(p3.x - p4.x);
	   sgny2=FSGN(p3.y - p4.y);
	   }
	  else
	  if(p2 == p4)
	   {
	   icpt = p2;
	   hitend=1;
	   sgnx1=FSGN(p1.x - p2.x);
	   sgny1=FSGN(p1.y - p2.y);
	   sgnx2=FSGN(p4.x - p3.x);
	   sgny2=FSGN(p4.y - p3.y);
	   }
	  else
	   hitend=0;

	  if(hitend)
	   {
	   if(sgnx1==sgnx2 && sgny1==sgny2)	/* Hit at endpoint */
	    return(1);
	   }
	  return(2);
	  }
	 return(0);
	 }

	if(p1.x == p2.x)
	 icpt.x = p1.x;
	else
	if(p3.x == p4.x)
	 icpt.x = p3.x;
	else
	icpt.x = (d1.x * C2 - d2.x * C1) / DENOM;

	if(p1.y == p2.y)
	 icpt.y = p1.y;
	else
	if(p3.y == p4.y)
	 icpt.y = p3.y;
	else
	icpt.y = (-C1 * d2.y + C2 * d1.y) / DENOM;

	/* See if it hit line 1 */

	if(p1.x < p2.x)
	 {
	 if(icpt.x < p1.x || icpt.x > p2.x)
	  return(0);
	 }
	else
	 {
	 if(icpt.x < p2.x || icpt.x > p1.x)
	  return(0);
	 }
	if(p1.y < p2.y)
	 {
	 if(icpt.y < p1.y || icpt.y > p2.y)
	  return(0);
	 }
	else
	 {
	 if(icpt.y < p2.y || icpt.y > p1.y)
	  return(0);
	 }

	/* See if it hit line 2 */

	if(p3.x < p4.x)
	 {
	 if(icpt.x < p3.x || icpt.x > p4.x)
	  return(0);
	 }
	else
	 {
	 if(icpt.x < p4.x || icpt.x > p3.x)
	  return(0);
	 }
	if(p3.y < p4.y)
	 {
	 if(icpt.y < p3.y || icpt.y > p4.y)
	  return(0);
	 }
	else
	 {
	 if(icpt.y < p4.y || icpt.y > p3.y)
	  return(0);
	 }

	/* It hits both! */

	return(1);
	}

// Determine where the line segments p1-p2 and p3-p4 intersect
static int FindIntercept(Point2& p1, Point2& p2, Point2& p3, Point2& p4, Point2& icpt) {
	float C1,C2,DENOM;
	Point2 d1,d2;
	float test1,test2;

	d1 = p2 - p1;
	C1= -(p1.y * d1.x - p1.x * d1.y);

	d2 = p4 - p3;
	C2= -(p3.y * d2.x - p3.x * d2.y);

	DENOM= -d1.y * d2.x + d2.y * d1.x;

	if(DENOM==0.0)		/* Lines parallel!!! */
	 {
	 test1 = p1.x * -d2.y + p1.y * d2.x + C2;
	 test2 = p3.x * -d1.y + p3.y * d1.x + C1;
	 if(test1==test2)	/* Lines collinear! */
	  return(2);
	 return(0);
	 }

	if(p1.x == p2.x)
	 icpt.x = p1.x;
	else
	if(p3.x == p4.x)
	 icpt.x = p3.x;
	else
	icpt.x = (d1.x * C2 - d2.x * C1) / DENOM;

	if(p1.y == p2.y)
	 icpt.y = p1.y;
	else
	if(p3.y == p4.y)
	 icpt.y = p3.y;
	else
	icpt.y = (C1 * (-d2.y) - C2 * (-d1.y)) / DENOM;

	return(1);
	}



class Template {
	public:
		int points;
		Point2 *pts;
		Template(Spline3D *spline);
		~Template();
		int Points() { return points; }
		int OriginalSegment(int seg) { return seg / 10; }
//		BOOL Orient();
		void FlagInside(Spline3D *spline, IntTab& flags);
		BOOL SurroundsPoint(Point2& point);
	};

Template::Template(Spline3D *spline) {
	int segments = spline->Segments();
	points = segments * 10 + 1;
	pts = new Point2[points];
	int point;
	Point3 p3;
	int i,j;
	float pct;
	for(i = 0, point = 0; i < segments; ++i) {
		for(j = 0, pct = 0.0f; j < 10; ++j, pct += 0.1f, ++point) {
			p3 = spline->InterpBezier3D(i, pct);
			pts[point] = Point2(p3.x, p3.y);
			}
		}
	if(spline->Closed())
		pts[point] = pts[0];
	else {
		p3 = spline->InterpBezier3D(segments, 0.0f);
		pts[point] = Point2(p3.x, p3.y);
		}
	}

Template::~Template() {
	delete [] pts;
	}

static int myrand(int mask) {
	return rand() & mask;
	}

BOOL Template::SurroundsPoint(Point2& point) {
	int ix,ix2,outs,xout,yout;
	int hits,cept,trials,odds,evens;
	Point2 point2, point3, point4, where;
		
	evens=odds=0;
	
	/* Do three trials -- use the one with most */

	for(trials=0; trials<3; ++trials)
		{

		surr_try:
		point2.x = point.x + (float)(myrand(63) + 1);		/* Random second point */
		point2.y = point.y + (float)(myrand(63) - 32);
//DebugPrint("Point:%.2f %.2f Point2:%.2f %.2f\n",point.x,point.y,point2.x,point2.y);
		hits=0;
		for(ix=0; ix<(points - 1); ++ix)
			{
			ix2=ix+1;
	
			point3 = pts[ix];
			point4 = pts[ix2];

			/* Check for intercept */

			cept=FindIntercept(point, point2, point3, point4, where);

			switch(cept)
				{
				case 1:
					/* If lines intersect, check to see if it's in segment */
					if(where.x > point.x) {
						outs = 0;
						/* Abort test if ray goes thru endpoint */
						if(where == point3 || where == point4)
							goto surr_try;
	
						/* Figure out major axis of test line */
	
						xout=(fabs(point4.x - point3.x) > fabs(point4.y - point3.y)) ? 2:1;
						yout=(xout==2) ? 1:2;
	
						/* If ray intercepts line, record hit */
	
						if(point3.x < point4.x)
							{
							if(where.x < point3.x || where.x > point4.x)
								outs+=xout;
							}
						else
						if(point3.x > point4.x)
							{
							if(where.x < point4.x || where.x > point3.x)
								outs+=xout;
							}
						else
							outs++;
	
						if(point3.y < point4.y)
							{
							if(where.y < point3.y || where.y > point4.y)
								outs+=yout;
							}
						else
						if(point3.y > point4.y)
							{
							if(where.y < point4.y || where.y > point3.y)
								outs+=yout;
							}
						else
							outs++;
	
						/* If not outside both coords, it hits! */
	
						if(outs<2)
							hits++;
						}
					break;
				case 2:
					goto surr_try;
				case 0:
					break;
				}
			}
	
	/* odd # hits = surrounded! */
	
		if(hits & 1)
			odds++;
		else
			evens++;
		}
	
	/* even # hits = outside! */
	
	if(odds>evens)
		return(TRUE);
	return(FALSE);
	}

void Template::FlagInside(Spline3D *spline, IntTab& flags) {
//DebugPrint("Starting FlagInside\n");
	int segs = spline->Segments();
	for(int i = 0; i < segs; ++i) {
		Point3 p = spline->InterpBezier3D(i, 0.5f);	// Use midpoint to determine whether we're inside or out
		flags[i] = flags[i] | SurroundsPoint(Point2(p.x, p.y)) ? POLYINSIDE : POLYOUTSIDE;
//DebugPrint("Seg %d inside:%s outside:%s\n",i, (flags[i] & POLYINSIDE) ? "YES":"NO", (flags[i] & POLYOUTSIDE) ? "YES":"NO");
		}
	}

class BoolHitData {
	public:
		int id;
		int poly;
		int seg;
		float pct;
		Point2 where;
		Point3 force;
		BOOL gotForce;
		BoolHitData() { gotForce = FALSE; }
		BoolHitData(int id, int poly, int seg, float pct, Point2& where)
			{ gotForce = FALSE; this->id = id; this->poly = poly; this->seg = seg; this->pct = pct; this->where = where; }
		int operator==( const BoolHitData& b ) const { 	return (poly==b.poly && seg==b.seg && pct==b.pct && where==b.where); }
	};

class BoolHit {
	public:
		int count;
		BoolHitData *data;
		BoolHit() { data = NULL; }
		~BoolHit() { if(data) delete [] data; }
		void SetSize(int count);
		void Set(Spline3D *spline, int location, int id, int poly, int seg, Point2& where);
		void Sort();
		BOOL GotForcePoint(BoolHitData& h, Point3* force);
		void SetForcePoint(BoolHitData& h, Point3& force);
	};

void BoolHit::SetSize(int count) {
	data = new BoolHitData[count];
	this->count = count;
	for(int i=0; i<count; ++i)
		data[i].gotForce = FALSE;
	}

BOOL BoolHit::GotForcePoint(BoolHitData& h, Point3* force) {
	for(int i = 0; i < count; ++i) {
		if(data[i].id == h.id && data[i].gotForce) {
			*force = data[i].force;
			return TRUE;
			}
		}
	return FALSE;
	}

void BoolHit::SetForcePoint(BoolHitData& h, Point3& force) {
	h.gotForce = TRUE;
	h.force = force;
//DebugPrint("Set force for id# %d to %.4f %.4f\n",h.id,force.x, force.y);
	}

#define CURVELOOPS 15

float Calc2DCurvePct(Spline3D *spline, int seg, Point2& where) {
	int count = CURVELOOPS;
	float low = 0.0f;
	float high = 1.0f;
	float bestL,bestPos;
	while(count--) {
		float range = high - low;
		float step = range / 10.0f;
		float pos;
		int i;

		for(i = 0, pos = low; i <= 10; ++i, pos += step) {
			Point3 p = spline->InterpBezier3D(seg, pos);
			Point2 p1 = Point2(p.x, p.y);
			float l = Length(p1 - where);
			if(i == 0 || l < bestL) {
				bestL = l;
				bestPos = pos;
				}
			}
		low = bestPos - step;
		if(low < 0.0f)
			low = 0.0f;
		high = bestPos + step;
		if(high > 1.0f)
			high = 1.0f;
		}
	return bestPos;
	}

void BoolHit::Set(Spline3D *spline, int location, int id, int poly, int seg, Point2& where) {
	// Find where on the curve the hit occurred, percentage-wise
	float pct = Calc2DCurvePct(spline, seg, where);
//DebugPrint("Hit %d on poly %d, seg:%d at %.4f %.4f was %.4f percent\n",location,poly,seg,where.x,where.y,pct);
//Point3 check = spline->InterpBezier3D(seg,pct);
//DebugPrint("Cross-check: %.4f %.4f\n",check.x,check.y);
	data[location] = BoolHitData(id, poly, seg, pct, where);
	}

void BoolHit::Sort() {
	BoolHitData temp;
	unsigned int srtcnt;
	register unsigned int srt1,srt2,srt3,srt4;

	srtcnt=count+1;

	nxtsc:
	srtcnt/=2;
	if(srtcnt==0) {
		int ix,jx;
		/* Eliminate duplicates */
		ix=0;
		jx=1;
		dup_loop:
	 	if(data[ix] == data[jx])
			jx++;
		else {
			ix++;
			if(ix!=jx)
				data[ix] = data[jx];
			jx++;
			}
		if(jx<count)
			goto dup_loop;
		count=ix+1;
		return;
		}
	srt1=1;
	srt2=count-srtcnt;

	srtsw1:
	srt3=srt1;

	srtsw2:
	srt4=srt3+srtcnt;

	if(data[srt3-1].poly < data[srt4-1].poly)
		goto swap;
	if(data[srt3-1].poly > data[srt4-1].poly)
		goto next;
	if(data[srt3-1].seg < data[srt4-1].seg)
		goto swap;
	if(data[srt3-1].seg > data[srt4-1].seg)
		goto next;
	if(data[srt3-1].pct >= data[srt4-1].pct)
		goto next;

	swap:
	temp = data[srt3-1];
	data[srt3-1] = data[srt4-1];
	data[srt4-1] = temp;
	if(srtcnt<srt3) {
		srt3-=srtcnt;
		goto srtsw2;
		}

	next:
	srt1++;
	if(srt1<=srt2)
		goto srtsw1;
	goto nxtsc;
	}

static void BoundSpline2D(Spline3D *spline, Box2D& box) {
	Point2 p2;
	Point3 p3;
	box.SetEmpty();
	int knots = spline->KnotCount();
	int closed = spline->Closed();
	int lastKnot = knots - 1;
	for(int i = 0; i < knots; ++i) {
		p3 = spline->GetKnotPoint(i);
		p2.x = p3.x;
		p2.y = p3.y;
		box += p2;
		if (i > 0 || closed) {
			p3 = spline->GetInVec(i);
			p2.x = p3.x;
			p2.y = p3.y;
			box += p2;
			}
		if(i < lastKnot || closed) {
			p3 = spline->GetOutVec(i);
			p2.x = p3.x;
			p2.y = p3.y;
			box += p2;
			}
		}
	}

static void BoolCopy(BezierShape *shape, int poly, IntTab& flags, int mask) {
	BitArray &ssel = shape->segSel[poly];
	ssel.ClearAll();
	Spline3D *spline = shape->splines[poly];
	int segs = spline->Segments();
	int i;
	// Mark the segments that don't contain the mask flag for deletion
	for(i = 0; i < segs; ++i) {
		if(!(flags[i] & mask))
			ssel.Set(i);
		}

	// Now delete all those segments that we just flagged!
#ifndef DEBUGBOOL
	DeleteSelSegs(shape, poly);
#endif
	}																

// Weld all polygons in the shape at common endpoints

static BOOL WeldShape(BezierShape *shape) {
	int i,j;
	int lastKnot1,lastKnot2,knots1,knots2;
	Point3 p;
	BOOL attached;
	do {
		attached = FALSE;

		for(i = 0; i < shape->splineCount; ++i) {
			Spline3D *spline1 = shape->splines[i];
			knots1 = spline1->KnotCount();
			lastKnot1 = knots1 - 1;
			p = spline1->GetKnotPoint(0);
			Point2 s1first = Point2(p.x, p.y);
			p = spline1->GetKnotPoint(lastKnot1);
			Point2 s1last = Point2(p.x, p.y);

			for(j = shape->splineCount - 1; j > i; --j) {
				Spline3D *spline2 = shape->splines[j];
				knots2 = spline2->KnotCount();
				lastKnot2 = knots2 - 1;
				p = spline2->GetKnotPoint(0);
				Point2 s2first = Point2(p.x, p.y);
				p = spline2->GetKnotPoint(lastKnot2);
				Point2 s2last = Point2(p.x, p.y);
//DebugPrint("Checking %d/%d: %.4f %.4f - %.4f %.4f / %.4f %.4f - %.4f %.4f\n",i,j,s1first.x,s1first.y,s1last.x,s1last.y,
//	s2first.x,s2first.y,s2last.x,s2last.y);
				if(s1first == s2first) {
					spline2->Reverse();
					spline1->Prepend(spline2);
					shape->DeleteSpline(j);
//DebugPrint("Welded 1!\n");
					attached = TRUE;
					}
				else
				if(s1first == s2last) {
					spline1->Prepend(spline2);
					shape->DeleteSpline(j);
//DebugPrint("Welded 2!\n");
					attached = TRUE;
					}
				else
				if(s1last == s2first) {
					spline1->Append(spline2);
					shape->DeleteSpline(j);
//DebugPrint("Welded 3!\n");
					attached = TRUE;
					}
				else
				if(s1last == s2last) {
					spline2->Reverse();
					spline1->Append(spline2);
					shape->DeleteSpline(j);
//DebugPrint("Welded 4!\n");
					attached = TRUE;
					}
				}
			}
		} while(attached);

	// Now we go thru and make sure the resulting polygons are valid -- That is,
	// each must have coincident first and last points.  These polygons are stitched
	// together at the endpoints and closed.  If others are left, something
	// went hideously wrong.

	int polys = shape->splineCount;
//DebugPrint("Boolean resulted in %d polys\n",polys);
	for(i = 0; i < polys; ++i) {
		Spline3D *spline = shape->splines[i];
		// Make sure the first point is the same as the last, then close it
		int lastKnot = spline->KnotCount() - 1;
		p = spline->GetKnotPoint(0);
		Point2 first = Point2(p.x, p.y);
		p = spline->GetKnotPoint(lastKnot);
		Point2 last = Point2(p.x, p.y);
		if(!(first == last)) {
//DebugPrint("First:%.8f %.8f Last:%.8f %.8f\n",first.x, first.y, last.x, last.y);
			return FALSE;
			}
		spline->SetKnotType(0, KTYPE_BEZIER_CORNER);
		spline->SetInVec(0, spline->GetInVec(lastKnot));
		spline->DeleteKnot(lastKnot);
		shape->splines[i]->SetClosed();
		}

	return TRUE;
	}

// PerformBoolean return codes
#define BOOL_OK 1
#define BOOL_COINCIDENT_VERTEX	-1
#define BOOL_MUST_OVERLAP		-2
#define BOOL_WELD_FAILURE		-3

static int PerformBoolean(BezierShape *shape, int poly1, int poly2, int op, int *newPolyNum) {
	Spline3D *spline1 = shape->splines[poly1];
	int knots1 = spline1->KnotCount();
	Spline3D *spline2 = shape->splines[poly2];
	int knots2 = spline2->KnotCount();
	int i,j;

	// Can't have coincident vertices (why?)
	for(i = 0; i < knots1; ++i) {
		for(j = 0; j < knots2; ++j) {
			if(spline1->GetKnotPoint(i) == spline2->GetKnotPoint(j))
				return BOOL_COINCIDENT_VERTEX;
			}
		}
	
	// Make sure the polygons overlap in 2D
	Box2D bound1, bound2;
	BoundSpline2D(spline1, bound1);
	BoundSpline2D(spline2, bound2);
	if(bound2.min.x > bound1.max.x || bound2.max.x < bound1.min.x ||
	   bound2.max.y < bound1.min.y || bound2.min.y > bound1.max.y) {
		no_overlap:
		return BOOL_MUST_OVERLAP;
		}

	// Load the splines into special 2D objects which contain the entire interpolated spline
	Template t1(spline1);
	int points1 = t1.Points();
	int last1 = points1 - 1;
	Template t2(spline2);
	int points2 = t2.Points();
	int last2 = points2 - 1;

	// Find the intersections between the two splines
	// This is a two-pass procedure -- Pass 0 counts the number of collisions,
	// and pass 1 records them.

	BoolHit hit;
	for(int pass = 0; pass < 2; ++pass) {
		int overlap = 0;
		int hitID = 0;
		for(i = 0; i < last1; ++i) {
			Point2 i1 = t1.pts[i];
			Point2 i2 = t1.pts[i+1];
			int iseg = t1.OriginalSegment(i);
			for(j = 0; j < last2; ++j) {
				Point2 j1 = t2.pts[j];
				Point2 j2 = t2.pts[j+1];
				int jseg = t2.OriginalSegment(j);

				/* Now compare line endpoints for collision */
	
				if(i1 == j1 || i1 == j2) {
					if(pass == 1) {
						hit.Set(spline1, overlap++, hitID, 0, iseg, i1);
						hit.Set(spline2, overlap++, hitID++, 1, jseg, i1);
						}
					else
						overlap += 2;
					}
				else
				if(i2 == j1 || i2 == j2) {
					if(pass == 1) {
						hit.Set(spline1, overlap++, hitID, 0, iseg, i2);
						hit.Set(spline2, overlap++, hitID++, 1, jseg, i2);
						}
					else
						overlap += 2;
					}
	
				/* Now test line segments themselves for collisions */
		
				Point2 dummy;
				if(IntSeg(i1, i2, j1, j2, dummy)==1)
					{
					Point2 where;
					FindIntercept(i1, i2, j1, j2, where);
					if(pass==1)
						{
						hit.Set(spline1, overlap++, hitID, 0, iseg, where);
						hit.Set(spline2, overlap++, hitID++, 1, jseg, where);
						}
					else
						overlap+=2;
					}
				}
			}
		if(pass==0)
			{
			if(overlap==0)
				goto no_overlap;
			hit.SetSize(overlap);
			}
		}

	// Sort the hits in ascending order and eliminate dupes
	hit.Sort();

	/* Go thru hitlist and adjust percentages for proper splits						*/
	/* This is necessary because the percentages of each cut are in relation		*/
	/* to the original segment, and as each cut is made, the lower percentages		*/
	/* must be adjusted.  Example: Original cuts at 25%, 50% and 75%.  After the	*/
	/* cut at 75% is made, the 50% cut becomes 66.6%.  After the 50% cut is made,	*/
	/* the 25% cut becomes 50%.														*/

	for(i = hit.count - 1; i > 0; --i)
		{
		j = i - 1;
		if(hit.data[j].poly == hit.data[i].poly && hit.data[j].seg == hit.data[i].seg)
		hit.data[i].pct = hit.data[i].pct / hit.data[j].pct;
		}

	// Copy the splines to a work shape
	BezierShape workShape;
	Spline3D *work0, *work1, *work[2];
	work0 = work[0] = workShape.NewSpline();
	*work[0] = *spline1;
	work1 = work[1] = workShape.NewSpline();
	*work[1] = *spline2;
	workShape.UpdateSels();

	/* Go thru hitlist and split up the original polygons */

	for(i = 0; i < hit.count; ++i)
		{
		BoolHitData &h = hit.data[i];
		Point3 force;
		int seg1 = h.seg;
		int seg2 = (h.seg + 1) % work[h.poly]->KnotCount();

		// Split 'em up and make sure the splits in the two polys are at the EXACT same point
		if(h.pct < 0.001) {
			if(hit.GotForcePoint(h, &force))
				workShape.splines[h.poly]->SetKnotPoint(seg1, force);
			else
				hit.SetForcePoint(h, workShape.splines[h.poly]->GetKnotPoint(seg1));
			}
		else
		if(h.pct > 0.999) {
			if(hit.GotForcePoint(h, &force))
				workShape.splines[h.poly]->SetKnotPoint(seg2, force);
			else
				hit.SetForcePoint(h, workShape.splines[h.poly]->GetKnotPoint(seg2));
			}
		else {
			RefineSegment(&workShape, h.poly, seg1, h.pct);
			if(hit.GotForcePoint(h, &force))
				workShape.splines[h.poly]->SetKnotPoint(seg1+1, force);
			else
				hit.SetForcePoint(h, workShape.splines[h.poly]->GetKnotPoint(seg1+1));
			}
		}

	// Set up some flags arrays
	IntTab flags0, flags1;
	IntTab *flags[2];
	flags[0] = &flags0;
	flags[1] = &flags1;
	int segs = work0->Segments();
	flags0.SetCount(segs);
	for(i = 0; i < segs; ++i)
		flags0[i] = 0;
	segs = work1->Segments();
	flags1.SetCount(segs);
	for(i = 0; i < segs; ++i)
		flags1[i] = 0;

	/* Polygons are diced up, now mark each piece	*/
	/* as being inside or outside the other polygon	*/

	t1.FlagInside(work1, flags1);
	t2.FlagInside(work0, flags0);

	/* Now delete spans according to boolean operator	*/
	/* UNION: Delete inside spans on both				*/
	/* SUBTRACTION: Delete pri/inside, sec/outside		*/
	/* INTERSECTION: Delete outside spans on both		*/

	switch(op)
		{
		case BOOL_UNION:
			BoolCopy(&workShape,1,flags1,POLYOUTSIDE);
			BoolCopy(&workShape,0,flags0,POLYOUTSIDE);
			break;
		case BOOL_SUBTRACTION:
			BoolCopy(&workShape,1,flags1,POLYINSIDE);
			BoolCopy(&workShape,0,flags0,POLYOUTSIDE);
			break;
		case BOOL_INTERSECTION:
			BoolCopy(&workShape,1,flags1,POLYINSIDE);
			BoolCopy(&workShape,0,flags0,POLYINSIDE);
			break;
		}

	/* Weld boolean pieces together, if necessary */
	
#ifndef DEBUGBOOL
	if(TRUE /*weldBooleans*/) {
		if(!WeldShape(&workShape))
			return BOOL_WELD_FAILURE;
		}

	// Get rid of the originals
	if(poly1 < poly2) {
		shape->DeleteSpline(poly2);
		shape->DeleteSpline(poly1);
		}
	else {
		shape->DeleteSpline(poly1);
		shape->DeleteSpline(poly2);
		}

	// Add all our new ones
	int oldPolys = shape->splineCount;
	int newPolys = workShape.splineCount;
	for(i = 0; i < newPolys; ++i) {
		Spline3D *poly = shape->NewSpline();
		*poly = *workShape.splines[i];
		}
	shape->UpdateSels();
	if(newPolys == 1) {
		shape->polySel.Set(oldPolys);
		if(newPolyNum)
			*newPolyNum = oldPolys;
		}
	else {
		if(newPolyNum)
			*newPolyNum = -1;
		}
#else
	*shape = workShape;
#endif // DEBUGBOOL
	return BOOL_OK;
	}

BOOL BooleanRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly1 >= shape->splineCount || poly2 >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline1 = *(shape->splines[poly1]);
		oldSpline2 = *(shape->splines[poly2]);
		oldVSel1 = shape->vertSel[poly1];
		oldSSel1 = shape->segSel[poly1];
		oldVSel2 = shape->vertSel[poly2];
		oldSSel2 = shape->segSel[poly2];
		}
	PerformBoolean(shape, poly1, poly2, operation, NULL);
	return TRUE;
	}

#define BOOLR_GENERAL_CHUNK		0x1001
#define BOOLR_OLDSPLINE1_CHUNK	0x1010
#define BOOLR_OLDVSEL1_CHUNK	0x1020
#define BOOLR_OLDSSEL1_CHUNK	0x1030
#define BOOLR_OLDSPLINE2_CHUNK	0x1040
#define BOOLR_OLDVSEL2_CHUNK	0x1050
#define BOOLR_OLDSSEL2_CHUNK	0x1060

IOResult BooleanRecord::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(BOOLR_GENERAL_CHUNK);
	isave->Write(&poly1,sizeof(int),&nb);
	isave->Write(&poly2,sizeof(int),&nb);
	isave->Write(&operation,sizeof(int),&nb);
	isave->Write(&oldSplineCount,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(BOOLR_OLDSPLINE1_CHUNK);
	oldSpline1.Save(isave);
	isave->EndChunk();
	isave->BeginChunk(BOOLR_OLDVSEL1_CHUNK);
	oldVSel1.Save(isave);
	isave->EndChunk();
	isave->BeginChunk(BOOLR_OLDSSEL1_CHUNK);
	oldSSel1.Save(isave);
	isave->EndChunk();
	isave->BeginChunk(BOOLR_OLDSPLINE2_CHUNK);
	oldSpline2.Save(isave);
	isave->EndChunk();
	isave->BeginChunk(BOOLR_OLDVSEL2_CHUNK);
	oldVSel2.Save(isave);
	isave->EndChunk();
	isave->BeginChunk(BOOLR_OLDSSEL2_CHUNK);
	oldSSel2.Save(isave);
	isave->EndChunk();
	return IO_OK;
	}

IOResult BooleanRecord::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 BOOLR_GENERAL_CHUNK:
				res = iload->Read(&poly1,sizeof(int),&nb);
				res = iload->Read(&poly2,sizeof(int),&nb);
				res = iload->Read(&operation,sizeof(int),&nb);
				res = iload->Read(&oldSplineCount,sizeof(int),&nb);
				break;
			case BOOLR_OLDSPLINE1_CHUNK:
				res = oldSpline1.Load(iload);
				break;
			case BOOLR_OLDVSEL1_CHUNK:
				res = oldVSel1.Load(iload);
				break;
			case BOOLR_OLDSSEL1_CHUNK:
				res = oldSSel1.Load(iload);
				break;
			case BOOLR_OLDSPLINE2_CHUNK:
				res = oldSpline2.Load(iload);
				break;
			case BOOLR_OLDVSEL2_CHUNK:
				res = oldVSel2.Load(iload);
				break;
			case BOOLR_OLDSSEL2_CHUNK:
				res = oldSSel2.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}


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

AttachRecord::AttachRecord(BezierShape *shape, BezierShape *attShape) : ModRecord() {
	this->attShape = *attShape;
	oldSplineCount = shape->splineCount;
	}

BOOL AttachRecord::Undo(BezierShape *shape) {
	// Get rid of all the new polys we attached...
	while(shape->splineCount > oldSplineCount)
		shape->DeleteSpline(shape->splineCount - 1);
	return TRUE;
	}

static void DoAttach(BezierShape *shape, BezierShape *attShape) {
	for(int i = 0; i < attShape->splineCount; ++i) {
		int index = shape->splineCount;
		Spline3D *spline = shape->NewSpline();
		*spline = *(attShape->splines[i]);
		shape->vertSel.Insert(shape->splineCount-1,spline->Verts());
		shape->segSel.Insert(shape->splineCount-1,spline->Segments());
		shape->polySel.Insert(shape->splineCount-1);
		shape->vertSel[index] = attShape->vertSel[i];
		shape->segSel[index] = attShape->segSel[i];
		shape->polySel.Set(index, attShape->polySel[i]);
		}
	}

BOOL AttachRecord::Redo(BezierShape *shape,int reRecord) {
	if(reRecord)
		oldSplineCount = shape->splineCount;
	DoAttach(shape, &attShape);
	return TRUE;
	}

#define ATTR_GENERAL_CHUNK		0x1001
#define ATTR_ATTSHAPE_CHUNK		0x1010

IOResult AttachRecord::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(&oldSplineCount,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(ATTR_ATTSHAPE_CHUNK);
	attShape.Save(isave);
	isave->EndChunk();
	return IO_OK;
	}

IOResult AttachRecord::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(&oldSplineCount,sizeof(int),&nb);
				break;
			case ATTR_ATTSHAPE_CHUNK:
				res = attShape.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

VertChangeRecord::VertChangeRecord(BezierShape *shape, int poly, int vertex, int type) : ModRecord() {
	oldSpline = *shape->splines[poly];
	this->poly = poly;
	this->vertex = vertex;
	this->type = type;
	}

BOOL VertChangeRecord::Undo(BezierShape *shape) {
	if(poly >= shape->splineCount)
		return FALSE;
	*shape->splines[poly] = oldSpline;
	return TRUE;
	}

static void ChangeVertexType(BezierShape *shape, int poly, int vertex, int type) {
	Spline3D *spline = shape->splines[poly];
	
	// If positive vertex number, do it to just one vertex
	if(vertex >= 0) {
		spline->SetKnotType(vertex, type);
		spline->ComputeBezPoints();
		shape->InvalidateGeomCache();
		return;
		}

	// Otherwise, do it to all selected vertices!
	int knots = spline->KnotCount();
	BitArray &vsel = shape->vertSel[poly];
	for(int i = 0; i < knots; ++i) {
		if(vsel[i*3+1])
			spline->SetKnotType(i, type);
		}
	spline->ComputeBezPoints();
	shape->InvalidateGeomCache();
	}

BOOL VertChangeRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount || (vertex > 0 && vertex >= shape->splines[poly]->KnotCount()))
		return FALSE;
	if(reRecord) {
		oldSpline = *shape->splines[poly];
		}
	ChangeVertexType(shape, poly, vertex, type);
	return TRUE;
	}

#define VCHG_GENERAL_CHUNK		0x1001
#define VCHG_SPLINE_CHUNK		0x1010

IOResult VertChangeRecord::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(&poly,sizeof(int),&nb);
	isave->Write(&vertex,sizeof(int),&nb);
	isave->Write(&type,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(VCHG_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->EndChunk();
	return IO_OK;
	}

IOResult VertChangeRecord::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(&poly,sizeof(int),&nb);
				res = iload->Read(&vertex,sizeof(int),&nb);
				res = iload->Read(&type,sizeof(int),&nb);
				break;
			case VCHG_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

SegChangeRecord::SegChangeRecord(BezierShape *shape, int poly, int segment, int type) : ModRecord() {
	oldSpline = *shape->splines[poly];
	this->poly = poly;
	this->segment = segment;
	this->type = type;
	}

BOOL SegChangeRecord::Undo(BezierShape *shape) {
	if(poly >= shape->splineCount)
		return FALSE;
	*shape->splines[poly] = oldSpline;
	return TRUE;
	}

static void ChangeSegmentType(BezierShape *shape, int poly, int segment, int type) {
	Spline3D *spline = shape->splines[poly];
	
	// If positive segment number, do it to just one segment
	if(segment >= 0) {
		spline->SetLineType(segment, type);
		spline->ComputeBezPoints();
		shape->InvalidateGeomCache();
		return;
		}

	// Otherwise, do it to all selected vertices!
	int segments = spline->Segments();
	BitArray &ssel = shape->segSel[poly];
	for(int i = 0; i < segments; ++i) {
		if(ssel[i])
			spline->SetLineType(i, type);
		}
	spline->ComputeBezPoints();
	shape->InvalidateGeomCache();
	}

BOOL SegChangeRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount || (segment > 0 && segment >= shape->splines[poly]->Segments()))
		return FALSE;
	if(reRecord) {
		oldSpline = *shape->splines[poly];
		}
	ChangeSegmentType(shape, poly, segment, type);
	return TRUE;
	}

#define SCHG_GENERAL_CHUNK		0x1001
#define SCHG_SPLINE_CHUNK		0x1010

IOResult SegChangeRecord::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(SCHG_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&segment,sizeof(int),&nb);
	isave->Write(&type,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(SCHG_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->EndChunk();
	return IO_OK;
	}

IOResult SegChangeRecord::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 SCHG_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&segment,sizeof(int),&nb);
				res = iload->Read(&type,sizeof(int),&nb);
				break;
			case SCHG_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

PolyChangeRecord::PolyChangeRecord(BezierShape *shape, int poly, int type) : ModRecord() {
	oldSpline = *shape->splines[poly];
	this->poly = poly;
	this->type = type;
	}

BOOL PolyChangeRecord::Undo(BezierShape *shape) {
	if(poly >= shape->splineCount)
		return FALSE;
	*shape->splines[poly] = oldSpline;
	return TRUE;
	}

static void ChangePolyType(BezierShape *shape, int poly, int type) {
	Spline3D *spline = shape->splines[poly];
	int i;
	int knots = spline->KnotCount();
	int segments = spline->Segments();
	switch(type) {
		case LTYPE_CURVE:
			for(i = 0; i < knots; ++i)
				spline->SetKnotType(i, KTYPE_AUTO);
			spline->ComputeBezPoints();
			for(i = 0; i < knots; ++i)
				spline->SetKnotType(i, KTYPE_BEZIER);
			break;
		case LTYPE_LINE:
			for(int i = 0; i < knots; ++i)
				spline->SetKnotType(i, KTYPE_CORNER);
			break;
		}
	for(i = 0; i < segments; ++i)
		spline->SetLineType(i, type);
	spline->ComputeBezPoints();
	shape->InvalidateGeomCache();
	}

BOOL PolyChangeRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline = *shape->splines[poly];
		}
	ChangePolyType(shape, poly, type);
	return TRUE;
	}

#define PCHG_GENERAL_CHUNK		0x1001
#define PCHG_SPLINE_CHUNK		0x1010

IOResult PolyChangeRecord::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(&poly,sizeof(int),&nb);
	isave->Write(&type,sizeof(int),&nb);
	isave->EndChunk();
	isave->BeginChunk(PCHG_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->EndChunk();
	return IO_OK;
	}

IOResult PolyChangeRecord::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(&poly,sizeof(int),&nb);
				res = iload->Read(&type,sizeof(int),&nb);
				break;
			case PCHG_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

VertDeleteRecord::VertDeleteRecord(BezierShape *shape, int poly) : ModRecord() {
	this->poly = poly;
	oldSpline = *(shape->splines[poly]);
	oldVSel = shape->vertSel[poly];
	oldSSel = shape->segSel[poly];
	selected = shape->polySel[poly];
	deleted = FALSE;
	oldSplineCount = shape->splineCount;
	}

BOOL VertDeleteRecord::Undo(BezierShape *shape) {
	// Get rid of all the new polys we created...
	while(shape->splineCount > oldSplineCount)
		shape->DeleteSpline(shape->splineCount - 1);
	// Get rid of the one we changed (if it's not deleted already!)
	if(poly >= shape->splineCount)
		return FALSE;
	if(!deleted)
		shape->DeleteSpline(poly);
	// Put back the original poly we changed...
	shape->InsertSpline(&oldSpline,poly);
	// Restore the selection information
	shape->vertSel[poly] = oldVSel;
	shape->segSel[poly] = oldSSel;
	shape->polySel.Set(poly,selected);
	return TRUE;
	}

// Delete all selected vertices
// Returns TRUE if the operation results in the polygon being deleted
int DeleteSelVerts(BezierShape *shape, int poly) {
	Spline3D *spline = shape->splines[poly];
	int segs = spline->Segments();
	int knots = spline->KnotCount();

	// If less than two vertices would remain, blow it away!
	if((knots - shape->vertSel[poly].NumberSet()) < 2) {
		shape->DeleteSpline(poly);
		return TRUE;				// It's TRUE -- We deleted the spline!
		}

	int altered = 0;
	BitArray& vsel = shape->vertSel[poly];
	BitArray& ssel = shape->segSel[poly];

	for(int k = knots-1; k >= 0; --k) {
		if(vsel[k*3+1]) {
			altered = 1;
			spline->DeleteKnot(k);
			}
		}

	if(altered) {
		vsel.SetSize(spline->Verts(),1);
		vsel.ClearAll();
		ssel.SetSize(spline->Segments(),1);
		ssel.ClearAll();
		spline->ComputeBezPoints();
		shape->InvalidateGeomCache();
		}
	return FALSE;	// The poly's still there
	}

BOOL VertDeleteRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	if(reRecord) {
		oldSpline = *(shape->splines[poly]);
		oldVSel = shape->vertSel[poly];
		oldSSel = shape->segSel[poly];
		selected = shape->polySel[poly];
		}
	deleted = DeleteSelVerts(shape, poly);
	return TRUE;
	}

#define VDELR_GENERAL_CHUNK		0x1000
#define VDELR_OLDVSEL_CHUNK		0x1040
#define VDELR_OLDSSEL_CHUNK		0x1050
#define VDELR_SPLINE_CHUNK		0x1060

IOResult VertDeleteRecord::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_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&selected,sizeof(int),&nb);
	isave->Write(&deleted,sizeof(int),&nb);
	isave->Write(&oldSplineCount,sizeof(int),&nb);
	isave->	EndChunk();
	isave->BeginChunk(VDELR_OLDVSEL_CHUNK);
	oldVSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VDELR_OLDSSEL_CHUNK);
	oldSSel.Save(isave);
	isave->	EndChunk();
	isave->BeginChunk(VDELR_SPLINE_CHUNK);
	oldSpline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult VertDeleteRecord::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_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&selected,sizeof(int),&nb);
				res = iload->Read(&deleted,sizeof(int),&nb);
				res = iload->Read(&oldSplineCount,sizeof(int),&nb);
				break;
			case VDELR_OLDVSEL_CHUNK:
				res = oldVSel.Load(iload);
				break;
			case VDELR_OLDSSEL_CHUNK:
				res = oldSSel.Load(iload);
				break;
			case VDELR_SPLINE_CHUNK:
				res = oldSpline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

CreateLineRecord::CreateLineRecord(Spline3D *s) : ModRecord() {
	spline = *s;
	}

BOOL CreateLineRecord::Undo(BezierShape *shape) {
	if(shape->splineCount == 0)
		return FALSE;
	shape->DeleteSpline(shape->splineCount-1);
	return TRUE;
	}

BOOL CreateLineRecord::Redo(BezierShape *shape,int reRecord) {
	shape->InsertSpline(&spline,shape->splineCount);
	shape->UpdateSels();
	return TRUE;
	}

#define CRELR_SPLINE_CHUNK		0x1000

IOResult CreateLineRecord::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(CRELR_SPLINE_CHUNK);
	spline.Save(isave);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult CreateLineRecord::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 CRELR_SPLINE_CHUNK:
				res = spline.Load(iload);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

static void CopySpline(BezierShape *shape, int poly, BOOL selOriginal, BOOL selCopy) {
	Spline3D *spline = shape->NewSpline();
	*spline = *(shape->splines[poly]);
	shape->UpdateSels();	// Make sure it readies the selection set info
	shape->InvalidateGeomCache();
	shape->polySel.Set(poly, selOriginal);
	shape->polySel.SetSize(*shape);		// Expand the selection array
	shape->polySel.Set(shape->splineCount - 1, selCopy);
	}

PolyCopyRecord::PolyCopyRecord(BezierShape *shape, int poly, BOOL selOrig, BOOL selCopy) : ModRecord() {
	this->poly = poly;
	this->selOrig = selOrig;
	this->selCopy = selCopy;
	}

BOOL PolyCopyRecord::Undo(BezierShape *shape) {
	if(shape->splineCount == 0)
		return FALSE;
	shape->DeleteSpline(shape->splineCount - 1);
	if(poly >= shape->splineCount)
		return FALSE;
	shape->polySel.Set(poly, TRUE);	// Select the old polygon
	shape->UpdateSels();	// Make sure it readies the selection set info
	shape->InvalidateGeomCache();
	return TRUE;
	}

BOOL PolyCopyRecord::Redo(BezierShape *shape,int reRecord) {
	if(poly >= shape->splineCount)
		return FALSE;
	CopySpline(shape, poly, selOrig, selCopy);
	return TRUE;
	}

#define PCOPYR_GENERAL_CHUNK		0x1000

IOResult PolyCopyRecord::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(PCOPYR_GENERAL_CHUNK);
	isave->Write(&poly,sizeof(int),&nb);
	isave->Write(&selOrig,sizeof(BOOL),&nb);
	isave->Write(&selCopy,sizeof(BOOL),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult PolyCopyRecord::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 PCOPYR_GENERAL_CHUNK:
				res = iload->Read(&poly,sizeof(int),&nb);
				res = iload->Read(&selOrig,sizeof(BOOL),&nb);
				res = iload->Read(&selCopy,sizeof(BOOL),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

static void CopySegments(BezierShape *shape, BOOL selCopy) {
	int oldSplineCount = shape->SplineCount();
	for(int poly = 0; poly < oldSplineCount; ++poly) {
		if(shape->segSel[poly].NumberSet()) {
			Spline3D *spline = shape->splines[poly];
			int segments = spline->Segments();
			int knots = spline->KnotCount();
			// If all segments selected, copy the whole polygon!
			if(shape->segSel[poly].NumberSet() == segments) {
				Spline3D *newSpline = shape->NewSpline();
				*newSpline = *spline;
				}
			else {
				int end = segments;
				for(int seg = 0; seg < end; ++seg) {
					if(shape->segSel[poly][seg]) {
						Spline3D *newSpline = shape->NewSpline();
						if(seg == 0 && spline->Closed()) {
							backLoop:
							if(shape->segSel[poly][--end]) {
								SplineKnot addKnot(spline->GetKnotType(end),spline->GetLineType(end),
									spline->GetKnotPoint(end),spline->GetInVec(end),spline->GetOutVec(end));
								newSpline->AddKnot(addKnot, 0);
								goto backLoop;
								}

							}
						SplineKnot addKnot(spline->GetKnotType(seg),spline->GetLineType(seg),
							spline->GetKnotPoint(seg),spline->GetInVec(seg),spline->GetOutVec(seg));
						newSpline->AddKnot(addKnot, -1);

						loop:
						int knot = (seg + 1) % knots;
						addKnot = SplineKnot(spline->GetKnotType(knot),spline->GetLineType(knot),
							spline->GetKnotPoint(knot),spline->GetInVec(knot),spline->GetOutVec(knot));
						newSpline->AddKnot(addKnot, -1);
						seg = (seg + 1) % segments;
						if(seg > 0 && seg < end && shape->segSel[poly][seg])
							goto loop;

						// Finish up the spline!
						newSpline->ComputeBezPoints();

						// Special termination test for wraparound
						if(seg == 0)
							seg = end;
						}
					}
				}
			}
		}
	shape->segSel.ClearAll();	// Deselect the originals
	shape->UpdateSels();	// Make sure it readies the selection set info
	shape->InvalidateGeomCache();
	for(int i = oldSplineCount; i < shape->SplineCount(); ++i) {
		if(selCopy)
			shape->segSel[i].SetAll();
		else
			shape->segSel[i].ClearAll();
		}
	}

SegCopyRecord::SegCopyRecord(BezierShape *shape, BOOL selCopy) : ModRecord() {
	this->selCopy = selCopy;
	oldSplineCount = shape->splineCount;
	oldSSel = shape->segSel;
	}

BOOL SegCopyRecord::Undo(BezierShape *shape) {
	while(shape->splineCount > oldSplineCount)
		shape->DeleteSpline(shape->splineCount - 1);
	shape->UpdateSels();	// Make sure it readies the selection set info
	shape->InvalidateGeomCache();
	if(!oldSSel.IsCompatible(*shape))
		return FALSE;
	shape->segSel = oldSSel;	// Select the old segments
	shape->InvalidateGeomCache();
	return TRUE;
	}

BOOL SegCopyRecord::Redo(BezierShape *shape,int reRecord) {
	if(reRecord) {
		oldSplineCount = shape->splineCount;
		oldSSel = shape->segSel;
		}
	CopySegments(shape, selCopy);
	return TRUE;
	}

#define SCOPYR_GENERAL_CHUNK		0x1000

IOResult SegCopyRecord::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(SCOPYR_GENERAL_CHUNK);
	isave->Write(&selCopy,sizeof(BOOL),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult SegCopyRecord::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 SCOPYR_GENERAL_CHUNK:
				res = iload->Read(&selCopy,sizeof(BOOL),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

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

ShapeRestore::ShapeRestore(EditSplineData* sd, EditSplineMod* mod, ModRecord *rec)
	{
	shapeData = sd;
	this->mod = mod;
	this->rec = rec;
	t = mod->iObjParams->GetTime();
	rec->SetRestore(this);
	iOwn = FALSE;			// Originally, this is owned by the modifier
	}

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

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

	BOOL ok = TRUE;

	if ( shapeData->tempData && shapeData->TempData(mod)->ShapeCached(t) ) {
		BezierShape *shape = shapeData->TempData(mod)->GetShape(t);
		if(shape)
			ok = rec->Undo(shape);
		shape->InvalidateGeomCache();
		shapeData->TempData(mod)->Invalidate(rec->Parts());
		Cancel2StepShapeModes(mod->iObjParams);
		}
	else
	if ( shapeData->tempData ) {
		shapeData->TempData(mod)->Invalidate(rec->Parts(),FALSE);
		}
	if(isUndo) {
		iOwn = TRUE;
		shapeData->RemoveChangeRecord();
		if(!ok) {
			rec->ClearRestore();
			delete rec;
			rec = NULL;
			}
		}
	mod->UpdatePolyVertCount();
	if(rec)
		mod->NotifyDependents(FOREVER, rec->Parts(), REFMSG_CHANGE);
	}

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

	BOOL ok = TRUE;
	if ( shapeData->tempData && shapeData->TempData(mod)->ShapeCached(t) ) {
		BezierShape *shape = shapeData->TempData(mod)->GetShape(t);
		if(shape)
			ok = rec->Redo(shape,0);
		shape->InvalidateGeomCache();
		shapeData->TempData(mod)->Invalidate(rec->Parts());
		Cancel2StepShapeModes(mod->iObjParams);
		}
	else
	if ( shapeData->tempData ) {
		shapeData->TempData(mod)->Invalidate(rec->Parts(),FALSE);
		}
	if(iOwn) {
		iOwn = FALSE;
		if(ok)
			shapeData->AdoptChangeRecord(rec);
		else {
			rec->ClearRestore();
			delete rec;
			rec = NULL;
			}
		}
	mod->UpdatePolyVertCount();
	if(rec)
		mod->NotifyDependents(FOREVER, rec->Parts(), REFMSG_CHANGE);
	}

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

void OutlineCMode::EnterMode()
	{
	if ( es->hEditSplineParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_OUTLINE));
		but->SetCheck(TRUE);
		ReleaseICustButton(but);
		}
	}

void OutlineCMode::ExitMode()
	{
	if ( es->hEditSplineParams ) {
		es->EndOutlineMove(es->iObjParams->GetTime(),TRUE);
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_OUTLINE));
		but->SetCheck(FALSE);
		ReleaseICustButton(but);
		}
	}

void EditSplineMod::StartOutlineMode()
	{
	if ( !iObjParams ) return;

	iObjParams->SetCommandMode(outlineMode);
	}

int OutlineMouseProc::proc( 
		HWND hwnd, 
		int msg, 
		int point, 
		int flags, 
		IPoint2 m )
	{	
	ViewExp *vpt = ip->GetViewport(hwnd);
	switch ( msg ) {
		case MOUSE_PROPCLICK:
			ip->SetStdCommandMode(CID_OBJMOVE);
			break;

		case MOUSE_POINT:						
			if ( point == 0 ) {							
				es->BeginOutlineMove(ip->GetTime());				
				p0 = vpt->SnapPoint(m,m,NULL,SNAP_IN_3D);
				sp0 = m;
			} else {
				float outSize = es->outlineSpin->GetFVal();
				es->EndOutlineMove(ip->GetTime(), (outSize == 0.0f) ? FALSE : TRUE);
				ip->RedrawViews(ip->GetTime(),REDRAW_END);
				}
			break;

		case MOUSE_MOVE: {
			float size = vpt->SnapLength(vpt->GetCPDisp(p0,Point3(0,0,1),sp0,m));
			es->OutlineMove( ip->GetTime(), size);
			es->outlineSpin->SetValue(size, FALSE);
			
			ip->RedrawViews(ip->GetTime(),REDRAW_INTERACTIVE);
			break;
			}

		case MOUSE_ABORT:
			es->EndOutlineMove(ip->GetTime(),FALSE);			
			ip->RedrawViews(ip->GetTime(),REDRAW_END);
			break;		
		}
	
	if ( vpt ) ip->ReleaseViewport(vpt);
	return TRUE;
	}

void EditSplineMod::BeginOutlineMove(TimeValue t)
	{
	EndOutlineMove(t);
	theHold.Begin();
	inOutline = TRUE;
	}

void EditSplineMod::EndOutlineMove(TimeValue t,BOOL accept)
	{	
	if ( !inOutline ) return;
	if ( !iObjParams ) return;	
	
	if (accept) {
		theHold.Accept(GetString(IDS_TH_OUTLINE));
	} else {
		theHold.Cancel();
		}
		
	inOutline = FALSE;
	if ( outlineSpin ) {
		outlineSpin->SetValue(0,FALSE);
		}
	}

void EditSplineMod::OutlineMove( TimeValue t, float amount ) 
	{	
	ModContextList mcList;		
	INodeTab nodes;

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

	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) continue;
				
		if ( theHold.Holding() && !TestAFlag(A_HELD) )
			shapeData->outRecs.Delete(0, shapeData->outRecs.Count());

		shapeData->BeginEdit(t);
		int polys = shape->splineCount;
		for(int poly = 0; poly < polys; ++poly) {				
			Spline3D *spline = shape->splines[poly];

			if(shape->polySel[poly]) {
				if ( theHold.Holding() && !TestAFlag(A_HELD) ) {
					OutlineRecord *rec = new OutlineRecord(shape, poly, centeredOutline);
					shapeData->outRecs.Append(1, &rec);
					shapeData->StartChangeGroup();
					shapeData->AddChangeRecord(rec);
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}

				// Outline the polygon
				OutlineSpline(shape, poly, amount, centeredOutline, TRUE);
				}
			}
		shapeData->TempData(this)->Invalidate(PART_GEOM);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}

	// Update the outline records
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	for ( i = 0; i < mcList.Count(); i++ ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;
		for(int j = 0; j < shapeData->outRecs.Count(); ++j)
			shapeData->outRecs[j]->SetOutlineSize(amount);
		}

	// Mark all objects in selection set
	SetAFlag(A_HELD);
	
	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	NotifyDependents(FOREVER, PART_GEOM, REFMSG_CHANGE);
	}

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

void SegBreakCMode::EnterMode()
	{
	if ( es->hEditSplineParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_SEGBREAK));
		but->SetCheck(TRUE);
		ReleaseICustButton(but);
		}
	}

void SegBreakCMode::ExitMode()
	{
	if ( es->hEditSplineParams ) {
//		es->EndOutlineMove(es->iObjParams->GetTime(),TRUE);
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_SEGBREAK));
		but->SetCheck(FALSE);
		ReleaseICustButton(but);
		}
	}

void EditSplineMod::StartSegBreakMode()
	{
	if ( !iObjParams ) return;

	iObjParams->SetCommandMode(segBreakMode);
	}

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

void CreateLineCMode::EnterMode()
	{
	if ( es->hEditSplineParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_CREATELINE));
		but->SetCheck(TRUE);
		ReleaseICustButton(but);
		}
	}

void CreateLineCMode::ExitMode()
	{
	if ( es->hEditSplineParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_CREATELINE));
		but->SetCheck(FALSE);
		ReleaseICustButton(but);
		}
	}

void EditSplineMod::StartCreateLineMode()
	{
	if ( !iObjParams ) return;

	iObjParams->SetCommandMode(createLineMode);
	}

BOOL EditSplineMod::StartCreateLine(BezierShape **shape) {
	ModContextList mcList;		
	INodeTab nodes;
	if ( !iObjParams ) return FALSE;

	iObjParams->GetModContexts(mcList,nodes);
	if(mcList.Count() != 1) {
		nodes.DisposeTemporary();
		return FALSE;
		}
	createShapeData = (EditSplineData*)mcList[0]->localData;
	if ( !createShapeData ) {
		nodes.DisposeTemporary();
		return FALSE;
		}

	// If the mesh isn't yet cache, this will cause it to get cached.
	createShape = createShapeData->TempData(this)->GetShape(iObjParams->GetTime());
	createNode = nodes[0]->GetActualINode();	
	createTM = nodes[0]->GetObjectTM(iObjParams->GetTime());
	*shape = createShape;
	nodes.DisposeTemporary();
	return TRUE;
	}

void EditSplineMod::EndCreateLine() {
	ModContextList mcList;		
	INodeTab nodes;
	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	EditSplineData *shapeData = (EditSplineData*)mcList[0]->localData;
	if ( !shapeData ) {
		nodes.DisposeTemporary();
		return;
		}

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

	theHold.Begin();
	shapeData->StartChangeGroup();

	// If the mesh isn't yet cache, this will cause it to get cached.
	BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
	if(!shape) {
		theHold.Cancel();
		nodes.DisposeTemporary();
		return;
		}
	CreateLineRecord *createRec = new CreateLineRecord(shape->splines[shape->splineCount-1]);
	shapeData->AddChangeRecord(createRec);
	if ( theHold.Holding() ) {
		theHold.Put(new ShapeRestore(shapeData,this,createRec));
		}
	theHold.Accept(GetString(IDS_TH_CREATELINE));
	nodes.DisposeTemporary();
	}

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

void SegRefineCMode::EnterMode()
	{
	if ( es->hEditSplineParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,(type==REFINE_VERT) ? IDC_VERTREFINE : IDC_REFINE));
		but->SetCheck(TRUE);
		ReleaseICustButton(but);
		}
	}

void SegRefineCMode::ExitMode()
	{
	if ( es->hEditSplineParams ) {
//		es->EndOutlineMove(es->iObjParams->GetTime(),TRUE);
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,(type==REFINE_VERT) ? IDC_VERTREFINE : IDC_REFINE));
		but->SetCheck(FALSE);
		ReleaseICustButton(but);
		}
	}

void EditSplineMod::StartSegRefineMode(int type)
	{
	if ( !iObjParams ) return;

	segRefineMode->SetType(type);
	iObjParams->SetCommandMode(segRefineMode);
	}

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

void VertConnectCMode::EnterMode()
	{
	if ( es->hEditSplineParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_CONNECT));
		but->SetCheck(TRUE);
		ReleaseICustButton(but);
		}
	}

void VertConnectCMode::ExitMode()
	{
	if ( es->hEditSplineParams ) {
//		es->EndOutlineMove(es->iObjParams->GetTime(),TRUE);
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_CONNECT));
		but->SetCheck(FALSE);
		ReleaseICustButton(but);
		}
	}

void EditSplineMod::StartVertConnectMode()
	{
	if ( !iObjParams ) return;

	iObjParams->SetCommandMode(vertConnectMode);
	}

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

void VertInsertCMode::EnterMode()
	{
	if ( es->hEditSplineParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_INSERT));
		but->SetCheck(TRUE);
		ReleaseICustButton(but);
		}
	}

void VertInsertCMode::ExitMode()
	{
	if ( es->hEditSplineParams ) {
//		es->EndOutlineMove(es->iObjParams->GetTime(),TRUE);
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_INSERT));
		but->SetCheck(FALSE);
		ReleaseICustButton(but);
		}
	}

void EditSplineMod::StartVertInsertMode()
	{
	if ( !iObjParams ) return;

	iObjParams->SetCommandMode(vertInsertMode);
	}

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

void BooleanCMode::EnterMode()
	{
	if ( es->hEditSplineParams ) {
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_BOOLEAN));
		but->SetCheck(TRUE);
		ReleaseICustButton(but);
		}
	}

void BooleanCMode::ExitMode()
	{
	if ( es->hEditSplineParams ) {
//		es->EndOutlineMove(es->iObjParams->GetTime(),TRUE);
		ICustButton *but = GetICustButton(GetDlgItem(es->hEditSplineParams,IDC_BOOLEAN));
		but->SetCheck(FALSE);
		ReleaseICustButton(but);
		}
	}

void EditSplineMod::StartBooleanMode()
	{
	if ( !iObjParams ) return;

	iObjParams->SetCommandMode(booleanMode);
	}

// See if a spline self-intersects
static BOOL SplineSelfIntersects(Spline3D *spline) {
	// Create a 2D template of the spline
	Template t(spline);
	int points = t.Points();
	int last = points - 1;
	int last2 = points - 2;
	Point2 where;
	for(int i = 0; i < last2; ++i) {
		Point2 i1 = t.pts[i];
		Point2 i2 = t.pts[i+1];
		for(int j = i + 2; j < last; ++j) {
			if(i==0 && j == last2) continue;	// No comparison with last seg & first!
			if(IntSeg(i1,i2,t.pts[j],t.pts[j+1],where) == 1) {
//DebugPrint("Self-int %d/%d: %.4f %.4f - %.4f %.4f / %.4f %.4f - %.4f %.4f @ %.4f %.4f\n",i,j,i1.x,i1.y,i2.x,i2.y,
//	t.pts[j].x,t.pts[j].y,t.pts[j+1].x,t.pts[j+1].y,where.x,where.y);
				return TRUE;
				}
			}
		}
	return FALSE;
	}

BOOL ValidBooleanPolygon(IObjParam *ip, Spline3D *spline) {
	if(!spline->Closed()) {
		ip->DisplayTempPrompt(GetString(IDS_TH_SELECTCLOSEDSPLINE),PROMPT_TIME);
		return FALSE;
		}
	if(SplineSelfIntersects(spline)) {
		ip->DisplayTempPrompt(GetString(IDS_TH_SPLINESELFINTERSECTS),PROMPT_TIME);
		return FALSE;
		}
	return TRUE;	// It's OK!
	}

// This function checks to see if there is only one polygon selected, and if so, starts up
// the second phase of the boolean operation

BOOL EditSplineMod::BooleanStartUp() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();

	if ( !iObjParams ) return FALSE;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	int selected = 0;
	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;

		// If the mesh isn't yet cache, this will cause it to get cached.
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) continue;
			
		int polys = shape->splineCount;
		for(int poly = 0; poly < polys; ++poly) {
			if(shape->polySel[poly]) {
				if(selected) {
					iObjParams->DisplayTempPrompt(GetString(IDS_TH_MORETHANONESPLINESEL),PROMPT_TIME);
					return FALSE;
					}
				selected = 1;
				boolShape = shape;
				boolPoly1 = poly;
				}
			}
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}

	nodes.DisposeTemporary();

	// If no polys selected -- No can do!
	if(!selected) {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_SELECTONESPLINE),PROMPT_TIME);
		return FALSE;
		}

	// Got one poly selected, make sure it's valid!
	if(!ValidBooleanPolygon(iObjParams, boolShape->splines[boolPoly1]))
		return FALSE;

	// It's kosher, start up the boolean mode!
	StartBooleanMode();

	return TRUE;
	}

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

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

	return FALSE;
	}

BOOL PickSplineAttach::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!
		es->iObjParams->GetModContexts(mcList,nodes);
		int numNodes = nodes.Count();
		for(int i = 0; i < numNodes; ++i) {
			if(nodes[i] == node) {
				nodes.DisposeTemporary();
				return FALSE;
				}
			}
		nodes.DisposeTemporary();
		if(object->CanConvertToType(splineShapeClassID))
			return TRUE;
		}

	return FALSE;
	}

BOOL PickSplineAttach::Pick(IObjParam *ip,ViewExp *vpt)
	{
	INode *node = vpt->GetClosestHit();
	assert(node);
	GeomObject *object = (GeomObject *)node->GetObjectRef()->Eval(ip->GetTime()).obj;
	if(object->CanConvertToType(splineShapeClassID)) {
		SplineShape *attSplShape = (SplineShape *)object->ConvertToType(ip->GetTime(),splineShapeClassID);
		if(attSplShape) {
			BezierShape shape = attSplShape->shape;
//			Point3 offset = node->GetObjOffsetPos();
//			Matrix3 tm(1);
//			tm.Translate(offset);
//			shape.Transform(tm);
			es->DoAttach(node, &shape);
			// Discard the copy it made, if it isn't the same as the object itself
			if(attSplShape != (SplineShape *)object)
				delete attSplShape;
			}
		}
	return FALSE;
	}


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

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

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

int EditSplineMod::DoAttach(INode *node, BezierShape *attShape) {
	ModContextList mcList;	
	INodeTab nodes;

	if ( !iObjParams ) return 0;

	iObjParams->GetModContexts(mcList,nodes);

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

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

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

	// 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()));
		}
	attShape->Transform(attMat);

	theHold.Begin();
	shapeData->StartChangeGroup();
	AttachRecord *rec = new AttachRecord(shape, attShape);
	shapeData->AddChangeRecord(rec);
	// Do the attach
	::DoAttach(shape, attShape);
	// Start a restore object...
	if ( theHold.Holding() ) {
		theHold.Put(new ShapeRestore(shapeData,this,rec));
		}
	shapeData->TempData(this)->Invalidate(PART_TOPO|PART_GEOM);

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

	theHold.Accept(GetString(IDS_TH_ATTACH));

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

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

EditSplineMod::EditSplineMod()
	{
	selLevel = ES_VERTEX;
	insertShape = NULL;	
	}

EditSplineMod::~EditSplineMod()
	{
	}

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

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



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


void EditSplineMod::XFormHandles( 
		XFormProc *xproc, 
		TimeValue t, 
		Matrix3& partm, 
		Matrix3& tmAxis,
		int masterObject)
	{	
	ModContextList mcList;		
	INodeTab nodes;
	Matrix3 mat,imat,theMatrix;
	Interval valid;
	int numAxis;
	int masterKnot;
	Point3 oldpt,newpt,oldin,oldout,rel;
	BOOL shiftPressed = FALSE;
	static BOOL wasBroken;
	Point3 theKnot;
	Point3 oldVector;
	Point3 newVector;
	BOOL isInVec;
	float oldLen;
	float newLen;
	float lengthRatio;

	if(lockType != IDC_LOCKALL)
		shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) ? TRUE : FALSE;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	numAxis = iObjParams->GetNumAxis();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	// Loop thru the objects, doing the master object first
	// If handles aren't locked, we only need to do the master object!
	int objects = lockedHandles ? mcList.Count() : 1;
	for ( int i = 0, object = masterObject; i < objects; i++, object = (object + 1) % objects ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[object]->localData;
		if ( !shapeData ) {
			nodes.DisposeTemporary();
			return;
			}
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) continue;
		
		// If this is the first edit, then the delta arrays will be allocated
		shapeData->BeginEdit(t);

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

		// If the master shape, move the handle!
		if(object == masterObject) {
			masterKnot = shape->bezVecVert / 3;
			int poly = shape->bezVecPoly;
			int vert = shape->bezVecVert;
			shapeData->SetHandleFlag(poly, vert);
			Spline3D *spline = shape->splines[poly];
			int primaryKnot = vert / 3;
			isInVec = ((vert % 3) == 0) ? TRUE : FALSE;
			int otherVert = isInVec ? vert + 2 : vert - 2;
			theKnot = spline->GetKnotPoint(primaryKnot);
			Point3Tab &pDeltas = shapeData->vdelta->dtab.ptab[poly];

			int vbase = shape->GetVertIndex(poly, primaryKnot * 3 + 1);
			tmAxis = iObjParams->GetTransformAxis(nodes[object],vbase);
			mat    = nodes[object]->GetObjectTM(t,&valid) * Inverse(tmAxis);
			imat   = Inverse(mat);
			xproc->SetMat(mat);
					
			// XForm the cache vertices
			oldpt = spline->GetVert(vert);
			newpt = xproc->proc(oldpt,mat,imat);
			spline->SetVert(vert,newpt);

			// Move the delta's vertices.
			shapeData->vdelta->SetPoint(poly,vert,newpt - oldpt);

			// If locked handles, turn the movement into a transformation matrix
			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(CrossProd(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 && spline->GetKnotType(primaryKnot) == KTYPE_BEZIER) {
							spline->SetKnotType(primaryKnot,KTYPE_BEZIER_CORNER);
							shapeData->vdelta->SetKType(poly,primaryKnot,KTYPE_BEZIER ^ KTYPE_BEZIER_CORNER);
							}
						}
					}
				}
			else {
				// If unlocked and the bezier is non-corner, do its partner on the other side of the knot!
				if(spline->GetKnotType(primaryKnot) == KTYPE_BEZIER) {
					if(shiftPressed) {
						wasBroken = TRUE;
						spline->SetKnotType(primaryKnot,KTYPE_BEZIER_CORNER);
						// Need to record this for undo!
						shapeData->vdelta->SetKType(poly,primaryKnot,KTYPE_BEZIER ^ KTYPE_BEZIER_CORNER);
						}
					// If a bezier smooth knot, do the opposite side!
					if(spline->GetKnotType(primaryKnot) == KTYPE_BEZIER) {
						Point3 oldpt2 = spline->GetVert(otherVert) - pDeltas[otherVert];
						float oldLen2 = Length(theKnot - oldpt2);
						float oldLen1 = Length(theKnot - oldpt);
						if(oldLen1!=0.0f && oldLen2!=0.0f) {
							float ratio = oldLen2 / oldLen1;
							Point3 newpt2 = theKnot - (newpt - theKnot) * ratio;
							// Alter the cache
							spline->SetVert(otherVert,newpt2);
							// Move the delta's vertices.
							shapeData->vdelta->SetPoint(poly,otherVert,newpt2-oldpt2);
							}
						}
					}
				}

			// Really only need to do this if neighbor knots are non-bezier
			spline->ComputeBezPoints();
			shape->InvalidateGeomCache();
			}

		// If doing locked handles, process all of the handles of selected verts!
		if(lockedHandles) {
			int count = 0;
			if(object!=masterObject)
				shapeData->SetHandleFlag(-1, -1);
			for(int poly = 0; poly < shape->splineCount; ++poly) {
				shapeData->vdelta->ClearUsed(poly);
				// Selected vertices - either directly or indirectly through selected faces or edges.
				BitArray sel = shape->VertexTempSel(poly);
				Spline3D *spline = shape->splines[poly];
				int knots = spline->KnotCount();
				Point3Tab &pDeltas = shapeData->vdelta->dtab.ptab[poly];
				for ( int k = 0; k < knots; k++ ) {
					int kvert = k*3+1;
					if(object == masterObject && poly == shape->bezVecPoly && kvert == shape->bezVecVert) {
						shapeData->vdelta->SetUsed(poly);
						continue;
						}
					if ( spline->IsBezierPt(k) && sel[kvert]) {
						shapeData->vdelta->SetUsed(poly);
						theKnot = spline->GetKnotPoint(k);
						int knotType = spline->GetKnotType(k);
						
						if(shiftPressed && knotType == KTYPE_BEZIER) {
							spline->SetKnotType(k,KTYPE_BEZIER_CORNER);
							shapeData->vdelta->SetKType(poly,k,KTYPE_BEZIER ^ KTYPE_BEZIER_CORNER);
							}

						if(isInVec || lockType == IDC_LOCKALL || (!shiftPressed && knotType == KTYPE_BEZIER)) {
							// In vector
							int vert = kvert - 1;
							// XForm the cache vertices
							oldpt = spline->GetVert(vert) - pDeltas[vert];
							oldVector = oldpt - theKnot;
							oldLen = Length(oldVector);
							// If the old vector existed, transform it!
							if(oldLen != 0.0f) {
								Point3 newpt = theKnot + ((oldVector * lengthRatio) * theMatrix);
								// Alter the cache
								spline->SetVert(vert,newpt);
								// Move the delta's vertices.
								shapeData->vdelta->SetPoint(poly,vert,newpt-oldpt);
								}
							}

						if(!isInVec || lockType == IDC_LOCKALL || (!shiftPressed && knotType == KTYPE_BEZIER)) {
							// Out vector
							int vert = kvert + 1;
							// XForm the cache vertices
							oldpt = spline->GetVert(vert) - pDeltas[vert];
							oldVector = oldpt - theKnot;
							oldLen = Length(oldVector);
							// If the old vector existed, transform it!
							if(oldLen != 0.0f) {
								Point3 newpt = theKnot + ((oldVector * lengthRatio) * theMatrix);
								// Alter the cache
								spline->SetVert(vert,newpt);
								// Move the delta's vertices.
								shapeData->vdelta->SetPoint(poly,vert,newpt-oldpt);
								}
							}
						}
					}
				}
			}

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

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	numAxis = iObjParams->GetNumAxis();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	if(!TestAFlag(A_HELD)) {
		handleEdit = FALSE;

		// Check all shapes to see if they are altering a bezier vector handle...
		if(selLevel == ES_VERTEX) {
			for ( int i = 0; i < mcList.Count(); i++ ) {
				EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
				if ( !shapeData ) continue;
				if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;
		
				// If the mesh isn't yet cache, this will cause it to get cached.
				BezierShape *shape = shapeData->TempData(this)->GetShape(t);
				if(!shape) continue;
				if(!iObjParams->SelectionFrozen() && shape->bezVecPoly >= 0) {
					// Editing a bezier handle -- Go do it!
					handleEdit = TRUE;
					handleObject = i;
					goto edit_handles;
					}
	 			shapeData->SetFlag(ESD_BEENDONE,TRUE);
				}
			}
		}
	
	// If editing the handles, cut to the chase!
	if(handleEdit) {
		edit_handles:
		XFormHandles(xproc, t, partm, tmAxis, handleObject);
		nodes.DisposeTemporary();
		return;
		}

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

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

		// Compute the transforms
		if (numAxis==NUMAXIS_INDIVIDUAL) {
			switch(selLevel) {
				case ES_VERTEX:
				case ES_SEGMENT: {
					int vbase = 1;
					for(int poly = 0; poly < shape->splineCount; ++poly) {
						shapeData->vdelta->ClearUsed(poly);
						// Selected vertices - either directly or indirectly through selected faces or edges.
						BitArray sel = shape->VertexTempSel(poly);
						Spline3D *spline = shape->splines[poly];
						int knots = spline->KnotCount();
						for ( int k = 0; k < knots; k++,vbase+=3 ) {
							int vert = k*3+1;
							if ( sel[vert] ) {
								shapeData->vdelta->SetUsed(poly);
								tmAxis = iObjParams->GetTransformAxis(nodes[i],vbase);
								mat    = nodes[i]->GetObjectTM(t,&valid) * Inverse(tmAxis);
								imat   = Inverse(mat);
								xproc->SetMat(mat);
					
								// XForm the cache vertices
								oldpt = spline->GetVert(vert);
								newpt = xproc->proc(oldpt,mat,imat);
								spline->SetVert(vert,newpt);
								delta = newpt - oldpt;

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

								// If it's a bezier knot, also affect its vectors
								if(spline->IsBezierPt(k)) {
									int in = vert - 1;
									int out = vert + 1;

									// XForm the cache vertices
									oldin = spline->GetVert(in);
									spline->SetVert(in,xproc->proc(oldin,mat,imat));
									delta = spline->GetVert(in) - oldin;

									// Move the delta's vertices.
									shapeData->vdelta->Move(poly,in,delta);

									// XForm the cache vertices
									oldout = spline->GetVert(out);
									spline->SetVert(out,xproc->proc(oldout,mat,imat));

									// Move the delta's vertices.
									shapeData->vdelta->Move(poly,out,spline->GetVert(out) - oldout);
									}
								}
							}
						if(shapeData->vdelta->IsUsed(poly)) {
							spline->ComputeBezPoints();
							shape->InvalidateGeomCache();
							}
						}
					}
					break;
				case ES_SPLINE: {
					for(int poly = 0; poly < shape->splineCount; ++poly) {
						shapeData->vdelta->ClearUsed(poly);
						// Selected vertices - either directly or indirectly through selected faces or edges.
						BitArray sel = shape->VertexTempSel(poly);
						Spline3D *spline = shape->splines[poly];
						int knots = spline->KnotCount();
						for ( int k = 0; k < knots; k++ ) {
							int vert = k*3+1;
							if ( sel[vert] ) {
								shapeData->vdelta->SetUsed(poly);
								tmAxis = iObjParams->GetTransformAxis(nodes[i],poly);
								mat    = nodes[i]->GetObjectTM(t,&valid) * Inverse(tmAxis);
								imat   = Inverse(mat);
								xproc->SetMat(mat);
						
								// XForm the cache vertices
								oldpt = spline->GetVert(vert);
								newpt = xproc->proc(oldpt,mat,imat);
								spline->SetVert(vert,newpt);
								delta = newpt - oldpt;

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

								// If it's a bezier knot, also affect its vectors
								if(spline->IsBezierPt(k)) {
									int in = vert - 1;
									int out = vert + 1;

									// XForm the cache vertices
									oldin = spline->GetVert(in);
									spline->SetVert(in,xproc->proc(oldin,mat,imat));
									delta = spline->GetVert(in) - oldin;

									// Move the delta's vertices.
									shapeData->vdelta->Move(poly,in,delta);

									// XForm the cache vertices
									oldout = spline->GetVert(out);
									spline->SetVert(out,xproc->proc(oldout,mat,imat));

									// Move the delta's vertices.
									shapeData->vdelta->Move(poly,out,spline->GetVert(out) - oldout);
									}
								}
							}
						if(shapeData->vdelta->IsUsed(poly)) {
							spline->ComputeBezPoints();
							shape->InvalidateGeomCache();
							}
						}
					}
					break;
				}			
			}
		else {
			mat = nodes[i]->GetObjectTM(t,&valid) * Inverse(tmAxis);
			imat = Inverse(mat);
			xproc->SetMat(mat);

			for(int poly = 0; poly < shape->splineCount; ++poly) {
				shapeData->vdelta->ClearUsed(poly);
				// Selected vertices - either directly or indirectly through selected faces or edges.
				BitArray sel = shape->VertexTempSel(poly);
				Spline3D *spline = shape->splines[poly];
				int knots = spline->KnotCount();
				for ( int k = 0; k < knots; k++ ) {
					int vert = k*3+1;
					if ( sel[vert] ) {
						shapeData->vdelta->SetUsed(poly);

						// XForm the cache vertices
						oldpt = spline->GetVert(vert);
						newpt = xproc->proc(oldpt,mat,imat);
						spline->SetVert(vert,newpt);
						delta = newpt - oldpt;

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

						// If it's a bezier knot, also affect its vectors
						if(spline->IsBezierPt(k)) {
							int in = vert - 1;
							int out = vert + 1;

							// XForm the cache vertices
							oldin = spline->GetVert(in);
							spline->SetVert(in,xproc->proc(oldin,mat,imat));

							// Move the delta's vertices.
							shapeData->vdelta->Move(poly,in,spline->GetVert(in) - oldin);

							// XForm the cache vertices
							oldout = spline->GetVert(out);
							spline->SetVert(out,xproc->proc(oldout,mat,imat));

							// Move the delta's vertices.
							shapeData->vdelta->Move(poly,out,spline->GetVert(out) - oldout);
							}
						}
					}
				if(shapeData->vdelta->IsUsed(poly)) {
					spline->ComputeBezPoints();
					shape->InvalidateGeomCache();
					}
				}
			}
		shapeData->TempData(this)->Invalidate(PART_GEOM);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	// Mark all objects in selection set
	SetAFlag(A_HELD);
	
	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	NotifyDependents(FOREVER, PART_GEOM, REFMSG_CHANGE);
	}

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

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

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

static IPoint2
ProjectPointI(GraphicsWindow *gw, Point3 fp) {
	IPoint3 out;
	gw->wTransPoint(&fp,&out);
	IPoint2 work;
	work.x = out.x + 1;
	work.y = out.y + 1;
	return work;
	}

static Point2
ProjectPointF(GraphicsWindow *gw, Point3 fp) {
	IPoint3 out;
	gw->wTransPoint(&fp,&out);
	Point2 work;
	work.x = (float)out.x;
	work.y = (float)out.y;
	return work;
	}

class AutoConnectPrompt {
	public:
		BOOL prompted;
		BOOL doIt;
		AutoConnectPrompt() { prompted = FALSE; }
		BOOL DoIt(ViewExp *vpt, Matrix3 &tm, Point3 p1, Point3 p2);
	};

#define HITSIZE 6

BOOL AutoConnectPrompt::DoIt(ViewExp *vpt, Matrix3 &tm, Point3 p1, Point3 p2) {
	GraphicsWindow *gw = vpt->getGW();
	gw->setTransform(tm);

	IPoint2 sp1 = ProjectPointI(gw, p1);
	IPoint2 sp2 = ProjectPointI(gw, p2);

	if(abs(sp1.x - sp2.x) >= HITSIZE || abs(sp1.y - sp2.y) >= HITSIZE)
		return FALSE;
	if(prompted)
		return doIt;
	if(weldThreshold == 0.0f)
		return FALSE;
	TSTR s1 = GetString(IDS_TH_CONNECT_COINCIDENT);
	TSTR s2 = GetString(IDS_TH_EDITSPLINE);
	int result = MessageBox(GetActiveWindow(), s1, s2, MB_YESNO);
	doIt = (result == IDYES) ? TRUE : FALSE;
	prompted = TRUE;
	if(!doIt)
		theHold.End();
	return doIt;
	}

// The following is called before the first Move(), Rotate() or Scale() call
void EditSplineMod::TransformStart(TimeValue t) {
	if(!iObjParams)
		return;
	iObjParams->LockAxisTripods(TRUE);
	NotifyDependents(FOREVER, 0, REFMSG_SHAPE_START_CHANGE);
	BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) ? TRUE : FALSE;
	if(shiftPressed) {
		if(selLevel == ES_SEGMENT) {
			ModContextList mcList;		
			INodeTab nodes;
			Interval valid;

			theHold.Begin();
			BOOL needUndo = FALSE;

			iObjParams->GetModContexts(mcList,nodes);
			ClearShapeDataFlag(mcList,ESD_BEENDONE);
			for ( int i = 0; i < mcList.Count(); i++ ) {
				EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
				if ( !shapeData ) continue;
				if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;
				
				// If the mesh isn't yet cache, this will cause it to get cached.
				BezierShape *shape = shapeData->TempData(this)->GetShape(t);
				if(!shape) continue;
				
				// Go thru all polygons -- If any segs selected, copy 'em
				int polys = shape->SplineCount();
				BOOL altered = FALSE;
				for(int poly = 0; poly < polys; ++poly) {
					if(shape->segSel[poly].NumberSet()) {
						Spline3D *spline = shape->splines[poly];
						// Start a restore object...
						if ( theHold.Holding() ) {
							SegCopyRecord *rec = new SegCopyRecord(shape, TRUE);	// Copy, select copy
							shapeData->AddChangeRecord(rec);
							theHold.Put(new ShapeRestore(shapeData,this,rec));
							}
						CopySegments(shape, TRUE);	// Actually copy it
						altered = needUndo = TRUE;
						}
					}
				if(altered)
					shapeData->TempData(this)->Invalidate(PART_TOPO|PART_GEOM);

				shapeData->TempData(this)->Invalidate(PART_GEOM);
				shapeData->SetFlag(ESD_BEENDONE,TRUE);
				}
			nodes.DisposeTemporary();
			if(needUndo)
				theHold.Accept(GetString(IDS_TH_COPY_SEGMENT));
			else
				theHold.End(); 		// Forget it!

			}
		else
		if(selLevel == ES_SPLINE) {
			ModContextList mcList;		
			INodeTab nodes;
			Interval valid;

			if ( !iObjParams ) return;

			theHold.Begin();
			BOOL needUndo = FALSE;

			iObjParams->GetModContexts(mcList,nodes);
			ClearShapeDataFlag(mcList,ESD_BEENDONE);
			for ( int i = 0; i < mcList.Count(); i++ ) {
				EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
				if ( !shapeData ) continue;
				if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;
				
				// If the mesh isn't yet cache, this will cause it to get cached.
				BezierShape *shape = shapeData->TempData(this)->GetShape(t);
				if(!shape) continue;
				
				// Go thru all polygons -- If it's selected, copy it
				int polys = shape->SplineCount();
				BOOL altered = FALSE;
				for(int poly = 0; poly < polys; ++poly) {
					if(shape->polySel[poly]) {
						Spline3D *spline = shape->splines[poly];
						// Start a restore object...
						if ( theHold.Holding() ) {
							PolyCopyRecord *rec = new PolyCopyRecord(shape, poly, FALSE, TRUE);	// Copy, deselect original, select copy
							shapeData->AddChangeRecord(rec);
							theHold.Put(new ShapeRestore(shapeData,this,rec));
							}
						CopySpline(shape, poly, FALSE, TRUE);	// Actually copy it
						altered = needUndo = TRUE;
						}
					}
				if(altered)
					shapeData->TempData(this)->Invalidate(PART_TOPO|PART_GEOM);

				shapeData->TempData(this)->Invalidate(PART_GEOM);
				shapeData->SetFlag(ESD_BEENDONE,TRUE);
				}
			nodes.DisposeTemporary();
			if(needUndo)
				theHold.Accept(GetString(IDS_TH_COPY_SPLINE));
			else
				theHold.End(); 		// Forget it!

			}	
		}
	}

// The following is called after the user has completed the Move, Rotate or Scale operation and
// the undo object has been accepted.
void EditSplineMod::TransformFinish(TimeValue t) {
	ModContextList mcList;		
	INodeTab nodes;
	Matrix3 mat,imat;	
	Interval valid;
	int numAxis;
	Point3 oldpt,newpt,oldin,oldout,rel,delta;
	AutoConnectPrompt thePrompt;
	if ( !iObjParams ) return;

	ViewExp *vpt = iObjParams->GetViewport(NULL);

	iObjParams->LockAxisTripods(FALSE);

	theHold.Begin();
	BOOL needUndo = FALSE;

	iObjParams->GetModContexts(mcList,nodes);
	numAxis = iObjParams->GetNumAxis();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) continue;
		
		// If this is the first edit, then the delta arrays will be allocated
		shapeData->BeginEdit(t);

		Matrix3 nodeTM = nodes[i]->GetObjectTM(t);

		changed:
		// Go thru all polygons -- If it's an open poly and it has a selected end vertex, see if
		// its end verts overlap -- if they do, prompt for closure.
		int polys = shape->SplineCount();
		for(int poly = 0; poly < polys; ++poly) {
			Spline3D *spline = shape->splines[poly];
			if(!spline->Closed()) {
				int knots = spline->KnotCount();
				BitArray pSel = shape->VertexTempSel(poly);
				if(knots > 2 && (pSel[1] || pSel[(knots-1)*3+1])) {
					Point3 p1 = spline->GetKnotPoint(0);
					Point3 p2 = spline->GetKnotPoint(knots-1);
					if(thePrompt.DoIt(vpt, nodeTM, p1, p2)) {
						shapeData->StartChangeGroup();
						PolyEndAttachRecord *rec = NULL;
						// Do the close
						if(pSel[1]) {
							rec = new PolyEndAttachRecord(shape, poly, 0, poly, knots-1);
							DoPolyEndAttach(shape, poly, 0, poly, knots-1);
							}
						else {
							rec = new PolyEndAttachRecord(shape, poly, knots-1, poly, 0);
							DoPolyEndAttach(shape, poly, knots-1, poly, 0);
							}
						shapeData->AddChangeRecord(rec);
						// Start a restore object...
						if ( theHold.Holding() )
							theHold.Put(new ShapeRestore(shapeData,this,rec));
						shapeData->TempData(this)->Invalidate(PART_TOPO|PART_GEOM);
						needUndo = TRUE;
						}
					}

				}
			}
		// Go thru all polygons -- If an open poly, and it has a selected end vertex, see if its
		// end vertices overlap the end vertex of another open poly.  If so, prompt for connection
		for(int poly1 = 0; poly1 < polys; ++poly1) {
			Spline3D *spline1 = shape->splines[poly1];
			if(!spline1->Closed()) {
				int knots1 = spline1->KnotCount();
				int lastKnot1 = knots1-1;
				int lastSel1 = lastKnot1 * 3 + 1;
				BitArray pSel = shape->VertexTempSel(poly1);
				if(pSel[1] || pSel[lastSel1]) {
					Point3 p1 = spline1->GetKnotPoint(0);
					Point3 p2 = spline1->GetKnotPoint(knots1-1);
					for(int poly2 = 0; poly2 < polys; ++poly2) {
						Spline3D *spline2 = shape->splines[poly2];
						if(poly1 != poly2 && !spline2->Closed()) {
							int knots2 = spline2->KnotCount();
							Point3 p3 = spline2->GetKnotPoint(0);
							Point3 p4 = spline2->GetKnotPoint(knots2-1);
							int vert1, vert2;
							if(pSel[1]) {
								if(thePrompt.DoIt(vpt, nodeTM, p3, p1)) {
									vert1 = 0;
									vert2 = 0;

									attach_it:
									PolyEndAttachRecord *rec = new PolyEndAttachRecord(shape, poly1, vert1, poly2, vert2);
									shapeData->AddChangeRecord(rec);
									// Do the attach
									DoPolyEndAttach(shape, poly1, vert1, poly2, vert2);
									// Start a restore object...
									if ( theHold.Holding() )
										theHold.Put(new ShapeRestore(shapeData,this,rec));
									shapeData->TempData(this)->Invalidate(PART_TOPO|PART_GEOM);
									needUndo = TRUE;
									goto changed;
									}
								else
								if(thePrompt.DoIt(vpt, nodeTM, p4, p1)) {
									vert1 = 0;
									vert2 = knots2-1;
									goto attach_it;
									}
								}
							if(pSel[lastSel1]) {
								if(thePrompt.DoIt(vpt, nodeTM, p3, p2)) {
									vert1 = knots1-1;
									vert2 = 0;
									goto attach_it;
									}
								else
								if(thePrompt.DoIt(vpt, nodeTM, p4, p2)) {
									vert1 = knots1-1;
									vert2 = knots2-1;
									goto attach_it;
									}
								}
							}
						}
					}
				}
			}
		shapeData->TempData(this)->Invalidate(PART_GEOM);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	if(needUndo)
		theHold.Accept(GetString(IDS_TH_POLYCONNECT));
	else
		theHold.End(); 		// Forget it!
	
	nodes.DisposeTemporary();
	UpdatePolyVertCount();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	NotifyDependents(FOREVER, 0, REFMSG_SHAPE_END_CHANGE);
	NotifyDependents(FOREVER, PART_GEOM, REFMSG_CHANGE);
	if ( vpt ) iObjParams->ReleaseViewport(vpt);
	}

// The following is called when the transform operation is cancelled by a right-click and the undo
// has been cancelled.
void EditSplineMod::TransformCancel(TimeValue t) {
//DebugPrint("Transform cancel\n");
	if (iObjParams) iObjParams->LockAxisTripods(FALSE);
	NotifyDependents(FOREVER, PART_ALL, REFMSG_SHAPE_END_CHANGE);
	}

void EditSplineMod::DoBoolean(int poly2)
	{
	ModContextList mcList;	
	INodeTab nodes;
	DWORD *clones = NULL;	
	BOOL altered = FALSE;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);

	theHold.Begin();

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

		// If the mesh isn't yet cached, this will cause it to get cached.
		BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
		if(!shape) continue;

		if(shape == boolShape) {
			BooleanRecord *rec = new BooleanRecord(boolShape, boolPoly1, poly2, boolType);
			int newPolyNum;

			int boolStat = PerformBoolean(boolShape, boolPoly1, poly2, boolType, &newPolyNum);
			switch(boolStat) {
				case BOOL_OK:
					altered = TRUE;
					shapeData->StartChangeGroup();
					shapeData->AddChangeRecord(rec);
					// Start a restore object...
					if ( theHold.Holding() ) {
						theHold.Put(new ShapeRestore(shapeData,this,rec));
						}
					shapeData->TempData(this)->Invalidate(PART_TOPO|PART_GEOM);
					if(newPolyNum >= 0)
						boolPoly1 = newPolyNum;
					else
						CancelEditSplineModes(iObjParams);	// Cancel mode if more than one poly resulted
					break;
				default: {
					delete rec;
					TSTR reason;
					switch(boolStat) {
						case BOOL_WELD_FAILURE:
							reason = GetString(IDS_TH_BOOLWELDFAILED);
							break;
						case BOOL_COINCIDENT_VERTEX:
							reason = GetString(IDS_TH_COINCIDENTVERTEX);
							break;
						case BOOL_MUST_OVERLAP:
							reason = GetString(IDS_TH_SPLINESMUSTOVERLAP);
							break;
						}
					iObjParams->DisplayTempPrompt(reason,PROMPT_TIME);
					}
					break;
				}	
			goto done;
			}
		}
			
	// Accept all the outlines so they go on the undo stack
	done:
	if(altered)
		theHold.Accept(GetString(IDS_TH_BOOLEAN));
	else
		theHold.Cancel();

	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	UpdatePolyVertCount();
	NotifyDependents(FOREVER, PART_TOPO|PART_GEOM, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}					

void EditSplineMod::DoSegRefine(ViewExp *vpt, BezierShape *rshape, int poly, int seg, IPoint2 p) {
	ModContextList mcList;		
	INodeTab nodes;
	int holdNeeded = 0;
	int altered = 0;
	TimeValue t = iObjParams->GetTime();
	Point2 fp = Point2((float)p.x, (float)p.y);

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);

	theHold.Begin();

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

		shapeData->StartChangeGroup();

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

		if(shape == rshape) {
			// Find the location on the segment where the user clicked
			INode *inode = nodes[i];
			GraphicsWindow *gw = vpt->getGW();
			Matrix3 mat = inode->GetObjectTM(t);
			gw->setTransform(mat);	

			Spline3D *spline = shape->splines[poly];

			// Go thru the segment and narrow down where they hit
			float rez = 0.01f;
			float lorez = 0.0f;
			float hirez = 1.0f;
			// Start with a rough estimate
			HitRegion hr;
			MakeHitRegion(hr, HITTYPE_POINT, 1, 4, &p);
			gw->setHitRegion(&hr);
			float bestParam = shape->FindSegmentPoint(poly, seg, gw, gw->getMaterial(), &hr);
			Point3 pt = spline->InterpBezier3D(seg, bestParam);
			Point2 sp = ProjectPointF(gw, pt);
			float bestDist = 1000.0f;
			int iBestDist = 1000;
			if(bestParam > 0.0f) {
				bestDist = Length(sp - fp);
				iBestDist = (int)bestDist;
				}
			BOOL bestChanged = TRUE;
			while((rez > 0.00005f) || bestChanged) {
				bestChanged = FALSE;
				for(float sample = lorez; sample <= hirez; sample += rez) {
					pt = spline->InterpBezier3D(seg, sample);
					sp = ProjectPointF(gw, pt);
					float dist = Length(sp - fp);
					if(dist < bestDist) {
						int ibd = (int)dist;
						if(ibd < iBestDist) {
							bestChanged = TRUE;
							iBestDist = ibd;
							}
						bestDist = dist;
						bestParam = sample;
						if(bestDist <= 0.5)
							goto got_it;
						}
					}
				lorez = bestParam - rez;
				if(lorez < 0.0f)
					lorez = 0.0f;
				hirez = bestParam + rez;
				if(hirez > 1.0f)
					hirez = 1.0f;
				rez /= 10.0f;
				}

			got_it:
			if(iBestDist < 1000) {			
				altered = holdNeeded = 1;
				SegRefineRecord *rec = new SegRefineRecord(shape, poly, seg, bestParam);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shapeData->TempData(this)->Invalidate(PART_TOPO);
				theHold.Accept(GetString(IDS_TH_REFINE));
				RefineSegment(shape, poly, seg, bestParam);
				}
			else
				theHold.Cancel();
			goto finished;
			}
		}
	finished:
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(t, REDRAW_NORMAL);
	}

void EditSplineMod::DoSegBreak(ViewExp *vpt, BezierShape *rshape, int poly, int seg, IPoint2 p) {
	ModContextList mcList;		
	INodeTab nodes;
	int holdNeeded = 0;
	int altered = 0;
	TimeValue t = iObjParams->GetTime();

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);

	theHold.Begin();

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

		shapeData->StartChangeGroup();

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

		if(shape == rshape) {
			// Find the location on the segment where the user clicked
			INode *inode = nodes[i];
			GraphicsWindow *gw = vpt->getGW();
			HitRegion hr;
			MakeHitRegion(hr, HITTYPE_POINT, 1, 4, &p);
			gw->setHitRegion(&hr);
			Matrix3 mat = inode->GetObjectTM(t);
			gw->setTransform(mat);	
	
			float param = shape->FindSegmentPoint(poly, seg, gw, gw->getMaterial(), &hr);
			
			altered = holdNeeded = 1;
			SegBreakRecord *rec = new SegBreakRecord(shape, poly, seg, param);
			shapeData->AddChangeRecord(rec);
			if ( theHold.Holding() ) {
				theHold.Put(new ShapeRestore(shapeData,this,rec));
				}
			shapeData->TempData(this)->Invalidate(PART_TOPO);
			theHold.Accept(GetString(IDS_TH_SEGBREAK));
			BreakSegment(shape, poly, seg, param);
			goto finished;
			}
		}
	finished:
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(t, REDRAW_NORMAL);
	}

void EditSplineMod::DoVertConnect(ViewExp *vpt, BezierShape *rshape, int poly1, int vert1, int poly2, int vert2) {
	ModContextList mcList;		
	INodeTab nodes;
	int holdNeeded = 0;
	int altered = 0;
	TimeValue t = iObjParams->GetTime();

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);

	theHold.Begin();

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

		shapeData->StartChangeGroup();

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

		if(shape == rshape) {
			altered = holdNeeded = 1;
			VertConnectRecord *rec = new VertConnectRecord(shape, poly1, vert1, poly2, vert2);
			shapeData->AddChangeRecord(rec);
			if ( theHold.Holding() ) {
				theHold.Put(new ShapeRestore(shapeData,this,rec));
				}
			shapeData->TempData(this)->Invalidate(PART_TOPO);
			theHold.Accept(GetString(IDS_TH_VERTCONNECT));
			ConnectVerts(shape, poly1, vert1, poly2, vert2);
			goto finished;
			}
		}
	finished:
	nodes.DisposeTemporary();
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(t, REDRAW_NORMAL);
	}

// Start inserting vertices -- Prep the spline object and start a record of the change.
// Return the index of the vertex where insertion will begin
int EditSplineMod::StartVertInsert(ViewExp *vpt, BezierShape *rshape, int poly, int seg, int vert, EditSplineMod **mod) {
	ModContextList mcList;		
	INodeTab nodes;
	int holdNeeded = 0;
	int altered = 0;
	TimeValue t = iObjParams->GetTime();

	if ( !iObjParams ) return -1;

	iObjParams->GetModContexts(mcList,nodes);

	theHold.Begin();

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

		shapeData->StartChangeGroup();

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

		if(shape == rshape) {
			altered = holdNeeded = 1;
			insertRec = new VertInsertRecord(shape, poly);
			shapeData->AddChangeRecord(insertRec);
			if ( theHold.Holding() ) {
				theHold.Put(new ShapeRestore(shapeData,this,insertRec));
				}
			insertShape = shape;
			shapeData->TempData(this)->Invalidate(PART_TOPO);
			theHold.Accept(GetString(IDS_TH_VERTINSERT));
			// Insert the points into the spline
			Spline3D *spline = shape->splines[poly];			
			insertSpline = spline;
			insertPoly = poly;
			// If inserting at first vertex, reverse the spline and relocate the insertion point!
			if(vert == 1) {
				spline->Reverse();
				shape->vertSel[poly].Reverse();
				shape->segSel[poly].Reverse();
				vert = (spline->KnotCount() - 1) * 3 + 1;
				}
			else	// If segment, find the insertion vertex
			if(seg >= 0)
				vert = seg * 3 + 1;
			insertNode = nodes[i]->GetActualINode();
			insertTM = nodes[i]->GetObjectTM(iObjParams->GetTime());
			insertVert = vert;
			insertShapeData = shapeData;
			*mod = this;
			nodes.DisposeTemporary();
			return vert;
			}
		}
	nodes.DisposeTemporary();
	return -1;
	}

void EditSplineMod::EndVertInsert() {
	if(!insertShape)
		return;
	// Save the resulting spline to the restore record
	insertRec->newSpline = *insertSpline;
	insertRec->newVSel = insertShape->vertSel[insertPoly];
	insertRec->newSSel = insertShape->segSel[insertPoly];
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(), REDRAW_NORMAL);
	insertShape->InvalidateGeomCache();
	insertShape = NULL;
	}

void EditSplineMod::ModifyObject(TimeValue t, ModContext &mc, ObjectState * os, INode *node) 
	{		
//Alert(_T("in ModifyObject"));
	assert( os->obj->ClassID() == Class_ID(SPLINESHAPE_CLASS_ID,0) );
//Alert(_T("ModifyObject class ID is OK"));
	
	SplineShape *splShape = (SplineShape *)os->obj;
	EditSplineData *shapeData;

	if ( !mc.localData ) {
		mc.localData = new EditSplineData();
		shapeData = (EditSplineData*)mc.localData;
	} else {
		shapeData = (EditSplineData*)mc.localData;
		}
	
	shapeData->Apply(t,splShape,selLevel);
	splShape->shape.InvalidateGeomCache();
	}

void EditSplineMod::NotifyInputChanged(Interval changeInt, PartID partID, RefMessage message, ModContext *mc)
	{
	if ( mc->localData ) {
		EditSplineData *shapeData = (EditSplineData*)mc->localData;
		if ( shapeData ) {
			// The FALSE parameter indicates the the mesh cache itself is
			// invalid in addition to any other caches that depend on the
			// mesh cache.
			shapeData->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 shapes being edited, we need
// to watch for control breaks in the shapeData pointer within the HitRecord!

void EditSplineMod::SelectSubComponent( HitRecord *hitRec, BOOL selected, BOOL all, BOOL invert )
	{
	if ( !iObjParams ) return; 
	TimeValue t = iObjParams->GetTime();
	EndOutlineMove(t);

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

		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) return;

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

		switch ( selLevel ) {
			case ES_VERTEX: {
				VertSelRecord *rec = new VertSelRecord();
				rec->oldSel = shape->vertSel;
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				if ( all ) {
					if ( invert ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto vert_done;
							int poly = ((ShapeHitData *)(hitRec->hitData))->poly;
							int vert = ((ShapeHitData *)(hitRec->hitData))->index;
							if(shape->vertSel[poly][vert])
								shape->vertSel[poly].Clear(vert);
							else
								shape->vertSel[poly].Set(vert);
							hitRec = hitRec->Next();
							}
						}
					else
					if ( selected ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto vert_done;
							shape->vertSel[((ShapeHitData *)(hitRec->hitData))->poly].Set(((ShapeHitData *)(hitRec->hitData))->index);
							hitRec = hitRec->Next();
							}
						}
					else {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto vert_done;
							shape->vertSel[((ShapeHitData *)(hitRec->hitData))->poly].Clear(((ShapeHitData *)(hitRec->hitData))->index);
							hitRec = hitRec->Next();
							}
						}
					}
				else {
					int poly = ((ShapeHitData *)(hitRec->hitData))->poly;
					int vert = ((ShapeHitData *)(hitRec->hitData))->index;
					if ( invert ) {
						if(shape->vertSel[poly][vert])
							shape->vertSel[poly].Clear(vert);
						else
							shape->vertSel[poly].Set(vert);
						}
					else
					if ( selected ) {
						shape->vertSel[poly].Set(vert);
						}
					else {
						shape->vertSel[poly].Clear(vert);
						}
					hitRec = NULL;	// Reset it so we can exit	
					}
				vert_done:
				rec->newSel = shape->vertSel;
				shapeData->AddChangeRecord(rec);
				break;
				}

			case ES_SEGMENT: {
				SegSelRecord *rec = new SegSelRecord();
				rec->oldSel = shape->segSel;
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				if ( all ) {				
					if ( invert ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto seg_done;
							int poly = ((ShapeHitData *)(hitRec->hitData))->poly;
							int seg = ((ShapeHitData *)(hitRec->hitData))->index;
							if(shape->segSel[poly][seg])
								shape->segSel[poly].Clear(seg);
							else
								shape->segSel[poly].Set(seg);
							hitRec = hitRec->Next();
							}
						}
					else
					if ( selected ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto seg_done;
							shape->segSel[((ShapeHitData *)(hitRec->hitData))->poly].Set(((ShapeHitData *)(hitRec->hitData))->index);
							hitRec = hitRec->Next();
							}
						}
					else {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto seg_done;
							shape->segSel[((ShapeHitData *)(hitRec->hitData))->poly].Clear(((ShapeHitData *)(hitRec->hitData))->index);
							hitRec = hitRec->Next();
							}
						}
					}
				else {
					int poly = ((ShapeHitData *)(hitRec->hitData))->poly;
					int seg = ((ShapeHitData *)(hitRec->hitData))->index;
					if ( invert ) {
						if(shape->segSel[poly][seg])
							shape->segSel[poly].Clear(seg);
						else
							shape->segSel[poly].Set(seg);
						}
					else
					if ( selected ) {
						shape->segSel[poly].Set(seg);
						}
					else {
						shape->segSel[poly].Clear(seg);
						}	
					hitRec = NULL;	// Reset it so we can exit	
					}
				seg_done:
				rec->newSel = shape->segSel;
				shapeData->AddChangeRecord(rec);
				break;
				}

			case ES_SPLINE: {
				PolySelRecord *rec = new PolySelRecord();
				rec->oldSel = shape->polySel;
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				if ( all ) {				
					if ( invert ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto poly_done;
							int poly = ((ShapeHitData *)(hitRec->hitData))->poly;
							if(shape->polySel[poly])
								shape->polySel.Clear(poly);
							else
								shape->polySel.Set(poly);
							hitRec = hitRec->Next();
							}
						}
					else
					if ( selected ) {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto poly_done;
							shape->polySel.Set(((ShapeHitData *)(hitRec->hitData))->poly);
							hitRec = hitRec->Next();
							}
						}
					else {
						while( hitRec ) {
							// If the object changes, we're done!
							if(shapeData != (EditSplineData*)hitRec->modContext->localData)
								goto poly_done;
							shape->polySel.Clear(((ShapeHitData *)(hitRec->hitData))->poly);
							hitRec = hitRec->Next();
							}
						}
					}
				else {
					int poly = ((ShapeHitData *)(hitRec->hitData))->poly;
					if ( invert ) {
						if(shape->polySel[poly])
							shape->polySel.Clear(poly);
						else
							shape->polySel.Set(poly);
						}
					else
					if ( selected ) {
						shape->polySel.Set(poly);
						}
					else {
						shape->polySel.Clear(poly);
						}	
					hitRec = NULL;	// Reset it so we can exit	
					}
				poly_done:
				rec->newSel = shape->polySel;
				shapeData->AddChangeRecord(rec);
				break;
				}
			case ES_OBJECT:
			default:
				return;
			}
		if ( shapeData->tempData ) {
			shapeData->tempData->Invalidate(PART_SELECT);
			}
		}

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

void EditSplineMod::ClearSelection(int selLevel) 
	{
	if(selLevel == ES_OBJECT)
		return;

	ModContextList mcList;
	INodeTab nodes;

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

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;		
		shapeData->StartChangeGroup();
		BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
		if(!shape) continue;

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

		switch ( selLevel ) {
			case ES_VERTEX: {
				ClearVertSelRecord *rec = new ClearVertSelRecord(shape->vertSel);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->vertSel.ClearAll();
				break;
				}

			case ES_SEGMENT: {
				ClearSegSelRecord *rec = new ClearSegSelRecord(shape->segSel);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->segSel.ClearAll();
				break;
				}

			case ES_SPLINE: {
				ClearPolySelRecord *rec = new ClearPolySelRecord(shape->polySel);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->polySel.ClearAll();
				break;
				}
			}
		if ( shapeData->tempData ) {
			shapeData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
	nodes.DisposeTemporary();
	UpdatePolyVertCount();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	}

void EditSplineMod::SelectAll(int selLevel) 
	{
	if(selLevel == ES_OBJECT)
		return;

	ModContextList mcList;
	INodeTab nodes;

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

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;		
		shapeData->StartChangeGroup();
		BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
		if(!shape) continue;
		shapeData->BeginEdit(iObjParams->GetTime());

		switch ( selLevel ) {
			case ES_VERTEX: {
				SetVertSelRecord *rec = new SetVertSelRecord(shape->vertSel);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->vertSel.SetAll();
				break;
				}

			case ES_SEGMENT: {
				SetSegSelRecord *rec = new SetSegSelRecord(shape->segSel);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->segSel.SetAll();
				break;
				}

			case ES_SPLINE: {
				SetPolySelRecord *rec = new SetPolySelRecord(shape->polySel);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->polySel.SetAll();
				break;
				}
			}
		if ( shapeData->tempData ) {
			shapeData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
	nodes.DisposeTemporary();
	UpdatePolyVertCount();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	}

void EditSplineMod::InvertSelection(int selLevel) 
	{
	if(selLevel == ES_OBJECT)
		return;

	ModContextList mcList;
	INodeTab nodes;

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

	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;		
		shapeData->StartChangeGroup();
		BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
		if(!shape) continue;
		shapeData->BeginEdit(iObjParams->GetTime());

		switch ( selLevel ) {
			case ES_VERTEX: {
				InvertVertSelRecord *rec = new InvertVertSelRecord();
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->vertSel.Toggle();
				break;
				}

			case ES_SEGMENT: {
				InvertSegSelRecord *rec = new InvertSegSelRecord();
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->segSel.Toggle();
				break;
				}

			case ES_SPLINE: {
				InvertPolySelRecord *rec = new InvertPolySelRecord();
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->polySel.Toggle();
				break;
				}
			}
		if ( shapeData->tempData ) {
			shapeData->TempData(this)->Invalidate(PART_SELECT);
			}
		}
	nodes.DisposeTemporary();
	UpdatePolyVertCount();
	NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
	}

void EditSplineMod::UpdatePolyVertCount(HWND hwnd) {
	ModContextList mcList;
	INodeTab nodes;

	if(hEditSplineParams)
		hwnd = hEditSplineParams;

	if(!hwnd || selLevel != ES_SPLINE)
		return;
	if ( !iObjParams )
		return;
	iObjParams->GetModContexts(mcList,nodes);
	int vertCount = 0;
	for ( int i = 0; i < mcList.Count(); i++ ) {
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;		
	
		if ( shapeData->tempData && shapeData->TempData(this)->ShapeCached(iObjParams->GetTime()) ) {
			BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
			if(!shape) continue;
			int polys = shape->splineCount;
			for(int poly = 0; poly < polys; ++poly) {
				if(shape->polySel[poly])
					vertCount += shape->splines[poly]->KnotCount();
				}
			}
		}		
	TSTR string;
	string.printf(_T("%d"),vertCount); 
	SetDlgItemText(hwnd, IDC_SPLINE_VERT_COUNT, string);
	nodes.DisposeTemporary();
	}

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

	if ( !iObjParams ) return;
	iObjParams->GetModContexts(mcList,nodes);
	selLevel = level;
	switch ( level ) {
		case ES_OBJECT:
			// Not imp.
			break;

		case ES_SPLINE:
		case ES_SEGMENT:
		case ES_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++ ) {
			EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
			if ( !shapeData ) continue;		
		
			if ( shapeData->tempData && shapeData->TempData(this)->ShapeCached(iObjParams->GetTime()) ) {
				BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
				if(!shape) continue;
				shape->dispFlags = 0;
				shape->SetDispFlag(shapeLevelDispFlags[selLevel]);
				shape->selLevel = shapeLevel[selLevel];
				}
			}		

		NotifyDependents( FOREVER, 	PART_SUBSEL_TYPE|PART_DISPLAY, 	REFMSG_CHANGE);
		iObjParams->PipeSelLevelChanged();
		}
	nodes.DisposeTemporary();
	}


int EditSplineMod::SubObjectIndex(HitRecord *hitRec)
	{	
	EditSplineData *shapeData = (EditSplineData*)hitRec->modContext->localData;
	if ( !shapeData ) return 0;
	if ( !iObjParams ) return 0;
	TimeValue t = iObjParams->GetTime();
	switch ( selLevel ) {
		case ES_VERTEX: {
			BezierShape *shape = shapeData->TempData(this)->GetShape(t);
			int hitIndex = ((ShapeHitData *)(hitRec->hitData))->index;
			return hitIndex;
			}
		case ES_SEGMENT: {
			BezierShape *shape = shapeData->TempData(this)->GetShape(t);
			int hitIndex = ((ShapeHitData *)(hitRec->hitData))->index;
			return hitIndex;
			}
		case ES_SPLINE:
			return ((ShapeHitData *)(hitRec->hitData))->poly;

		default:
			return 0;
		}
	}

BOOL EditSplineMod::DependOnTopology(ModContext &mc)
	{
	EditSplineData *shapeData = (EditSplineData*)mc.localData;
	if (shapeData) {
		if (shapeData->GetFlag(ESD_HASDATA)) {
			return TRUE;
			}
		}
	return FALSE;
	}

void EditSplineMod::GetSubObjectTMs(
		SubObjAxisCallback *cb,TimeValue t,INode *node,ModContext *mc)
	{
	Interval valid;
	if ( mc->localData ) {
		EditSplineData *shapeData = (EditSplineData*)mc->localData;
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) return;

		switch ( selLevel ) {
			case ES_VERTEX: {
				int poly;
				Spline3D *spline;
 				Matrix3 otm = node->GetObjectTM(t,&valid);
				Matrix3 tm = node->GetNodeTM(t,&valid);
				int vbase = 1;
				for(poly = 0; poly < shape->splineCount; ++poly) {
					spline = shape->splines[poly];
					BitArray sel = shape->VertexTempSel(poly);
					for(int i = 1; i < spline->Verts(); i+=3, vbase+=3) {
						// Only display knots' axes
						if(sel[i]) {
							tm.SetTrans(otm * spline->GetVert(i));
							cb->TM(tm, vbase);
							}
						}
					}
				break;
				}
			case ES_SEGMENT: {
				int polys = shape->splineCount;
 				Matrix3 otm = node->GetObjectTM(t,&valid);
				Matrix3 tm = node->GetNodeTM(t,&valid);
				Box3 box;
				for(int poly = 0; poly < polys; ++poly) {
					BitArray sel = shape->VertexTempSel(poly);
					Spline3D *spline = shape->splines[poly];
					for ( int i = 0; i < spline->Verts(); i+=3 ) {
						if ( sel[i] )
							box += spline->GetVert(i);
						}
					}
				tm.SetTrans(otm * box.Center());
				cb->TM(tm, 0);
				break;
				}
			case ES_SPLINE: {
				int polys = shape->splineCount;
 				Matrix3 otm = node->GetObjectTM(t,&valid);
				Matrix3 tm = node->GetNodeTM(t,&valid);
				for(int poly = 0; poly < polys; ++poly) {
					if(shape->polySel[poly]) {
						Box3 box;
						Spline3D *spline = shape->splines[poly];
						for ( int i = 1; i < spline->Verts(); i+=3)
							box += spline->GetVert(i);
						tm.SetTrans(otm * box.Center());
						cb->TM(tm, poly);
						}
					}
				break;
				}
			}
		}
	}

void EditSplineMod::GetSubObjectCenters(
	SubObjAxisCallback *cb,TimeValue t,INode *node,ModContext *mc)
	{
	Interval valid;
	Matrix3 tm = node->GetObjectTM(t,&valid);	
	
	assert(iObjParams);
	if ( mc->localData ) {	
		EditSplineData *shapeData = (EditSplineData*)mc->localData;		
		BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
		if(!shape) return;

		switch ( selLevel ) {
			case ES_VERTEX: {
				int polys = shape->splineCount;
				Spline3D *spline;
				Box3 box;
				int vbase = 1;
				for(int poly = 0; poly < polys; ++poly) {
					BitArray sel = shape->VertexTempSel(poly);
					spline = shape->splines[poly];
					for ( int i = 1; i < spline->Verts(); i+=3, vbase+=3 ) {
						if ( sel[i] )
							cb->Center(spline->GetVert(i) * tm, vbase);
						}
					}
				break;
				}
			case ES_SEGMENT: { 
				int polys = shape->splineCount;
				Box3 box;
				for(int poly = 0; poly < polys; ++poly) {
					BitArray sel = shape->VertexTempSel(poly);
					Spline3D *spline = shape->splines[poly];
					for ( int i = 0; i < spline->Verts(); i++ ) {
						if ( sel[i] && ((i-1)%3) == 0 )
							box += spline->GetVert(i) * tm;
						}
					}
				cb->Center(box.Center(),0);
				break;
				}
			case ES_SPLINE: {
				int polys = shape->splineCount;
				for(int poly = 0; poly < polys; ++poly) {
					if(shape->polySel[poly]) {
						Box3 box;
						Spline3D *spline = shape->splines[poly];
						for ( int i = 1; i < spline->Verts(); i+=3)
							box += spline->GetVert(i) * tm;
						cb->Center(box.Center(), poly);
						}
					}
				break;
				}
			
			default:
				cb->Center(tm.GetTrans(),0);
				break;
			}		
		}
	}

void EditSplineMod::DeleteShapeDataTempData()
	{
	ModContextList mcList;
	INodeTab nodes;

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

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


void EditSplineMod::CreateShapeDataTempData()
	{
	ModContextList mcList;
	INodeTab nodes;

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

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

//--------------------------------------------------------------
int EditSplineMod::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 ) {		
		EditSplineData *shapeData = (EditSplineData*)mc->localData;
		BezierShape *shape = shapeData->TempData(this)->GetShape(iObjParams->GetTime());
		if(!shape) return 0;
		SubShapeHitList hitList;
		ShapeSubHitRec *rec;
		res = shape->SubObjectHitTest( gw, gw->getMaterial(), &hr,
			flags|((splineHitOverride) ? shapeHitLevel[splineHitOverride] : shapeHitLevel[selLevel]), hitList );
	
		rec = hitList.First();
		while( rec ) {
			vpt->LogHit(inode,mc,rec->dist,123456,new ShapeHitData(rec->shape, rec->poly, rec->index));
			rec = rec->Next();
			}
		}

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

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

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



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

void EditSplineMod::SetRollupPage(IObjParam *ip)
	{
	EndOutlineMove(ip->GetTime());
	if ( hEditSplineParams ) {
		RemoveRollupPage(ip);
		}
	hEditSplineParams = ip->AddRollupPage( 
			hInstance, 
			MAKEINTRESOURCE(shapeDlg[selLevel]),
			shapeDlgProc[selLevel],
			GetString(shapeDlgString[selLevel]), 
			(LPARAM)this );		
	
	ip->RegisterDlgWnd( hEditSplineParams );
	if(selLevel == ES_SPLINE)
		UpdatePolyVertCount();
	}

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

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

BOOL ESModContextEnumProc::proc(ModContext *mc) {
	EditSplineData *shapeData = (EditSplineData*)mc->localData;
	if ( shapeData )		
		shapeData->RescaleWorldUnits(f);
	return TRUE;
	}

// World scaling
void EditSplineMod::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
	ESModContextEnumProc proc(f);
	EnumModContexts(&proc);
	NotifyDependents(FOREVER, PART_GEOM, REFMSG_CHANGE);
	}

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

	if ( !hEditSplineParams ) {
		SetRollupPage(ip);		
	} else {
		SetWindowLong( hEditSplineParams, 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);
	outlineMode     = new OutlineCMode(this,ip);
	segBreakMode    = new SegBreakCMode(this,ip);
	segRefineMode   = new SegRefineCMode(this,ip);
	vertConnectMode = new VertConnectCMode(this,ip);
	vertInsertMode  = new VertInsertCMode(this,ip);
	booleanMode     = new BooleanCMode(this,ip);
	createLineMode  = new CreateLineCMode(this,ip);

	// Add our sub object type
	TSTR type1( GetString(IDS_TH_VERTEX) );
	TSTR type2( GetString(IDS_TH_SEGMENT) );
	TSTR type3( GetString(IDS_TH_SPLINE) );
	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 EditSplineMod::EndEditParams( IObjParam *ip, ULONG flags,Animatable *next )
	{
	EndOutlineMove(ip->GetTime());

	if ( flags&END_EDIT_REMOVEUI ) {
		RemoveRollupPage(ip);
	} else {		
		SetWindowLong( hEditSplineParams, GWL_USERDATA, 0 );		
		}

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

	CancelEditSplineModes(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);
	
	DeleteShapeDataTempData();
	iObjParams = NULL;
	
	ip->DeleteMode(moveMode);
	ip->DeleteMode(rotMode);
	ip->DeleteMode(uscaleMode);
	ip->DeleteMode(nuscaleMode);
	ip->DeleteMode(squashMode);
	ip->DeleteMode(selectMode);
	ip->DeleteMode(outlineMode);
	ip->DeleteMode(segBreakMode);
	ip->DeleteMode(segRefineMode);
	ip->DeleteMode(vertConnectMode);
	ip->DeleteMode(vertInsertMode);
	ip->DeleteMode(booleanMode);
	ip->DeleteMode(createLineMode);

	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;
	if ( outlineMode ) delete outlineMode;
	outlineMode = NULL;
	if ( segBreakMode ) delete segBreakMode;
	segBreakMode = NULL;
	if ( segRefineMode ) delete segRefineMode;
	segRefineMode = NULL;
	if ( vertConnectMode ) delete vertConnectMode;
	vertConnectMode = NULL;
	if ( vertInsertMode ) delete vertInsertMode;
	vertInsertMode = NULL;
	if ( booleanMode ) delete booleanMode;
	booleanMode = NULL;
	if ( createLineMode ) delete createLineMode;
	createLineMode = NULL;
	}

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	theHold.Begin();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;

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

		int polys = shape->splineCount;
		for(int poly = 0; poly < polys; ++poly) {
			Spline3D *spline = shape->splines[poly];
			// If any bits are set in the selection set, let's DO IT!!
			if(shape->vertSel[poly].NumberSet()) {
				altered = holdNeeded = 1;
				VertBreakRecord *rec = new VertBreakRecord(shape, poly);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				// Call the spline break function
				BreakSplineAtSelVerts(shape, poly);
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_VERTBREAK));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOVALIDVERTSSEL),PROMPT_TIME);
		theHold.End();
		}

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

static BOOL NeedsWeld(Spline3D *spline, BitArray sel, float thresh) {
	int knots = spline->KnotCount();
	for(int i = 0; i < knots; ++i) {
		if(sel[i*3+1]) {
			int next = (i + 1) % knots;
			if(sel[next*3+1] && Length(spline->GetKnotPoint(i) - spline->GetKnotPoint(next)) <= thresh)
				return TRUE;
			}
		}
	return FALSE;
	}

// Vertex Weld modifier method
void EditSplineMod::DoVertWeld() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	BOOL holdNeeded = FALSE;
	BOOL hadSelected = FALSE;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	int oldVerts = 0;
	int newVerts = 0;

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

		shapeData->StartChangeGroup();
		
		// If the mesh isn't yet cache, this will cause it to get cached.
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) continue;

		// Add the original number of knots to the oldVerts field
		for(int poly = 0; poly < shape->splineCount; ++poly)
			oldVerts += shape->splines[poly]->KnotCount();

		// If this is the first edit, then the delta arrays will be allocated
		shapeData->BeginEdit(t);

		// Poly-to-poly weld loops back here because a weld between polys creates one
		// polygon which may need its two new endpoints welded
		changed:
		int polys = shape->splineCount;
		for(poly = polys - 1; poly >= 0; --poly) {
			Spline3D *spline = shape->splines[poly];
			// If any bits are set in the selection set, let's DO IT!!
			if(shape->vertSel[poly].NumberSet()) {
				hadSelected = TRUE;
				if(NeedsWeld(spline, shape->vertSel[poly], weldThreshold)) {
					altered = holdNeeded = TRUE;
					VertWeldRecord *rec = new VertWeldRecord(shape, poly, weldThreshold);
					shapeData->AddChangeRecord(rec);
					if ( theHold.Holding() ) {
						theHold.Put(new ShapeRestore(shapeData,this,rec));
						}
					// Call the weld function
					rec->deleted = WeldSplineAtSelVerts(shape, poly, weldThreshold);
					}
				}
			}

		// Now check for welds with other polys (endpoints only)
		polys = shape->SplineCount();
		for(int poly1 = 0; poly1 < polys; ++poly1) {
			Spline3D *spline1 = shape->splines[poly1];
			if(!spline1->Closed()) {
				int knots1 = spline1->KnotCount();
				int lastKnot1 = knots1-1;
				int lastSel1 = lastKnot1 * 3 + 1;
				BitArray pSel = shape->VertexTempSel(poly1);
				if(pSel[1] || pSel[lastSel1]) {
					Point3 p1 = spline1->GetKnotPoint(0);
					Point3 p2 = spline1->GetKnotPoint(knots1-1);
					for(int poly2 = 0; poly2 < polys; ++poly2) {
						Spline3D *spline2 = shape->splines[poly2];
						if(poly1 != poly2 && !spline2->Closed()) {
							int knots2 = spline2->KnotCount();
							Point3 p3 = spline2->GetKnotPoint(0);
							Point3 p4 = spline2->GetKnotPoint(knots2-1);
							int vert1, vert2;
							if(pSel[1]) {
								if(Length(p3 - p1) <= weldThreshold) {
									vert1 = 0;
									vert2 = 0;

									attach_it:
									PolyEndAttachRecord *rec = new PolyEndAttachRecord(shape, poly1, vert1, poly2, vert2);
									shapeData->AddChangeRecord(rec);
									// Do the attach
									DoPolyEndAttach(shape, poly1, vert1, poly2, vert2);
									// Start a restore object...
									if ( theHold.Holding() )
										theHold.Put(new ShapeRestore(shapeData,this,rec));
									shapeData->TempData(this)->Invalidate(PART_TOPO|PART_GEOM);
									altered = holdNeeded = TRUE;
									goto changed;
									}
								else
								if(Length(p4 - p1) <= weldThreshold) {
									vert1 = 0;
									vert2 = knots2-1;
									goto attach_it;
									}
								}
							if(pSel[lastSel1]) {
								if(Length(p3 - p2) <= weldThreshold) {
									vert1 = knots1-1;
									vert2 = 0;
									goto attach_it;
									}
								else
								if(Length(p4 - p2) <= weldThreshold) {
									vert1 = knots1-1;
									vert2 = knots2-1;
									goto attach_it;
									}
								}
							}
						}
					}
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);

		// Add the original number of knots to the oldVerts field
		for(poly = 0; poly < shape->splineCount; ++poly)
			newVerts += shape->splines[poly]->KnotCount();

		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		theHold.Accept(GetString(IDS_TH_VERTWELD));
		TSTR s1;
		int welded = oldVerts - newVerts;
		s1.printf(GetString(IDS_TH_VERTWELDRESULT), welded, oldVerts);
		iObjParams->DisplayTempPrompt(s1,PROMPT_TIME);
		}
	else {
		TSTR s1;
		if(!hadSelected)
			s1 = TSTR( GetString(IDS_TH_NOVALIDVERTSSEL) );
		else
			s1 = TSTR( GetString(IDS_TH_NOWELDPERFORMED) );
		iObjParams->DisplayTempPrompt(s1,PROMPT_TIME);
		theHold.End();
		}

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

// Make First modifier method
void EditSplineMod::DoMakeFirst() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	BOOL holdNeeded = FALSE;
	BOOL hadSelected = FALSE;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

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

		int polys = shape->splineCount;
		for(int poly = polys - 1; poly >= 0; --poly) {
			Spline3D *spline = shape->splines[poly];
			int knots = spline->KnotCount();
			// If one bits is set in the selection set, let's DO IT!!
			BitArray &vsel = shape->vertSel[poly];
			if(vsel.NumberSet() == 1) {
				if(spline->Closed()) {
					for(int i = 0; i < knots; ++i) {
						if(vsel[i*3+1])
							break;
						}
					PolyFirstRecord *rec = new PolyFirstRecord(shape, poly, i);
					shapeData->AddChangeRecord(rec);
					if ( theHold.Holding() ) {
						theHold.Put(new ShapeRestore(shapeData,this,rec));
						}
					shape->MakeFirst(poly, i);
					altered = holdNeeded = TRUE;
					}
				else
				if(vsel[(knots-1)*3+1]) {	// Last vertex?
					PolyReverseRecord *rec = new PolyReverseRecord(poly);
					shapeData->AddChangeRecord(rec);
					if ( theHold.Holding() ) {
						theHold.Put(new ShapeRestore(shapeData,this,rec));
						}
					shape->Reverse(poly);
					altered = holdNeeded = TRUE;
					}
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_MAKEFIRST));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOVALIDVERTSSEL),PROMPT_TIME);
		theHold.End();
		}

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

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	theHold.Begin();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;

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

		int polys = shape->splineCount;
		for(int poly = polys - 1; poly >= 0; --poly) {
			Spline3D *spline = shape->splines[poly];
			// If any bits are set in the selection set, let's DO IT!!
			if(shape->vertSel[poly].NumberSet()) {
				altered = holdNeeded = 1;
				VertDeleteRecord *rec = new VertDeleteRecord(shape, poly);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() )
					theHold.Put(new ShapeRestore(shapeData,this,rec));
				// Call the vertex delete function
				rec->deleted = DeleteSelVerts(shape, poly);
				}
			}

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

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


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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	theHold.Begin();
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;

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

		int polys = shape->splineCount;
		for(int poly = polys - 1; poly >= 0; --poly) {
			Spline3D *spline = shape->splines[poly];
			// If any bits are set in the selection set, let's DO IT!!
			if(shape->segSel[poly].NumberSet()) {
				altered = holdNeeded = 1;
				SegDeleteRecord *rec = new SegDeleteRecord(shape, poly);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				// Call the segment delete function
				rec->deleted = DeleteSelSegs(shape, poly);
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_SEGDELETE));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOSEGSSEL),PROMPT_TIME);
		theHold.End();
		}

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

// Segment Detach modifier method
void EditSplineMod::DoSegDetach(int copy, int reorient) {
	int dialoged = 0;
	TSTR newName(GetString(IDS_TH_SHAPE));
	int retain = 1;
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	theHold.Begin();

	// Create a spline shape object
	SplineShape *splShape = new SplineShape;

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

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

		int polys = shape->splineCount;
		for(int poly = polys - 1; poly >= 0; --poly) {
			Spline3D *spline = shape->splines[poly];
			// If any bits are set in the selection set, let's DO IT!!
			int segsSelected = shape->segSel[poly].NumberSet();
			if(segsSelected) {
				if(!dialoged) {
					dialoged = 1;
					if(!GetDetachOptions(iObjParams, newName))
						goto bail_out;
					}
				altered = holdNeeded = 1;					
				SegDetachRecord *rec = new SegDetachRecord(shape, poly, copy);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				int segments = spline->Segments();
				// If all segments selected, copy the whole polygon!
				if(segsSelected == segments) {
					Spline3D *newSpline = splShape->shape.NewSpline();
					*newSpline = *spline;
					if(multipleObjects && !reorient)
						newSpline->Transform(&nodes[i]->GetObjectTM(t));
					}
				else {
					int end = segments;
					for(int seg = 0; seg < end; ++seg) {
						if(shape->segSel[poly][seg]) {
							Spline3D *newSpline = splShape->shape.NewSpline();
							if(seg == 0) {
								backLoop:
								if(shape->segSel[poly][--end]) {
									SplineKnot addKnot(spline->GetKnotType(end),spline->GetLineType(end),
										spline->GetKnotPoint(end),spline->GetInVec(end),spline->GetOutVec(end));
									newSpline->AddKnot(addKnot, 0);
									goto backLoop;
									}

								}
							SplineKnot addKnot(spline->GetKnotType(seg),spline->GetLineType(seg),
								spline->GetKnotPoint(seg),spline->GetInVec(seg),spline->GetOutVec(seg));
							newSpline->AddKnot(addKnot, -1);

							loop:
							seg = (seg + 1) % segments;
							addKnot = SplineKnot(spline->GetKnotType(seg),spline->GetLineType(seg),
								spline->GetKnotPoint(seg),spline->GetInVec(seg),spline->GetOutVec(seg));
							newSpline->AddKnot(addKnot, -1);
							if(seg > 0 && seg < end && shape->segSel[poly][seg])
								goto loop;

							if(multipleObjects && !reorient)
								newSpline->Transform(&nodes[i]->GetObjectTM(t));

							// Finish up the spline!
							newSpline->ComputeBezPoints();
							shape->InvalidateGeomCache();

							// Special termination test for wraparound
							if(seg == 0)
								seg = end;
							}
						}
					}
				// Call the segment delete function
				if(!copy)
					rec->deleted = DeleteSelSegs(shape, poly);
				}
			}

		bail_out:
		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		INode *newNode = iObjParams->CreateObjectNode(splShape);
		newNode->SetName(newName.data());
		splShape->shape.UpdateSels();	// Make sure it readies the selection set info
		splShape->shape.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!
		theHold.Accept(GetString(IDS_TH_SEGDETACH));
		}
	else {
		delete splShape;	// Didn't need it after all!
		if(!dialoged)
			iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOSEGSSEL),PROMPT_TIME);
		theHold.End();
		}

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

// Close all selected polygons (that aren't already closed)
void EditSplineMod::DoPolyClose() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	theHold.Begin();

	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;

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

		int polys = shape->splineCount;
		for(int poly = 0; poly < polys; ++poly) {
			if(shape->polySel[poly]) {
				Spline3D *spline = shape->splines[poly];
				if(!spline->Closed() && spline->KnotCount()>2) {
					altered = holdNeeded = 1;
					// Save the unmodified verts.
					PolyCloseRecord *rec = new PolyCloseRecord(poly);
					shapeData->AddChangeRecord(rec);
					if ( theHold.Holding() ) {
						theHold.Put(new ShapeRestore(shapeData,this,rec));
						}
					spline->SetClosed();
					shape->vertSel[poly].SetSize(spline->Verts(),1);
					shape->segSel[poly].SetSize(spline->Segments(),1);
					shape->segSel[poly].Clear(spline->Segments()-1);
					spline->ComputeBezPoints();
					shape->InvalidateGeomCache();
					}
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_CLOSESPLINE));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOVALIDSPLINESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	UpdatePolyVertCount();
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

// Detach all selected polygons
void EditSplineMod::DoPolyDetach(int copy, int reorient) {
	int dialoged = 0;
	TSTR newName(GetString(IDS_TH_SHAPE));
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	theHold.Begin();

	// Create a spline shape object
	SplineShape *splShape = new SplineShape;

	BOOL setSegs = FALSE;
	 
	int multipleObjects = (mcList.Count() > 1) ? 1 : 0;
	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;

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

		int polys = shape->splineCount;
		for(int poly = polys-1; poly >= 0; --poly) {
			if(shape->polySel[poly]) {
				Spline3D *spline = shape->splines[poly];
				if(!dialoged) {
					dialoged = 1;
					if(!GetDetachOptions(iObjParams, newName))
						goto bail_out;
					}
				altered = holdNeeded = 1;
				// Save the unmodified verts.
				PolyDetachRecord *rec = new PolyDetachRecord(shape,poly,copy);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				// If haven't set destination segments, do it now
				if(!setSegs) {
					setSegs = TRUE;
					splShape->shape.steps = shape->steps;
					splShape->shape.optimize = shape->optimize;
					}
				// Copy selected polys to a new output object
				Spline3D *newSpline = splShape->shape.NewSpline();
				*newSpline = *spline;
				if(multipleObjects && !reorient)
					newSpline->Transform(&nodes[i]->GetObjectTM(t));
				if(!copy)
					shape->DeleteSpline(poly);
				}
			}

		bail_out:
		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		INode *newNode = iObjParams->CreateObjectNode(splShape);
		newNode->SetName(newName.data());
		splShape->shape.UpdateSels();	// Make sure it readies the selection set info
		splShape->shape.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!
		theHold.Accept(GetString(IDS_TH_DETACHSPLINE));
		}
	else {
		delete splShape;	// Didn't need it after all!
		if(!dialoged)
			iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOSPLINESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	UpdatePolyVertCount();
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(t,REDRAW_NORMAL);
	}

// Mirror all selected polygons
void EditSplineMod::DoPolyMirror(int type, int copy) {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	theHold.Begin();

	for (int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;

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

		int polys = shape->splineCount;
		for(int poly = 0; poly < polys; ++poly) {
			if(shape->polySel[poly]) {
				Spline3D *spline = shape->splines[poly];
				altered = holdNeeded = 1;
				// Save the unmodified verts.
				PolyMirrorRecord *rec = new PolyMirrorRecord(poly,type,copy);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				MirrorPoly(shape, poly, type, copy);
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded) {
		theHold.Accept(GetString(IDS_TH_MIRROR));
		}
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOSPLINESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	UpdatePolyVertCount();
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(t,REDRAW_NORMAL);
	}

// Delete all selected polygons
void EditSplineMod::DoPolyDelete() {
	ModContextList mcList;		
	INodeTab nodes;
	TimeValue t = iObjParams->GetTime();
	int holdNeeded = 0;

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

	theHold.Begin();

	for ( int i = 0; i < mcList.Count(); i++ ) {
		int altered = 0;
		EditSplineData *shapeData = (EditSplineData*)mcList[i]->localData;
		if ( !shapeData ) continue;
		if ( shapeData->GetFlag(ESD_BEENDONE) ) continue;

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

		int polys = shape->splineCount;
		for(int poly = polys-1; poly >= 0; --poly) {
			if(shape->polySel[poly]) {
				Spline3D *spline = shape->splines[poly];
				altered = holdNeeded = 1;
				// Save the unmodified verts.
				PolyDeleteRecord *rec = new PolyDeleteRecord(shape,poly);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() ) {
					theHold.Put(new ShapeRestore(shapeData,this,rec));
					}
				shape->DeleteSpline(poly);
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_DELETESPLINE));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOSPLINESSEL),PROMPT_TIME);
		theHold.End();
		}

	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	UpdatePolyVertCount();
	NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
	iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
	}

int EditSplineMod::GetBoolCursorID() {
	switch(boolType) {
		case BOOL_UNION:
			return IDC_BOOLUNION;
		case BOOL_SUBTRACTION:
			return IDC_BOOLSUBTRACTION;
		case BOOL_INTERSECTION:
			return IDC_BOOLINTERSECTION;
		}
	assert(0);
	return IDC_BOOLUNION;
	}

int EditSplineMod::GetBoolMirrString(int type) {
	switch(type) {
		case BOOL_UNION:
			return IDS_TH_UNION;
		case BOOL_SUBTRACTION:
			return IDS_TH_SUBTRACTION;
		case BOOL_INTERSECTION:
			return IDS_TH_INTERSECTION;
		case MIRROR_HORIZONTAL:
			return IDS_TH_MIRROR_H;
		case MIRROR_VERTICAL:
			return IDS_TH_MIRROR_V;
		case MIRROR_BOTH:
			return IDS_TH_MIRROR_BOTH;
		}
	assert(0);
	return IDC_BOOLUNION;
	}

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

	// Initialize so there isn't any remembered shape
	rememberedShape = NULL;

	if ( !iObjParams ) return 0;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_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);
	SubShapeHitList hitList;

	int result = 0;

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

		// If the mesh isn't yet cache, this will cause it to get cached.
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) continue;
		INode *inode = nodes[i];
		Matrix3 mat = inode->GetObjectTM(t);
		gw->setTransform(mat);	
		if(shape->SubObjectHitTest(gw, gw->getMaterial(), &hr, SUBHIT_SHAPE_VERTS | HIT_ABORTONHIT, hitList )) {
			ShapeSubHitRec *hit = hitList.First();
			if(shape->vertSel[hit->poly][hit->index]) {
				rememberedShape = NULL;
				rememberedData = shape->splines[hit->poly]->GetKnotType(hit->index / 3);
				}
			else {
				if(iObjParams->SelectionFrozen())
					goto finish;
				// Select just this vertex
				VertSelRecord *rec = new VertSelRecord;
				rec->oldSel = shape->vertSel;
				shape->vertSel.ClearAll();
				shape->vertSel[hit->poly].Set(hit->index);
				rec->newSel = shape->vertSel;
			 	theHold.Begin();
				shapeData->StartChangeGroup();
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() )
					theHold.Put(new ShapeRestore(shapeData,this,rec));
				theHold.Accept(GetString(IDS_DS_SELECT));
				NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
				iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);

				rememberedShape = shape;
				rememberedPoly = hit->poly;
				rememberedIndex = hit->index / 3;	// Convert from bezier index to knot
				rememberedData = shape->splines[hit->poly]->GetKnotType(rememberedIndex);
				}
			result = 1;
			goto finish;
			}
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
finish:
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	if ( vpt ) iObjParams->ReleaseViewport(vpt);
	nodes.DisposeTemporary();

	return result;
	}

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

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

		int polys = shape->splineCount;
		for(int poly = polys - 1; poly >= 0; --poly) {
			Spline3D *spline = shape->splines[poly];
			// If any bits are set in the selection set, let's DO IT!!
			if(shape->vertSel[poly].NumberSet()) {
				altered = holdNeeded = TRUE;
				VertChangeRecord *rec = new VertChangeRecord(shape, poly, -1, type);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() )
					theHold.Put(new ShapeRestore(shapeData,this,rec));
				// Call the vertex type change function
				ChangeVertexType(shape, poly, -1, type);
				}
			}

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

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

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

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

			theHold.Begin();
			VertChangeRecord *rec = new VertChangeRecord(shape, rememberedPoly, rememberedIndex, type);
			shapeData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new ShapeRestore(shapeData,this,rec));
			// Call the vertex type change function
			ChangeVertexType(shape, rememberedPoly, rememberedIndex, type);
			shapeData->TempData(this)->Invalidate(PART_TOPO);
			theHold.Accept(GetString(IDS_TH_VERTCHANGE));
			ClearShapeDataFlag(mcList,ESD_BEENDONE);
			NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
			iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
			return;
 			}
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	}

void EditSplineMod::SetRememberedVertType(int type) {
	if(rememberedShape)
		ChangeRememberedVert(type);
	else
		ChangeSelVerts(type);
	}


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

	// Initialize so there isn't any remembered shape
	rememberedShape = NULL;

	if ( !iObjParams ) return 0;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

	int result = 0;

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

		// If the mesh isn't yet cache, this will cause it to get cached.
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) continue;
		INode *inode = nodes[i];
		Matrix3 mat = inode->GetObjectTM(t);
		gw->setTransform(mat);	
		if(shape->SubObjectHitTest(gw, gw->getMaterial(), &hr, SUBHIT_SHAPE_SEGMENTS | HIT_ABORTONHIT, hitList )) {
			ShapeSubHitRec *hit = hitList.First();
			if(shape->segSel[hit->poly][hit->index]) {
				rememberedShape = NULL;
				rememberedData = shape->splines[hit->poly]->GetLineType(hit->index);
				}
			else {
				if(iObjParams->SelectionFrozen())
					goto finish;
				// Select just this segment
				SegSelRecord *rec = new SegSelRecord;
				rec->oldSel = shape->segSel;
				shape->segSel.ClearAll();
				shape->segSel[hit->poly].Set(hit->index);
				rec->newSel = shape->segSel;
			 	theHold.Begin();
				shapeData->StartChangeGroup();
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() )
					theHold.Put(new ShapeRestore(shapeData,this,rec));
				theHold.Accept(GetString(IDS_DS_SELECT));
				NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
				iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);

				rememberedShape = shape;
				rememberedPoly = hit->poly;
				rememberedIndex = hit->index;
				rememberedData = shape->splines[rememberedPoly]->GetLineType(rememberedIndex);
				}
			result = 1;
			goto finish;
			}
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}

finish:
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	if ( vpt ) iObjParams->ReleaseViewport(vpt);
	nodes.DisposeTemporary();
	return result;
	}

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

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

		int polys = shape->splineCount;
		for(int poly = polys - 1; poly >= 0; --poly) {
			Spline3D *spline = shape->splines[poly];
			// If any bits are set in the selection set, let's DO IT!!
			if(shape->segSel[poly].NumberSet()) {
				altered = holdNeeded = TRUE;
				SegChangeRecord *rec = new SegChangeRecord(shape, poly, -1, type);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() )
					theHold.Put(new ShapeRestore(shapeData,this,rec));
				// Call the segment type change function
				ChangeSegmentType(shape, poly, -1, type);
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_SEGCHANGE));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOSEGSSEL),PROMPT_TIME);
		theHold.End();
		}

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

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

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

			theHold.Begin();
			SegChangeRecord *rec = new SegChangeRecord(shape, rememberedPoly, rememberedIndex, type);
			shapeData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new ShapeRestore(shapeData,this,rec));
			// Call the segment type change function
			ChangeSegmentType(shape, rememberedPoly, rememberedIndex, type);
			shapeData->TempData(this)->Invalidate(PART_TOPO);
			theHold.Accept(GetString(IDS_TH_SEGCHANGE));
			ClearShapeDataFlag(mcList,ESD_BEENDONE);
			NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
			iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
			nodes.DisposeTemporary();
			return;
 			}
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	nodes.DisposeTemporary();
	}

void EditSplineMod::SetRememberedSegType(int type) {
	if(rememberedShape)
		ChangeRememberedSeg(type);
	else
		ChangeSelSegs(type);
	}

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

	// Initialize so there isn't any remembered shape
	rememberedShape = NULL;

	if ( !iObjParams ) return 0;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

	int result = 0;

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

		// If the mesh isn't yet cache, this will cause it to get cached.
		BezierShape *shape = shapeData->TempData(this)->GetShape(t);
		if(!shape) continue;
		INode *inode = nodes[i];
		Matrix3 mat = inode->GetObjectTM(t);
		gw->setTransform(mat);	
		if(shape->SubObjectHitTest(gw, gw->getMaterial(), &hr, SUBHIT_SHAPE_POLYS | HIT_ABORTONHIT, hitList )) {
			ShapeSubHitRec *hit = hitList.First();
			if(shape->polySel[hit->poly]) {
				rememberedShape = NULL;
				}
			else {
				if(iObjParams->SelectionFrozen())
					goto finish;
				// Select just this poly
				PolySelRecord *rec = new PolySelRecord;
				rec->oldSel = shape->polySel;
				shape->polySel.ClearAll();
				shape->polySel.Set(hit->poly);
				rec->newSel = shape->polySel;
			 	theHold.Begin();
				shapeData->StartChangeGroup();
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() )
					theHold.Put(new ShapeRestore(shapeData,this,rec));
				theHold.Accept(GetString(IDS_DS_SELECT));
				NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
				iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);

				rememberedShape = shape;
				rememberedPoly = hit->poly;
				}
			result = 1;
			goto finish;
			}
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}

finish:
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	if ( vpt ) iObjParams->ReleaseViewport(vpt);
	nodes.DisposeTemporary();
	return result;
	}

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

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

		int polys = shape->splineCount;
		for(int poly = polys - 1; poly >= 0; --poly) {
			Spline3D *spline = shape->splines[poly];
			// If any bits are set in the selection set, let's DO IT!!
			if(shape->polySel[poly]) {
				altered = holdNeeded = TRUE;
				PolyChangeRecord *rec = new PolyChangeRecord(shape, poly, type);
				shapeData->AddChangeRecord(rec);
				if ( theHold.Holding() )
					theHold.Put(new ShapeRestore(shapeData,this,rec));
				// Call the polygon type change function
				ChangePolyType(shape, poly, type);
				}
			}

		if(altered)
			shapeData->TempData(this)->Invalidate(PART_TOPO);
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	
	if(holdNeeded)
		theHold.Accept(GetString(IDS_TH_SPLINECHANGE));
	else {
		iObjParams->DisplayTempPrompt(GetString(IDS_TH_NOSPLINESSEL),PROMPT_TIME);
		theHold.End();
		}

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

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

	if ( !iObjParams ) return;

	iObjParams->GetModContexts(mcList,nodes);
	ClearShapeDataFlag(mcList,ESD_BEENDONE);

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

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

			theHold.Begin();
			PolyChangeRecord *rec = new PolyChangeRecord(shape, rememberedPoly, type);
			shapeData->AddChangeRecord(rec);
			if ( theHold.Holding() )
				theHold.Put(new ShapeRestore(shapeData,this,rec));
			// Call the segment type change function
			ChangePolyType(shape, rememberedPoly, type);
			shapeData->TempData(this)->Invalidate(PART_TOPO);
			theHold.Accept(GetString(IDS_TH_SPLINECHANGE));
			ClearShapeDataFlag(mcList,ESD_BEENDONE);
			NotifyDependents(FOREVER, PART_TOPO, REFMSG_CHANGE);
			iObjParams->RedrawViews(iObjParams->GetTime(),REDRAW_NORMAL);
			nodes.DisposeTemporary();
			return;
 			}
		shapeData->SetFlag(ESD_BEENDONE,TRUE);
		}
	nodes.DisposeTemporary();
	ClearShapeDataFlag(mcList,ESD_BEENDONE);
	}

void EditSplineMod::SetRememberedPolyType(int type) {
	if(rememberedShape)
		ChangeRememberedPoly(type);
	else
		ChangeSelPolys(type);
	}

// Return TRUE if this modifier is just working on one object
BOOL EditSplineMod::SingleObjectMod() {
	ModContextList mcList;		
	INodeTab nodes;
	if ( !iObjParams ) return FALSE;
	iObjParams->GetModContexts(mcList,nodes);
	nodes.DisposeTemporary();
	return (mcList.Count() == 1) ? TRUE : FALSE;
	}

BezierShape *EditSplineMod::SingleObjectShape(INode **node) {
	ModContextList mcList;		
	INodeTab nodes;
	if ( !iObjParams ) return NULL;
	iObjParams->GetModContexts(mcList,nodes);
	if(mcList.Count() != 1) {
		nodes.DisposeTemporary();
		return NULL;
		}
	EditSplineData *shapeData = (EditSplineData*)mcList[0]->localData;
	if ( !shapeData ) {
		nodes.DisposeTemporary();
		return NULL;
		}
	if(node)
		*node = nodes[0];
	nodes.DisposeTemporary();
	return shapeData->TempData(this)->GetShape(iObjParams->GetTime());
	}

BOOL CALLBACK ShapeObjectParamDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{
	EditSplineMod *es = (EditSplineMod *)GetWindowLong( hDlg, GWL_USERDATA );
	if ( !es && message != WM_INITDIALOG ) return FALSE;
	
	switch ( message ) {
		case WM_INITDIALOG: {
		 	es = (EditSplineMod *)lParam;
		 	SetWindowLong( hDlg, GWL_USERDATA, (LONG)es );		 	
			ICustButton *but = GetICustButton(GetDlgItem(hDlg,IDC_ATTACH));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
			CheckDlgButton( hDlg, IDC_ATTACHREORIENT, attachReorient);
			but = GetICustButton(GetDlgItem(hDlg,IDC_CREATELINE));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
			EnableWindow(GetDlgItem(hDlg, IDC_CREATELINE), es->SingleObjectMod());
		 	return TRUE;
			}

		case WM_DESTROY:
			// Don't leave in one of our modes!
			es->iObjParams->DeleteMode(es->createLineMode);
			es->iObjParams->ClearPickMode();
			CancelEditSplineModes(es->iObjParams);
			return FALSE;
		
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			es->iObjParams->RollupMouseMessage(hDlg,message,wParam,lParam);
			return FALSE;		
		
		case WM_COMMAND:			
			switch ( LOWORD(wParam) ) {				
				case IDC_ATTACH: {
					ModContextList mcList;
					INodeTab nodes;
					// If the mode is on, turn it off and bail
					if (es->iObjParams->GetCommandMode()->ID() == CID_STDPICK) {
						es->iObjParams->SetStdCommandMode(CID_OBJMOVE);
						return FALSE;
						}
					// Want to turn on the mode.  Make sure we're valid first
					es->iObjParams->GetModContexts(mcList,nodes);
					if(mcList.Count() > 1) {
						es->iObjParams->DisplayTempPrompt(GetString(IDS_TH_CANHAVEONLYONESHAPE),PROMPT_TIME);
						ICustButton *but = GetICustButton(GetDlgItem(hDlg,IDC_ATTACH));
						but->SetCheck(FALSE);
						ReleaseICustButton(but);
						nodes.DisposeTemporary();
						return FALSE;
						}
					es->pickCB.es = es;
					es->iObjParams->SetPickMode(&es->pickCB);
					nodes.DisposeTemporary();
					break;
					}
				case IDC_ATTACHREORIENT:
					attachReorient = IsDlgButtonChecked( hDlg, IDC_ATTACHREORIENT);
					break;
				case IDC_CREATELINE:
					es->StartCreateLineMode();
					break;
				}
			break;
		}
	
	return FALSE;
	}

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

static HIMAGELIST hBoolImages = NULL;

static void LoadBoolImages()
	{
	if ( !hBoolImages ) {
		HBITMAP hBitmap, hMask;
		hBoolImages = ImageList_Create(16, 15, TRUE, 3, 0);
		hBitmap     = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_BOOLEANTYPES));
		hMask       = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MASK_BOOLEANTYPES));
		ImageList_Add(hBoolImages,hBitmap,hMask);
		DeleteObject(hBitmap);
		DeleteObject(hMask);
		}
	}	

void EditSplineMod::SetBooleanButton() {
	iUnion->SetCheck((boolType == BOOL_UNION) ? TRUE : FALSE);
	iSubtraction->SetCheck((boolType == BOOL_SUBTRACTION) ? TRUE : FALSE);
	iIntersection->SetCheck((boolType == BOOL_INTERSECTION) ? TRUE : FALSE);
	}

static HIMAGELIST hMirrorImages = NULL;

static void LoadMirrorImages()
	{
	if ( !hMirrorImages ) {
		HBITMAP hBitmap, hMask;
		hMirrorImages = ImageList_Create(16, 15, TRUE, 3, 0);
		hBitmap     = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MIRRORTYPES));
		hMask       = LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MASK_MIRRORTYPES));
		ImageList_Add(hMirrorImages,hBitmap,hMask);
		DeleteObject(hBitmap);
		DeleteObject(hMask);
		}
	}	

void EditSplineMod::SetMirrorButton() {
	iMirrorHorizontal->SetCheck((mirrorType == MIRROR_HORIZONTAL) ? TRUE : FALSE);
	iMirrorVertical->SetCheck((mirrorType == MIRROR_VERTICAL) ? TRUE : FALSE);
	iMirrorBoth->SetCheck((mirrorType == MIRROR_BOTH) ? TRUE : FALSE);
	}

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

void PolygonRightMenu::Init(RightClickMenuManager* manager, HWND hWnd, IPoint2 m) {
	if(es->RememberPolyThere(hWnd, m)) {
		int oldType = -1;
		int flags1, flags2;
		flags1 = flags2 = MF_STRING;
		manager->AddMenu(this, MF_SEPARATOR, 0, NULL);
		manager->AddMenu(this, flags1, LTYPE_CURVE, GetString(IDS_TH_CURVE));
		manager->AddMenu(this, flags2, LTYPE_LINE, GetString(IDS_TH_LINE));
		}
	}

void PolygonRightMenu::Selected(UINT id) {
	es->SetRememberedPolyType((int)id);
	}

PolygonRightMenu pMenu;

class PolygonDeleteUser : public EventUser {
	private:
		EditSplineMod *es;
	public:
		void Notify() { es->DoPolyDelete(); }
		void SetMod(EditSplineMod *es) { this->es = es; }
	};

static PolygonDeleteUser pDel;

BOOL CALLBACK ShapePolygonParamDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{
	EditSplineMod *es = (EditSplineMod *)GetWindowLong( hDlg, GWL_USERDATA );
	if ( !es && message != WM_INITDIALOG ) return FALSE;
	
	switch ( message ) {
		case WM_INITDIALOG: {
		 	LoadBoolImages();
		 	LoadMirrorImages();
		 	es = (EditSplineMod *)lParam;
		 	SetWindowLong( hDlg, GWL_USERDATA, (LONG)es );		 	
			CheckDlgButton( hDlg, IDC_DETACHCOPY, polyDetachCopy);
			CheckDlgButton( hDlg, IDC_DETACHREORIENT, polyDetachReorient);
		 	es->outlineSpin = GetISpinner(GetDlgItem(hDlg,IDC_OUTLINESPINNER));
			es->outlineSpin->SetLimits( -999999, 999999, FALSE );
			es->outlineSpin->LinkToEdit( GetDlgItem(hDlg,IDC_OUTLINEWIDTH), EDITTYPE_UNIVERSE );
			CheckDlgButton( hDlg, IDC_OUTCENTER, centeredOutline);
			ICustButton *but = GetICustButton(GetDlgItem(hDlg,IDC_OUTLINE));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
			but = GetICustButton(GetDlgItem(hDlg,IDC_BOOLEAN));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
		 	ICustToolbar *iToolbar = GetICustToolbar(GetDlgItem(hDlg,IDC_BOOLTYPE));
			iToolbar->SetImage(hBoolImages);
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,0,0,0,0,16,15,24,23,BOOL_UNION));
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,1,1,1,1,16,15,24,23,BOOL_SUBTRACTION));
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,2,2,2,1,16,15,24,23,BOOL_INTERSECTION));
			es->iUnion        = iToolbar->GetICustButton(BOOL_UNION);
			es->iSubtraction  = iToolbar->GetICustButton(BOOL_SUBTRACTION);
			es->iIntersection = iToolbar->GetICustButton(BOOL_INTERSECTION);
			ReleaseICustToolbar(iToolbar);
			es->SetBooleanButton();
		 	iToolbar = GetICustToolbar(GetDlgItem(hDlg,IDC_MIRRORTYPE));
			iToolbar->SetImage(hMirrorImages);
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,0,0,0,0,16,15,24,23,MIRROR_HORIZONTAL));
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,1,1,1,1,16,15,24,23,MIRROR_VERTICAL));
			iToolbar->AddTool(ToolButtonItem(CTB_CHECKBUTTON,2,2,2,1,16,15,24,23,MIRROR_BOTH));
			es->iMirrorHorizontal = iToolbar->GetICustButton(MIRROR_HORIZONTAL);
			es->iMirrorVertical   = iToolbar->GetICustButton(MIRROR_VERTICAL);
			es->iMirrorBoth       = iToolbar->GetICustButton(MIRROR_BOTH);
			ReleaseICustToolbar(iToolbar);
			es->SetMirrorButton();
			CheckDlgButton( hDlg, IDC_MIRRORCOPY, polyMirrorCopy);
			pMenu.SetMod(es);
			es->iObjParams->GetRightClickMenuManager()->Register(&pMenu);
			pDel.SetMod(es);
			es->iObjParams->RegisterDeleteUser(&pDel);
		 	return TRUE;
			}

		case WM_DESTROY:
			if ( es->outlineSpin ) {
				ReleaseISpinner(es->outlineSpin);
				es->outlineSpin = NULL;
				}
 			if ( es->iUnion ) {
				ReleaseICustButton(es->iUnion);
				es->iUnion = NULL;
				}
 			if ( es->iSubtraction ) {
				ReleaseICustButton(es->iSubtraction);
				es->iSubtraction = NULL;
				}
 			if ( es->iIntersection ) {
				ReleaseICustButton(es->iIntersection);
				es->iIntersection = NULL;
				}
 			if ( es->iMirrorHorizontal ) {
				ReleaseICustButton(es->iMirrorHorizontal);
				es->iMirrorHorizontal = NULL;
				}
 			if ( es->iMirrorVertical ) {
				ReleaseICustButton(es->iMirrorVertical);
				es->iMirrorVertical = NULL;
				}
 			if ( es->iMirrorBoth ) {
				ReleaseICustButton(es->iMirrorBoth);
				es->iMirrorBoth = NULL;
				}

			// Don't leave in one of our modes!
			es->iObjParams->DeleteMode(es->outlineMode);
			es->iObjParams->DeleteMode(es->booleanMode);
			CancelEditSplineModes(es->iObjParams);
			// Detach our right-menu stuff!
			es->iObjParams->GetRightClickMenuManager()->Unregister(&pMenu);
			es->iObjParams->UnRegisterDeleteUser(&pDel);
			return FALSE;

		case CC_SPINNER_CHANGE:
			switch ( LOWORD(wParam) ) {
				case IDC_OUTLINESPINNER: {
					float outSize = es->outlineSpin->GetFVal();
					if ( !es->inOutline )
						es->BeginOutlineMove(es->iObjParams->GetTime());
					es->OutlineMove( es->iObjParams->GetTime(), outSize );
					// If not being dragged, finish it now!
					if(!HIWORD(wParam))
						es->EndOutlineMove(es->iObjParams->GetTime(), (outSize == 0.0f) ? FALSE : TRUE);
					es->iObjParams->RedrawViews(es->iObjParams->GetTime(),REDRAW_INTERACTIVE);					
					}
					break;
				}
			break;

		case CC_SPINNER_BUTTONUP:
			switch( LOWORD(wParam) ) {
				case IDC_OUTLINESPINNER:
					if(es->inOutline) {
						float outSize = es->outlineSpin->GetFVal();
						es->EndOutlineMove(es->iObjParams->GetTime(), (outSize == 0.0f) ? FALSE : TRUE);
						}
					break;
				}
			return TRUE;

		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			es->iObjParams->RollupMouseMessage(hDlg,message,wParam,lParam);
			return FALSE;		
		
		case WM_COMMAND:			
			switch ( LOWORD(wParam) ) {				
				case IDC_CLOSE:
					es->DoPolyClose();
					break;
				case IDC_DETACH:
					es->DoPolyDetach(polyDetachCopy, polyDetachReorient);
					break;
				case IDC_DETACHCOPY:
					polyDetachCopy = IsDlgButtonChecked( hDlg, IDC_DETACHCOPY);
					break;
				case IDC_DETACHREORIENT:
					polyDetachReorient = IsDlgButtonChecked( hDlg, IDC_DETACHREORIENT);
					break;
				case IDC_OUTCENTER:
					centeredOutline = IsDlgButtonChecked( hDlg, IDC_OUTCENTER);
					if ( es->inOutline ) {
						es->OutlineMove( es->iObjParams->GetTime(),es->outlineSpin->GetFVal() );
						es->iObjParams->RedrawViews(es->iObjParams->GetTime(),REDRAW_INTERACTIVE);					
						}
					break;
				case IDC_OUTLINE:
					es->StartOutlineMode();
					break;
				case IDC_BOOLEAN: {
					if(!es->BooleanStartUp()) {
						ICustButton *but = GetICustButton(GetDlgItem(hDlg,IDC_BOOLEAN));
						but->SetCheck(FALSE);
						ReleaseICustButton(but);
						CancelEditSplineModes(es->iObjParams);
						}
					break;
					}
				case BOOL_UNION:
				case BOOL_SUBTRACTION:
				case BOOL_INTERSECTION:
					es->SetBoolOperation(LOWORD(wParam));
					es->SetBooleanButton();
					break;
				case IDC_MIRROR:
					es->DoPolyMirror(es->mirrorType,polyMirrorCopy);
					break;
				case IDC_MIRRORCOPY:
					polyMirrorCopy = IsDlgButtonChecked( hDlg, IDC_MIRRORCOPY);
					break;
				case IDC_DELETESPLINE:
					es->DoPolyDelete();
					break;
				case MIRROR_HORIZONTAL:
				case MIRROR_VERTICAL:
				case MIRROR_BOTH:
					es->SetMirrorOperation(LOWORD(wParam));
					es->SetMirrorButton();
					break;
				}
			break;
		case WM_NOTIFY:
			if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {
				LPTOOLTIPTEXT lpttt;
				lpttt = (LPTOOLTIPTEXT)lParam;
				lpttt->lpszText = GetString(es->GetBoolMirrString(lpttt->hdr.idFrom));
				}
			break;
		}
	
	return FALSE;
	}

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

void SegmentRightMenu::Init(RightClickMenuManager* manager, HWND hWnd, IPoint2 m) {
	if(es->RememberSegThere(hWnd, m)) {
		int oldType = -1;
		int flags1, flags2;
		flags1 = flags2 = MF_STRING;
		switch(es->rememberedData) {
			case LTYPE_CURVE:
				flags1 |= MF_CHECKED;
				break;
			case LTYPE_LINE:
				flags2 |= MF_CHECKED;
				break;
			}
		manager->AddMenu(this, MF_SEPARATOR, 0, NULL);
		manager->AddMenu(this, flags1, LTYPE_CURVE, GetString(IDS_TH_CURVE));
		manager->AddMenu(this, flags2, LTYPE_LINE, GetString(IDS_TH_LINE));
		}
	}

void SegmentRightMenu::Selected(UINT id) {
	es->SetRememberedSegType((int)id);
	}

SegmentRightMenu sMenu;

class SegmentDeleteUser : public EventUser {
	private:
		EditSplineMod *es;
	public:
		void Notify() { es->DoSegDelete(); }
		void SetMod(EditSplineMod *es) { this->es = es; }
	};

static SegmentDeleteUser sDel;

BOOL CALLBACK ShapeSegmentParamDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{	
	EditSplineMod *es = (EditSplineMod *)GetWindowLong( hDlg, GWL_USERDATA );
	if ( !es && message != WM_INITDIALOG ) return FALSE;
	
	switch ( message ) {
		case WM_INITDIALOG: {
		 	es = (EditSplineMod *)lParam;
		 	SetWindowLong( hDlg, GWL_USERDATA, (LONG)es );		 	
			CheckDlgButton( hDlg, IDC_DETACHCOPY, segDetachCopy);
			CheckDlgButton( hDlg, IDC_DETACHREORIENT, segDetachReorient);
			ICustButton *but = GetICustButton(GetDlgItem(hDlg,IDC_SEGBREAK));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
			but = GetICustButton(GetDlgItem(hDlg,IDC_REFINE));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
			sMenu.SetMod(es);
			es->iObjParams->GetRightClickMenuManager()->Register(&sMenu);
			sDel.SetMod(es);
			es->iObjParams->RegisterDeleteUser(&sDel);
		 	return TRUE;
			}

		case WM_DESTROY:			
			// Don't leave in one of our modes!
			es->iObjParams->DeleteMode(es->segRefineMode);
			es->iObjParams->DeleteMode(es->segBreakMode);
			CancelEditSplineModes(es->iObjParams);
			// Detach our right-menu stuff!
			es->iObjParams->GetRightClickMenuManager()->Unregister(&sMenu);
			es->iObjParams->UnRegisterDeleteUser(&sDel);
			return FALSE;
		
		case CC_SPINNER_CHANGE:
//			switch ( LOWORD(wParam) ) {
//				}
			break;

		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			es->iObjParams->RollupMouseMessage(hDlg,message,wParam,lParam);
			return FALSE;		
		
		case WM_COMMAND:			
			switch ( LOWORD(wParam) ) {				
				case IDC_SEGDELETE:
					es->DoSegDelete();
					break;
				case IDC_DETACH:
					es->DoSegDetach(segDetachCopy, segDetachReorient);
					break;
				case IDC_DETACHCOPY:
					segDetachCopy = IsDlgButtonChecked( hDlg, IDC_DETACHCOPY);
					break;
				case IDC_DETACHREORIENT:
					segDetachReorient = IsDlgButtonChecked( hDlg, IDC_DETACHREORIENT);
					break;
				case IDC_SEGBREAK:
					es->StartSegBreakMode();
					break;
				case IDC_REFINE:
					es->StartSegRefineMode(REFINE_SEG);
					break;
				}
			break;
		}
	
	return FALSE;
	}

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

void VertexRightMenu::Init(RightClickMenuManager* manager, HWND hWnd, IPoint2 m) {
	if(es->RememberVertThere(hWnd, m)) {
		int oldType = -1;
		int flags1, flags2, flags3, flags4;
		flags1 = flags2 = flags3 = flags4 = MF_STRING;
		switch(es->rememberedData) {
			case KTYPE_AUTO:
				flags1 |= MF_CHECKED;
				break;
			case KTYPE_CORNER:
				flags2 |= MF_CHECKED;
				break;
			case KTYPE_BEZIER:
				flags3 |= MF_CHECKED;
				break;
			case KTYPE_BEZIER_CORNER:
				flags4 |= MF_CHECKED;
				break;
			}
		manager->AddMenu(this, MF_SEPARATOR, 0, NULL);
		manager->AddMenu(this, flags1, KTYPE_AUTO, GetString(IDS_TH_SMOOTH));
		manager->AddMenu(this, flags2, KTYPE_CORNER, GetString(IDS_TH_CORNER));
		manager->AddMenu(this, flags3, KTYPE_BEZIER, GetString(IDS_TH_BEZIER));
		// TH 5/14/97 -- These next 3 lines are a kludge to get around some strangeness
		// that prevented the 5th entry from appearing.  Inserting 3 dummy entries fixes it!
		// God, I hate this kind of crap...
		manager->AddMenu(this, MF_SEPARATOR, 0, NULL);
		manager->AddMenu(this, MF_SEPARATOR, 0, NULL);
		manager->AddMenu(this, MF_SEPARATOR, 0, NULL);
		manager->AddMenu(this, flags4, KTYPE_BEZIER_CORNER, GetString(IDS_TH_BEZIERCORNER));
		}
	}

void VertexRightMenu::Selected(UINT id) {
	es->SetRememberedVertType((int)id);
	}

VertexRightMenu vMenu;

class VertexDeleteUser : public EventUser {
	private:
		EditSplineMod *es;
	public:
		void Notify() { es->DoVertDelete(); }
		void SetMod(EditSplineMod *es) { this->es = es; }
	};

static VertexDeleteUser vDel;

BOOL CALLBACK ShapeVertexParamDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{
	EditSplineMod *es = (EditSplineMod *)GetWindowLong( hDlg, GWL_USERDATA );
	if ( !es && message != WM_INITDIALOG ) return FALSE;
	
	switch ( message ) {
		case WM_INITDIALOG: {
		 	es = (EditSplineMod *)lParam;
		 	SetWindowLong( hDlg, GWL_USERDATA, (LONG)es );		 	
			CheckDlgButton( hDlg, IDC_LOCK_HANDLES, lockedHandles);
			CheckRadioButton( hDlg, IDC_LOCKALIKE, IDC_LOCKALL, lockType);
			ICustButton *but = GetICustButton(GetDlgItem(hDlg,IDC_CONNECT));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
			but = GetICustButton(GetDlgItem(hDlg,IDC_INSERT));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
			but = GetICustButton(GetDlgItem(hDlg,IDC_VERTREFINE));
			but->SetHighlightColor(GREEN_WASH);
			but->SetType(CBT_CHECK);
			ReleaseICustButton(but);
		 	es->weldSpin = GetISpinner(GetDlgItem(hDlg,IDC_THRESHSPINNER));
			es->weldSpin->SetLimits( 0, 999999, FALSE );
			es->weldSpin->LinkToEdit( GetDlgItem(hDlg,IDC_WELDTHRESH), EDITTYPE_UNIVERSE );
			es->weldSpin->SetValue(weldThreshold,FALSE);
			vMenu.SetMod(es);
			es->iObjParams->GetRightClickMenuManager()->Register(&vMenu);
			vDel.SetMod(es);
			es->iObjParams->RegisterDeleteUser(&vDel);
		 	return TRUE;
			}

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

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

		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:   			
   			es->iObjParams->RollupMouseMessage(hDlg,message,wParam,lParam);
			return FALSE;		
		
		case WM_COMMAND:			
			switch ( LOWORD(wParam) ) {				
				case IDC_VERTDELETE:
					es->DoVertDelete();
					break;
				case IDC_VERTBREAK:
					es->DoVertBreak();
					break;
				case IDC_VERTREFINE:
					es->StartSegRefineMode(REFINE_VERT);		// Double-duty here in the vertex branch
					break;
				case IDC_CONNECT:
					es->StartVertConnectMode();
					break;
				case IDC_INSERT:
					es->StartVertInsertMode();
					break;
				case IDC_WELD:
					es->DoVertWeld();
					break;
				case IDC_MAKEFIRST:
					es->DoMakeFirst();
					break;
				case IDC_LOCK_HANDLES:
					lockedHandles = IsDlgButtonChecked( hDlg, IDC_LOCK_HANDLES);
					break;
				case IDC_LOCKALIKE:
				case IDC_LOCKALL:
					lockType = LOWORD(wParam);
					break;
				}
			break;
		}
	
	return FALSE;
	}

#define OLD_SEL_LEVEL_CHUNK 0x1000	// Old backwards ordering
#define SEL_LEVEL_CHUNK 0x1001

IOResult EditSplineMod::Save(ISave *isave) {
	Modifier::Save(isave);
	Interval valid;
	ULONG nb;
	short sl = selLevel;
	isave->BeginChunk(SEL_LEVEL_CHUNK);
	isave->Write(&sl,sizeof(short),&nb);
	isave->	EndChunk();
	return IO_OK;
	}

IOResult EditSplineMod::Load(ILoad *iload) {
	Modifier::Load(iload);
	IOResult res;
	ULONG nb;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case OLD_SEL_LEVEL_CHUNK:
				{
				short sl;
				res = iload->Read(&sl,sizeof(short),&nb);
				selLevel = sl;
				switch(selLevel) {
					case 1:
						selLevel = ES_SPLINE;
						break;
					case 3:
						selLevel = ES_VERTEX;
						break;
					}
				}
				break;
			case SEL_LEVEL_CHUNK:
				res = iload->Read(&selLevel,sizeof(int),&nb);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}


#define EDITSPLINEDATA_CHUNK 0x1000

IOResult EditSplineMod::SaveLocalData(ISave *isave, LocalModData *ld) {
	EditSplineData *es = (EditSplineData *)ld;

	isave->BeginChunk(EDITSPLINEDATA_CHUNK);
	es->Save(isave);
	isave->EndChunk();

	return IO_OK;
	}

IOResult EditSplineMod::LoadLocalData(ILoad *iload, LocalModData **pld) {
	IOResult res;
	EditSplineData *es;
	if (*pld==NULL) {
		*pld =(LocalModData *) new EditSplineData();
		}
	es = (EditSplineData *)*pld;

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

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

