/**********************************************************************
 *<
	FILE: surfctrl.cpp

	DESCRIPTION: A controller that moves an object along a surface

	CREATED BY: Rolf Berteig

	HISTORY: created 2/13/97

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

#include "ctrl.h"
#include "units.h"
#include "interpik.h"
#include "istdplug.h"

#define SURF_CONTROL_CNAME		GetString(IDS_RB_SURF)

#define U_REF		0
#define V_REF		1
#define SURF_REF	2

class SurfPosition;

class PickSurfMode : 
		public PickModeCallback,
		public PickNodeCallback {
	public:		
		SurfPosition *cont;
		
		PickSurfMode(SurfPosition *c) {cont=c;}
		BOOL HitTest(IObjParam *ip,HWND hWnd,ViewExp *vpt,IPoint2 m,int flags);
		BOOL Pick(IObjParam *ip,ViewExp *vpt);
		void EnterMode(IObjParam *ip);
		void ExitMode(IObjParam *ip);
		BOOL RightClick(IObjParam *ip,ViewExp *vpt)	{return TRUE;}
		BOOL Filter(INode *node);		
		PickNodeCallback *GetFilter() {return this;}
	};

class SurfPosition : public ISurfPosition, public TimeChangeCallback {
	public:
		Control *uCont, *vCont;
		INode *surf;
		int align;
		BOOL flip;

		Point3 curval;
		Quat curRot;
		Interval ivalid;

		static HWND hWnd;
		static IObjParam *ip;
		static ICustButton *iPick;		
		static PickSurfMode *pickMode;
		static ISpinnerControl *iU, *iV;
		static SurfPosition *editCont;

		SurfPosition();
		~SurfPosition();

		// Animatable methods
		Class_ID ClassID() {return SURF_CONTROL_CLASSID;}  
		SClass_ID SuperClassID() { return CTRL_POSITION_CLASS_ID; } 
		void GetClassName(TSTR& s) {s = SURF_CONTROL_CNAME;}
		void DeleteThis() {delete this;}
		int IsKeyable() {return 0;}
		int NumSubs()  {return 2;}
		Animatable* SubAnim(int i);
		TSTR SubAnimName(int i);
		int SubNumToRefNum(int subNum);
		ParamDimension* GetParamDimension(int i) {return stdPercentDim;}
		BOOL AssignController(Animatable *control,int subAnim);
		void BeginEditParams(IObjParam *ip, ULONG flags,Animatable *prev);
		void EndEditParams(IObjParam *ip, ULONG flags,Animatable *next); 
		int SetProperty(ULONG id, void *data);
		void *GetProperty(ULONG id);

		// Reference methods
		int NumRefs() {return 3;};
		RefTargetHandle GetReference(int i);
		void SetReference(int i, RefTargetHandle rtarg);
		RefResult NotifyRefChanged(Interval, RefTargetHandle, PartID&, RefMessage);
		RefTargetHandle Clone(RemapDir& remap);
		IOResult Save(ISave *isave);
		IOResult Load(ILoad *iload);

		// Control methods
		void Copy(Control *from);		
		BOOL IsLeaf() {return FALSE;}
		void GetValue(TimeValue t, void *val, Interval &valid, GetSetMethod method);	
		void SetValue(TimeValue t, void *val, int commit, GetSetMethod method) {}
		void CommitValue(TimeValue t);
		void RestoreValue(TimeValue t);
		void EnumIKParams(IKEnumCallback &callback);
		BOOL CompDeriv(TimeValue t,Matrix3& ptm,IKDeriv& derivs,DWORD flags);
		float IncIKParam(TimeValue t,int index,float delta);
		void ClearIKParam(Interval iv,int index);

		// TimeChangeCallback methods
		void TimeChanged(TimeValue t);

		// Local methods
		void SetSurface(INode *node);
		void SetupDialog(HWND hWnd);
		void DestroyDialog();
		void UpdateDialog();
		void GetUV(TimeValue t,float &u, float &v,Interval *iv=NULL);
		Point3 DU(TimeValue t,float u, float v);
		Point3 DV(TimeValue t,float u, float v);
		Point3 Norm(TimeValue t,float u, float v);
		Point3 Pos(TimeValue t,float u, float v,Interval *iv=NULL);

		int GetAlign() {return align;}
		void SetAlign(int a);
		BOOL GetFlip() {return flip;}
		void SetFlip(BOOL f);
	};

class JointParamsSurf : public JointParams {
	public:			 	
		JointParamsSurf() : JointParams((DWORD)JNT_POS,2,1.0f) {}
		void SpinnerChange(InterpCtrlUI *ui,WORD id,ISpinnerControl *spin,BOOL interactive);
	};

//--- ClassDesc and class vars -------------------------------------

class SurfClassDesc:public ClassDesc {
	public:
	int 			IsPublic() {return 1;}
	void *			Create(BOOL loading) { return new SurfPosition();}
	const TCHAR *	ClassName() {return SURF_CONTROL_CNAME;}
	SClass_ID		SuperClassID() {return CTRL_POSITION_CLASS_ID;}
	Class_ID		ClassID() {return SURF_CONTROL_CLASSID;}
	const TCHAR* 	Category() {return _T("");}
	};
static SurfClassDesc surfCD;
ClassDesc* GetSurfCtrlDesc() {return &surfCD;}


HWND             SurfPosition::hWnd     = NULL;
IObjParam       *SurfPosition::ip       = NULL;
ICustButton     *SurfPosition::iPick    = NULL;
ISpinnerControl *SurfPosition::iU       = NULL;
ISpinnerControl *SurfPosition::iV       = NULL;
PickSurfMode    *SurfPosition::pickMode = NULL;
SurfPosition    *SurfPosition::editCont = NULL;

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

//--- SurfPosition methods ------------------------------------------

SurfPosition::SurfPosition()
	{
	uCont = vCont = NULL;
	surf = NULL;
	ReplaceReference(U_REF,NewDefaultFloatController());
	ReplaceReference(V_REF,NewDefaultFloatController());
	align = IDC_ALIGN_NONE;
	flip  = FALSE;
	ivalid.SetEmpty();	
	}

SurfPosition::~SurfPosition()
	{
	DeleteAllRefsFromMe();
	}

Animatable* SurfPosition::SubAnim(int i)
	{
	switch (i) {
		case 0: return uCont; break;
		case 1: return vCont; break;
		};
	return NULL;
	}

TSTR SurfPosition::SubAnimName(int i)
	{
	switch (i) {
		case 0: return GetString(IDS_RB_U); break;
		case 1: return GetString(IDS_RB_V); break;
		}
	return _T("");
	}

int SurfPosition::SubNumToRefNum(int subNum)
	{
	switch (subNum) {
		case 0: return 0;
		case 1: return 1;
		default: return -1;
		}
	}

BOOL SurfPosition::AssignController(
		Animatable *control,int subAnim)
	{
	ReplaceReference(subAnim,(ReferenceTarget*)control);
	ivalid.SetEmpty();
	NotifyDependents(FOREVER,0,REFMSG_SUBANIM_STRUCTURE_CHANGED);
	NotifyDependents(FOREVER,0,REFMSG_CHANGE);
	return TRUE;
	}

void SurfPosition::BeginEditParams(
		IObjParam *ip, ULONG flags,Animatable *prev)
	{
	this->ip = ip;
	editCont = this;
	if (flags&BEGIN_EDIT_HIERARCHY) {		
		if (align!=IDC_ALIGN_NONE) return;

		JointParamsSurf *jp = (JointParamsSurf*)GetProperty(PROPID_JOINTPARAMS);
		InterpCtrlUI *ui;	

		if (!jp) {
			jp = new JointParamsSurf();
			SetProperty(PROPID_JOINTPARAMS,jp);
			}

		if (prev &&
			prev->ClassID()==ClassID() && 
		    (ui = (InterpCtrlUI*)prev->GetProperty(PROPID_INTERPUI))) {
			JointParams *prevjp = (JointParams*)prev->GetProperty(PROPID_JOINTPARAMS);
			prevjp->EndDialog(ui);
			ui->cont = this;
			ui->ip   = ip;
			prev->SetProperty(PROPID_INTERPUI,NULL);
			JointDlgData *jd = (JointDlgData*)GetWindowLong(ui->hParams,GWL_USERDATA);
			jd->jp = jp;
			jp->InitDialog(ui);
		} else {
			ui = new InterpCtrlUI(NULL,ip,this);
			DWORD f=0;
			if (!jp || !jp->RollupOpen()) f = APPENDROLL_CLOSED;	

			ui->hParams = ip->AddRollupPage( 
				hInstance, 
				MAKEINTRESOURCE(IDD_SURFJOINTPARAMS),
				(int (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,long))JointParamDlgProc,
				GetString(IDS_RB_SURFJOINTPARAMS), 
				(LPARAM)new JointDlgData(ui,jp),f);	
			}
	
		SetProperty(PROPID_INTERPUI,ui);
	} else {
		pickMode = new PickSurfMode(this);
		hWnd     = ip->AddRollupPage( 
			hInstance, 
			MAKEINTRESOURCE(IDD_SURFPARAMS),
			SurfParamDlgProc,
			GetString(IDS_RB_SURFPARAMS), 
			(LPARAM)this );				
		ip->RegisterTimeChangeCallback(this);
		}
	}

void SurfPosition::EndEditParams(
		IObjParam *ip, ULONG flags,Animatable *next)
	{
	editCont = NULL;
	this->ip = NULL;
	if (next && next->ClassID()==ClassID() && !hWnd) return;

	if (hWnd) {
		ip->UnRegisterTimeChangeCallback(this);
		ip->ClearPickMode();
		delete pickMode;
		pickMode = NULL;		
		ip->DeleteRollupPage(hWnd);
		hWnd = NULL;
	} else {
		int index = aprops.FindProperty(PROPID_INTERPUI);
		if (index>=0) {
			InterpCtrlUI *ui = (InterpCtrlUI*)aprops[index];
			if (ui->hParams) {
				ip->UnRegisterDlgWnd(ui->hParams);
				ip->DeleteRollupPage(ui->hParams);			
				}
			index = aprops.FindProperty(PROPID_INTERPUI);
			if (index>=0) {
				delete aprops[index];
				aprops.Delete(index,1);
				}
			}	
		}
	}

RefTargetHandle SurfPosition::GetReference(int i)
	{
	switch (i) {
		case U_REF: return uCont;
		case V_REF: return vCont;
		case SURF_REF: return surf;
		}
	return NULL;
	}

void SurfPosition::SetReference(int i, RefTargetHandle rtarg)
	{
	switch (i) {
		case U_REF: uCont = (Control*)rtarg; break;
		case V_REF: vCont = (Control*)rtarg; break;
		case SURF_REF: surf = (INode*)rtarg; break;
		}
	}

RefResult SurfPosition::NotifyRefChanged(
		Interval iv, RefTargetHandle hTarg, 
		PartID &partID, RefMessage msg)
	{
	switch (msg) {
		case REFMSG_CHANGE:
			ivalid.SetEmpty();
			break;

		case REFMSG_TARGET_DELETED:
			if (hTarg == surf) {
				surf = NULL;
				if (hWnd && editCont == this) {
					SetWindowText(GetDlgItem(hWnd,IDC_SURFNAME),
						GetString(IDS_RB_NONE));
					}
				}			
			break;
		}
	return REF_SUCCEED;
	}

void SurfPosition::SetAlign(int a)
	{
	align = a;
	NotifyDependents(FOREVER,0,REFMSG_CHANGE);
	}

void SurfPosition::SetFlip(BOOL f)
	{
	flip = f;
	NotifyDependents(FOREVER,0,REFMSG_CHANGE);
	}

RefTargetHandle SurfPosition::Clone(RemapDir& remap)
	{
	SurfPosition *cont = new SurfPosition;
	cont->ReplaceReference(U_REF,remap.CloneRef(uCont));
	cont->ReplaceReference(V_REF,remap.CloneRef(vCont));
	cont->ReplaceReference(SURF_REF,surf);
	cont->align = align;
	cont->flip  = flip;
	return cont;
	}

#define ALIGN_CHUNK				0x0100
#define FLIP_CHUNK				0x0110
#define JOINTPARAMSURF_CHUNK	0x0200

IOResult SurfPosition::Save(ISave *isave)
	{
	JointParamsSurf *jp = (JointParamsSurf*)GetProperty(PROPID_JOINTPARAMS);
	ULONG nb;

	if (jp) {
		isave->BeginChunk(JOINTPARAMSURF_CHUNK);
		jp->Save(isave);
		isave->EndChunk();
		}

	isave->BeginChunk(ALIGN_CHUNK);
	isave->Write(&align,sizeof(align),&nb);
	isave->EndChunk();

	if (flip) {
		isave->BeginChunk(FLIP_CHUNK);	
		isave->EndChunk();
		}
	return IO_OK;
	}

IOResult SurfPosition::Load(ILoad *iload)
	{
	ULONG nb;
	IOResult res = IO_OK;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch (iload->CurChunkID()) {
			case ALIGN_CHUNK:
				res=iload->Read(&align,sizeof(align),&nb);
				break;

			case JOINTPARAMSURF_CHUNK: {
				JointParamsSurf *jp = new JointParamsSurf;
				jp->Load(iload);
				SetProperty(PROPID_JOINTPARAMS,jp);
				break;
				}

			case FLIP_CHUNK:
				flip = TRUE;
				break;
			}		
		iload->CloseChunk();
		if (res!=IO_OK)  return res;
		}
	return IO_OK;
	}

void SurfPosition::Copy(Control *from)
	{
	if (from->ClassID()==ClassID()) {
		SurfPosition *cont = (SurfPosition*)from;
		ReplaceReference(U_REF,cont->uCont->Clone());
		ReplaceReference(V_REF,cont->vCont->Clone());
		ReplaceReference(SURF_REF,cont->surf);
		align = cont->align;
		flip  = cont->flip;
		}
	}

void SurfPosition::GetUV(
		TimeValue t,float &u, float &v,Interval *iv)
	{	
	Interval valid;
	if (!iv) iv = &valid;	
	uCont->GetValue(t,&u,*iv);
	vCont->GetValue(t,&v,*iv);
	}

Point3 SurfPosition::DU(TimeValue t,float u, float v)
	{
	return (Pos(t,u+0.01f,v)-Pos(t,u,v))/0.01f;
	}

Point3 SurfPosition::DV(TimeValue t,float u, float v)
	{
	return (Pos(t,u,v+0.01f)-Pos(t,u,v))/0.01f;
	}

Point3 SurfPosition::Norm(TimeValue t,float u, float v)
	{
	Point3 du = DU(t,u,v);
	Point3 dv = DV(t,u,v);
	return Normalize(du^dv);
	}

Point3 SurfPosition::Pos(
		TimeValue t,float u, float v,Interval *iv)
	{	
	Interval valid;
	if (!iv) iv = &valid;
	if (surf) {
		ObjectState os = surf->EvalWorldState(0);		
		return os.obj->GetSurfacePoint(t,u,v,*iv) *
			surf->GetObjTMBeforeWSM(t,iv);
	} else {
		return Point3(0,0,0);
		}
	}

void SurfPosition::GetValue(
		TimeValue t, void *val, Interval &valid, 
		GetSetMethod method)
	{
	if (!ivalid.InInterval(t)) {
		float u, v;
		ivalid = FOREVER;
		GetUV(t,u,v,&ivalid);
		curval = Pos(t,u,v,&ivalid);
		if (align!=IDC_ALIGN_NONE) {
			Matrix3 tm(1);
			Point3 norm = Norm(t,u,v);
			tm.SetRow(2,flip?-norm:norm);
			if (align==IDC_ALIGN_U) 
				 tm.SetRow(0,Normalize(DU(t,u,v)));
			else tm.SetRow(0,Normalize(DV(t,u,v)));
			tm.SetRow(1,Normalize(tm.GetRow(2)^tm.GetRow(0)));
			curRot = Quat(tm);
			}
		}

	if (method==CTRL_RELATIVE) {
		Matrix3 *mat = (Matrix3*)val;
		if (align!=IDC_ALIGN_NONE) {
			curRot.MakeMatrix(*mat);
			}
		mat->SetTrans(curval);		
	} else {
		*((Point3*)val) = curval;
		}
	valid &= ivalid;
	}

void SurfPosition::CommitValue(TimeValue t)
	{
	uCont->CommitValue(t);
	vCont->CommitValue(t);
	}

void SurfPosition::RestoreValue(TimeValue t)
	{
	uCont->RestoreValue(t);
	vCont->RestoreValue(t);
	}

void SurfPosition::TimeChanged(TimeValue t)
	{
	UpdateDialog();
	}

void SurfPosition::SetSurface(INode *node)
	{
	ivalid.SetEmpty();
	ReplaceReference(SURF_REF,node);
	NotifyDependents(FOREVER,0,REFMSG_CHANGE);
	UpdateDialog();
	}


//--- PickSurfMode methods -----------------------------------------

BOOL PickSurfMode::HitTest(
		IObjParam *ip,HWND hWnd,ViewExp *vpt,
		IPoint2 m,int flags)
	{
	INode *node = cont->ip->PickNode(hWnd,m);
	return node?TRUE:FALSE;
	}

BOOL PickSurfMode::Pick(IObjParam *ip,ViewExp *vpt)
	{
	INode *node = vpt->GetClosestHit();
	if (node) {
		cont->SetSurface(node);
		ip->RedrawViews(ip->GetTime());
		}
	return TRUE;
	}

void PickSurfMode::EnterMode(IObjParam *ip)
	{
	cont->iPick->SetCheck(TRUE);
	}

void PickSurfMode::ExitMode(IObjParam *ip)
	{
	cont->iPick->SetCheck(FALSE);
	}

BOOL PickSurfMode::Filter(INode *node)
	{
	if (node) {
		ObjectState os = node->EvalWorldState(0);
		if (os.obj->IsParamSurface()) return TRUE;
		}
	return FALSE;
	}


//--- UI -------------------------------------------------------------

static BOOL CALLBACK SurfParamDlgProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	SurfPosition *co = (SurfPosition*)GetWindowLong(hWnd,GWL_USERDATA);
	switch (msg) {
		case WM_INITDIALOG:
			co = (SurfPosition*)lParam;
			SetWindowLong(hWnd, GWL_USERDATA, (LONG)co);
			co->SetupDialog(hWnd);
			break;

		case CC_SPINNER_BUTTONDOWN:
			theHold.Begin();
			break;

		case WM_CUSTEDIT_ENTER:
		case CC_SPINNER_BUTTONUP:
			if (HIWORD(wParam)) {
				theHold.Accept(GetString(IDS_RB_CHANGEUV));
			} else {
				theHold.Cancel();
				}
			co->ip->RedrawViews(co->ip->GetTime());
			break;

		case CC_SPINNER_CHANGE: {
			float f;
			switch (LOWORD(wParam)) {
				case IDC_SURF_USPIN:
					f = co->iU->GetFVal();
					co->uCont->SetValue(co->ip->GetTime(),&f);
					break;
				case IDC_SURF_VSPIN:
					f = co->iV->GetFVal();
					co->vCont->SetValue(co->ip->GetTime(),&f);
					break;
				}
			co->ip->RedrawViews(co->ip->GetTime());
			break;
			}

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_ALIGN_FLIP:
					co->flip = IsDlgButtonChecked(hWnd,IDC_ALIGN_FLIP);
					co->ivalid.SetEmpty();
					co->NotifyDependents(FOREVER,0,REFMSG_CHANGE);
					co->ip->RedrawViews(co->ip->GetTime());
					break;

				case IDC_ALIGN_NONE:
				case IDC_ALIGN_U:
				case IDC_ALIGN_V:
					co->align = LOWORD(wParam);
					co->ivalid.SetEmpty();
					co->NotifyDependents(FOREVER,0,REFMSG_CHANGE);
					co->ip->RedrawViews(co->ip->GetTime());
					co->UpdateDialog();
					break;

				case IDC_PICKSURF:
					co->ip->SetPickMode(co->pickMode);
					break;
				}
			break;

		case WM_LBUTTONDOWN:case WM_LBUTTONUP:	case WM_MOUSEMOVE:
			co->ip->RollupMouseMessage(hWnd,msg,wParam,lParam);
			return FALSE;

		case WM_DESTROY:
			co->DestroyDialog();
			break;

		default:
			return FALSE;
		}
	return TRUE;
	}

void SurfPosition::SetupDialog(HWND hWnd)
	{
	this->hWnd = hWnd;

	iPick = GetICustButton(GetDlgItem(hWnd,IDC_PICKSURF));
	iPick->SetType(CBT_CHECK);
	iPick->SetHighlightColor(GREEN_WASH);

	iU = GetISpinner(GetDlgItem(hWnd,IDC_SURF_USPIN));			
	iU->SetLimits(-999999999.0f,999999999.0f,FALSE);			
	iU->SetAutoScale();
	iU->LinkToEdit(GetDlgItem(hWnd,IDC_SURF_U),EDITTYPE_FLOAT);
	
	iV = GetISpinner(GetDlgItem(hWnd,IDC_SURF_VSPIN));
	iV->SetLimits(-999999999.0f,999999999.0f,FALSE);			
	iV->SetAutoScale();
	iV->LinkToEdit(GetDlgItem(hWnd,IDC_SURF_V),EDITTYPE_FLOAT);
	
	UpdateDialog();
	}

void SurfPosition::UpdateDialog()
	{	
	TimeValue t = ip->GetTime();
	float f;
	uCont->GetValue(t,&f,FOREVER);
	iU->SetValue(f,FALSE);
	vCont->GetValue(t,&f,FOREVER);
	iV->SetValue(f,FALSE);

	CheckDlgButton(hWnd,align,TRUE);
	CheckDlgButton(hWnd,IDC_ALIGN_FLIP,flip);
	if (align!=IDC_ALIGN_NONE)
		 EnableWindow(GetDlgItem(hWnd,IDC_ALIGN_FLIP),TRUE);
	else EnableWindow(GetDlgItem(hWnd,IDC_ALIGN_FLIP),FALSE);

	if (surf) {
		SetWindowText(GetDlgItem(hWnd,IDC_SURFNAME),
			surf->GetName());
	} else {
		SetWindowText(GetDlgItem(hWnd,IDC_SURFNAME),
			GetString(IDS_RB_NONE));
		}
	}

void SurfPosition::DestroyDialog()
	{
	ReleaseISpinner(iU); iU = NULL;
	ReleaseISpinner(iV); iV = NULL;
	ReleaseICustButton(iPick); iPick = NULL;
	}


//--- IK -----------------------------------------------------------

int SurfPosition::SetProperty(ULONG id, void *data)
	{
	if (id==PROPID_JOINTPARAMS) {		
		if (!data) {
			int index = aprops.FindProperty(id);
			if (index>=0) {
				aprops.Delete(index,1);
				}
		} else {
			JointParamsSurf *jp = (JointParamsSurf*)GetProperty(id);
			if (jp) {
				*jp = *((JointParamsSurf*)data);
				delete (JointParamsSurf*)data;
			} else {
				aprops.Append(1,(AnimProperty**)&data);
				}					
			}
		return 1;
	} else
	if (id==PROPID_INTERPUI) {		
		if (!data) {
			int index = aprops.FindProperty(id);
			if (index>=0) {
				aprops.Delete(index,1);
				}
		} else {
			InterpCtrlUI *ui = (InterpCtrlUI*)GetProperty(id);
			if (ui) {
				*ui = *((InterpCtrlUI*)data);
			} else {
				aprops.Append(1,(AnimProperty**)&data);
				}					
			}
		return 1;
	} else {
		return Animatable::SetProperty(id,data);
		}
	}

void* SurfPosition::GetProperty(ULONG id)
	{
	if (id==PROPID_INTERPUI || id==PROPID_JOINTPARAMS) {
		int index = aprops.FindProperty(id);
		if (index>=0) {
			return aprops[index];
		} else {
			return NULL;
			}
	} else {
		return Animatable::GetProperty(id);
		}
	}

void JointParamsSurf::SpinnerChange(
		InterpCtrlUI *ui,WORD id,ISpinnerControl *spin,
		BOOL interactive)
	{
	float val;
	int set = 0;

	switch (id) {
		case IDC_XFROMSPIN:
			val = min[0] = spin->GetFVal()/scale; 
			set = 1;
			break;
		case IDC_XTOSPIN:
			val = max[0] = spin->GetFVal()/scale;
			set = 1;
			break;
		
		case IDC_XDAMPINGSPIN:
			damping[0] = spin->GetFVal(); break;

		case IDC_YFROMSPIN:
			val = min[1] = spin->GetFVal()/scale; 
			set = 2;
			break;
		case IDC_YTOSPIN:
			val = max[1] = spin->GetFVal()/scale;
			set = 2;
			break;
		
		case IDC_YDAMPINGSPIN:
			damping[1] = spin->GetFVal(); break;
		}
	
	if (set && interactive) {
		SurfPosition *c = (SurfPosition*)ui->cont;
 		if (set==1) 
			 c->uCont->SetValue(
				ui->ip->GetTime(),&val,TRUE,CTRL_ABSOLUTE);
		else c->vCont->SetValue(
				ui->ip->GetTime(),&val,TRUE,CTRL_ABSOLUTE);
		ui->ip->RedrawViews(ui->ip->GetTime(),REDRAW_INTERACTIVE);
		}
	}

void SurfPosition::EnumIKParams(IKEnumCallback &callback)
	{
	JointParamsSurf *jp = (JointParamsSurf*)GetProperty(PROPID_JOINTPARAMS);
	if (jp && align==IDC_ALIGN_NONE) {
		if (jp->Active(0)) callback.proc(this,0);
		if (jp->Active(1)) callback.proc(this,1);
		}
	}

BOOL SurfPosition::CompDeriv(
		TimeValue t,Matrix3& ptm,IKDeriv& derivs,DWORD flags)
	{
	JointParamsSurf *jp = (JointParamsSurf*)GetProperty(PROPID_JOINTPARAMS);
	if (!jp || align!=IDC_ALIGN_NONE) return FALSE;

	float u, v;		
	GetUV(t,u,v);
	Point3 zero(0,0,0);

	if (jp->Active(0)) {
		Point3 d = DU(t,u,v);
		for (int j=0; j<derivs.NumEndEffectors(); j++) {
			if (flags&POSITION_DERIV) {
				derivs.DP(d,j);
				}
			if (flags&ROTATION_DERIV) {
				derivs.DR(zero,j);
				}
			}
		derivs.NextDOF();
		}
	if (jp->Active(1)) {
		Point3 d = DV(t,u,v);
		for (int j=0; j<derivs.NumEndEffectors(); j++) {
			if (flags&POSITION_DERIV) {
				derivs.DP(d,j);
				}
			if (flags&ROTATION_DERIV) {
				derivs.DR(zero,j);
				}
			}
		derivs.NextDOF();
		}
	
	ptm.SetTrans(Pos(t,u,v));
	return TRUE;
	}

float SurfPosition::IncIKParam(TimeValue t,int index,float delta)
	{
	JointParamsSurf *jp = (JointParamsSurf*)GetProperty(PROPID_JOINTPARAMS);
	if (fabs(delta) > 0.01f) {
		if (delta<0) delta = -0.01f;
		else delta = 0.01f;
		}
	if (jp) {
		float u, v;
		if (index==0) {
			if (jp->Limited(0)) {			
				uCont->GetValue(t,&u,FOREVER);			
				}
			delta = jp->ConstrainInc(0,u,delta);
			uCont->SetValue(t,&delta,FALSE,CTRL_RELATIVE);
		} else {
			if (jp->Limited(1)) {			
				vCont->GetValue(t,&v,FOREVER);			
				}
			delta = jp->ConstrainInc(0,v,delta);
			vCont->SetValue(t,&delta,FALSE,CTRL_RELATIVE);
			}
		}	
	return delta;
	}

void SurfPosition::ClearIKParam(Interval iv,int index)
	{
	uCont->DeleteTime(iv,TIME_INCRIGHT|TIME_NOSLIDE);
	vCont->DeleteTime(iv,TIME_INCRIGHT|TIME_NOSLIDE);
	}

