/**********************************************************************
 *<
	FILE: eulrctrl.cpp

	DESCRIPTION: An Euler angle rotation controller

	CREATED BY: Rolf Berteig

	HISTORY: created 13 June 1995

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

#include "ctrl.h"
#include "interpik.h"
#include "euler.h"

#define EULER_CONTROL_CNAME		GetString(IDS_RB_EULERXYZ)

#define EULER_X_REF		0
#define EULER_Y_REF		1
#define EULER_Z_REF		2

class EulerDlg;

static DWORD subColor[] = {PAINTCURVE_XCOLOR, PAINTCURVE_YCOLOR, PAINTCURVE_ZCOLOR};

class EulerRotation : public Control {
	public:
		Control *rotX;
		Control *rotY;
		Control *rotZ;
		int order;
		Quat curval;
		Interval ivalid;

		static EulerDlg *dlg;
		static IObjParam *ip;
		static ULONG beginFlags;
		static EulerRotation *editControl; // The one being edited.

		EulerRotation(const EulerRotation &ctrl);
		EulerRotation(BOOL loading=FALSE);
		~EulerRotation();
		void Update(TimeValue t);

		// Animatable methods
		Class_ID ClassID() { return Class_ID(EULER_CONTROL_CLASS_ID,0); }  
		SClass_ID SuperClassID() { return CTRL_ROTATION_CLASS_ID; }  		
		
		void GetClassName(TSTR& s);
		void DeleteThis() {delete this;}		
		int IsKeyable() {return 1;}		

		int NumSubs()  {return 3;}
		Animatable* SubAnim(int i);
		TSTR SubAnimName(int i);
		int SubNumToRefNum(int subNum) {return subNum;}

		DWORD GetSubAnimCurveColor(int subNum) {return subColor[subNum];}

		ParamDimension* GetParamDimension(int i) {return stdAngleDim;}
		BOOL AssignController(Animatable *control,int subAnim);
		void AddNewKey(TimeValue t,DWORD flags);
		int NumKeys();
		TimeValue GetKeyTime(int index);
		void CopyKeysFromTime(TimeValue src,TimeValue dst,DWORD flags);
		BOOL IsKeyAtTime(TimeValue t,DWORD flags);
		BOOL GetNextKeyTime(TimeValue t,DWORD flags,TimeValue &nt);
		void DeleteKeyAtTime(TimeValue t);

		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);
		void RescaleWorldUnits(float f) {}

		IOResult Save(ISave *isave);
		IOResult Load(ILoad *iload);

		// Control methods
		Control *GetXController() {return rotX;}
		Control *GetYController() {return rotY;}
		Control *GetZController() {return rotZ;}
		void Copy(Control *from);
		RefTargetHandle Clone(RemapDir& remap);
		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);
		void EnableORTs(BOOL enable);
		void MirrorIKConstraints(int axis,int which);		
		BOOL CanCopyIKParams(int which);
		IKClipObject *CopyIKParams(int which);
		BOOL CanPasteIKParams(IKClipObject *co,int which);
		void PasteIKParams(IKClipObject *co,int which);

		void ChangeOrdering(int newOrder);
	};

EulerDlg *EulerRotation::dlg = NULL;
IObjParam *EulerRotation::ip = NULL;
ULONG EulerRotation::beginFlags = 0;
EulerRotation *EulerRotation::editControl = NULL;

class JointParamsEuler : public JointParams {
	public:			 	
		JointParamsEuler() : JointParams((DWORD)JNT_ROT,3) {flags |= JNT_LIMITEXACT;}
		void SpinnerChange(InterpCtrlUI *ui,WORD id,ISpinnerControl *spin,BOOL interactive);
	};

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

static const int editButs[] = {IDC_EULER_X,IDC_EULER_Y,IDC_EULER_Z};

static int eulerIDs[] = {
	IDS_RB_EULERTYPE0,IDS_RB_EULERTYPE1,IDS_RB_EULERTYPE2,
	IDS_RB_EULERTYPE3,IDS_RB_EULERTYPE4,IDS_RB_EULERTYPE5,
	IDS_RB_EULERTYPE6,IDS_RB_EULERTYPE7,IDS_RB_EULERTYPE8};

typedef int EAOrdering[3];
static EAOrdering orderings[] = {
	{0,1,2},
	{0,2,1},
	{1,2,0},
	{1,0,2},
	{2,0,1},
	{2,1,0},
	{0,1,0},
	{1,2,1},
	{2,0,2},
	};

static int xyzIDs[] = {IDS_RB_X,IDS_RB_Y,IDS_RB_Z};
static int xyzRotIDs[] = {IDS_RB_XROTATION,IDS_RB_YROTATION,IDS_RB_ZROTATION};
static int xyzAxisIDs[] = {IDS_RB_XAXIS,IDS_RB_YAXIS,IDS_RB_ZAXIS};

#define EDIT_X	0
#define EDIT_Y	1
#define EDIT_Z	2

#define EULER_BEGIN		1
#define EULER_MIDDLE	2
#define EULER_END		3

class EulerDlg {
	public:
		EulerRotation *cont;
		HWND hWnd;
		IObjParam *ip;
		ICustButton *iEdit[3];
		static int cur;
		
		EulerDlg(EulerRotation *cont,IObjParam *ip);
		~EulerDlg();

		void Init();
		void SetButtonText();
		void EndingEdit(EulerRotation *next);
		void BeginingEdit(EulerRotation *cont,IObjParam *ip,EulerRotation *prev);
		void SetCur(int c,int code=EULER_MIDDLE);
		void WMCommand(int id, int notify, HWND hCtrl);
	};

int EulerDlg::cur = EDIT_X;

EulerDlg::EulerDlg(EulerRotation *cont,IObjParam *ip)
	{
	this->ip   = ip;
	this->cont = cont;
	for (int i=0; i<3; i++) {
		iEdit[i] = NULL;
		}
	
	hWnd = ip->AddRollupPage( 
		hInstance,
		MAKEINTRESOURCE(IDD_EULER_PARAMS),
		EulerParamDialogProc,
		GetString(IDS_RB_EULERPARAMS), 
		(LPARAM)this);
	ip->RegisterDlgWnd(hWnd);	
	
	SetCur(cur,EULER_BEGIN);	
	UpdateWindow(hWnd);
	}

EulerDlg::~EulerDlg()
	{
	SetCur(cur,EULER_END);
	for (int i=0; i<3; i++) {
		ReleaseICustButton(iEdit[i]);		
		}
	ip->UnRegisterDlgWnd(hWnd);
	ip->DeleteRollupPage(hWnd);
	hWnd = NULL;
	}

void EulerDlg::EndingEdit(EulerRotation *next)
	{
	switch (cur) {
		case EDIT_X:
			cont->rotX->EndEditParams(ip,0,next->rotX);
			break;
		case EDIT_Y:
			cont->rotY->EndEditParams(ip,0,next->rotY);
			break;
		case EDIT_Z:
			cont->rotZ->EndEditParams(ip,0,next->rotZ);
			break;
		}
	cont = NULL;
	ip   = NULL;
	}

void EulerDlg::BeginingEdit(EulerRotation *cont,IObjParam *ip,EulerRotation *prev)
	{
	this->ip   = ip;
	this->cont = cont;
	switch (cur) {
		case EDIT_X:
			cont->rotX->BeginEditParams(ip,BEGIN_EDIT_MOTION,prev->rotX);
			break;
		case EDIT_Y:
			cont->rotY->BeginEditParams(ip,BEGIN_EDIT_MOTION,prev->rotY);
			break;
		case EDIT_Z:
			cont->rotZ->BeginEditParams(ip,BEGIN_EDIT_MOTION,prev->rotZ);
			break;
		}	
	UpdateWindow(hWnd);
	}

void EulerDlg::SetButtonText()
	{
	for (int i=0; i<3; i++) {
		iEdit[i]->SetText(GetString(
			xyzIDs[orderings[cont->order][i]]));
		}
	}

void EulerDlg::Init()
	{	
	for (int i=0; i<3; i++) {
		iEdit[i] = GetICustButton(GetDlgItem(hWnd,editButs[i]));		
		iEdit[i]->SetType(CBT_CHECK);		
		}
	iEdit[cur]->SetCheck(TRUE);	
	SetButtonText();

	SendDlgItemMessage(hWnd,IDC_EULER_ORDER,CB_RESETCONTENT,0,0);
	for (i=0; i<9; i++) {
		SendDlgItemMessage(hWnd,IDC_EULER_ORDER,CB_ADDSTRING,0,
			(LPARAM)GetString(eulerIDs[i]));
		}
	SendDlgItemMessage(hWnd,IDC_EULER_ORDER,CB_SETCURSEL,cont->order,0);
	}

void EulerDlg::SetCur(int c,int code)
	{
	if (c==cur && code==EULER_MIDDLE) return;
	Control *prev = NULL, *next = NULL;

	if (code!=EULER_END) {
		switch (c) {
			case EDIT_X:
				next = cont->rotX;
				break;
			case EDIT_Y:
				next = cont->rotY;
				break;
			case EDIT_Z:
				next = cont->rotZ;
				break;
			}
		}

	if (code!=EULER_BEGIN) {
		switch (cur) {
			case EDIT_X:
				cont->rotX->EndEditParams(ip,END_EDIT_REMOVEUI,next);
				prev = cont->rotX;
				break;
			case EDIT_Y:
				cont->rotY->EndEditParams(ip,END_EDIT_REMOVEUI,next);
				prev = cont->rotY;
				break;
			case EDIT_Z:
				cont->rotZ->EndEditParams(ip,END_EDIT_REMOVEUI,next);
				prev = cont->rotZ;
				break;
			}
		}

	cur = c;

	if (code!=EULER_END) {
		switch (cur) {
			case EDIT_X:
				cont->rotX->BeginEditParams(ip,BEGIN_EDIT_MOTION,prev);
				break;
			case EDIT_Y:
				cont->rotY->BeginEditParams(ip,BEGIN_EDIT_MOTION,prev);
				break;
			case EDIT_Z:
				cont->rotZ->BeginEditParams(ip,BEGIN_EDIT_MOTION,prev);
				break;
			}
		}
	}

void EulerDlg::WMCommand(int id, int notify, HWND hCtrl)
	{
	switch (id) {
		case IDC_EULER_X:
			SetCur(0);
			iEdit[0]->SetCheck(TRUE);
			iEdit[1]->SetCheck(FALSE);
			iEdit[2]->SetCheck(FALSE);
			break;
		case IDC_EULER_Y:
			SetCur(1);
			iEdit[0]->SetCheck(FALSE);
			iEdit[1]->SetCheck(TRUE);
			iEdit[2]->SetCheck(FALSE);
			break;
		case IDC_EULER_Z:
			SetCur(2);
			iEdit[0]->SetCheck(FALSE);
			iEdit[1]->SetCheck(FALSE);
			iEdit[2]->SetCheck(TRUE);
			break;

		case IDC_EULER_ORDER:
			if (notify==CBN_SELCHANGE) {
				int res = SendDlgItemMessage(hWnd,IDC_EULER_ORDER,CB_GETCURSEL,0,0);
				if (res!=CB_ERR) {
					cont->ChangeOrdering(res);
					SetButtonText();
					}
				}
			break;			
		}
	}

static BOOL CALLBACK EulerParamDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
	{
	EulerDlg *dlg = (EulerDlg*)GetWindowLong(hDlg,GWL_USERDATA);

	switch (message) {
		case WM_INITDIALOG:
			dlg = (EulerDlg*)lParam;			
			SetWindowLong(hDlg,GWL_USERDATA,lParam);
			dlg->hWnd = hDlg;
			dlg->Init();
			break;
		
		case WM_COMMAND:
			dlg->WMCommand(LOWORD(wParam),HIWORD(wParam),(HWND)lParam);
			break;

		case WM_LBUTTONDOWN:case WM_LBUTTONUP:	case WM_MOUSEMOVE:
			dlg->ip->RollupMouseMessage(hDlg,message,wParam,lParam);
			break;
				
		default:
			return FALSE;
		}
	return TRUE;
	}


//********************************************************
// EULER CONTROL
//********************************************************
static Class_ID eulerControlClassID(EULER_CONTROL_CLASS_ID,0); 
class EulerClassDesc:public ClassDesc {
	public:
	int 			IsPublic() { return 1; }
	void *			Create(BOOL loading) { return new EulerRotation(loading); }
	const TCHAR *	ClassName() { return EULER_CONTROL_CNAME; }
	SClass_ID		SuperClassID() { return CTRL_ROTATION_CLASS_ID; }
	Class_ID		ClassID() { return eulerControlClassID; }
	const TCHAR* 	Category() { return _T("");  }
	};
static EulerClassDesc eulerCD;
ClassDesc* GetEulerCtrlDesc() {return &eulerCD;}

EulerRotation::EulerRotation(const EulerRotation &ctrl)
	{
	order = EULERTYPE_XYZ;
	rotX = NULL;
	rotY = NULL;
	rotZ = NULL;

	if (ctrl.rotX) {
		ReplaceReference(EULER_X_REF,ctrl.rotX);
	} else {
		ReplaceReference(EULER_X_REF,NewDefaultFloatController());
		}
	if (ctrl.rotY) {
		ReplaceReference(EULER_Y_REF,ctrl.rotY);
	} else {
		ReplaceReference(EULER_Y_REF,NewDefaultFloatController());
		}
	if (ctrl.rotZ) {
		ReplaceReference(EULER_Z_REF,ctrl.rotZ);
	} else {
		ReplaceReference(EULER_Z_REF,NewDefaultFloatController());
		}
	curval = ctrl.curval;
	ivalid = ctrl.ivalid;
	}

EulerRotation::EulerRotation(BOOL loading) 
	{
	order = EULERTYPE_XYZ;
	rotX = NULL;
	rotY = NULL;
	rotZ = NULL;
	if (!loading) {
		ReplaceReference(EULER_X_REF,NewDefaultFloatController());
		ReplaceReference(EULER_Y_REF,NewDefaultFloatController());
		ReplaceReference(EULER_Z_REF,NewDefaultFloatController());
		ivalid = FOREVER;
		curval.Identity();
	} else {
		ivalid.SetEmpty();
		}	
	}

RefTargetHandle EulerRotation::Clone(RemapDir& remap) 
	{
	EulerRotation *euler = new EulerRotation(TRUE);	
	euler->ReplaceReference(EULER_X_REF, remap.CloneRef(rotX));
	euler->ReplaceReference(EULER_Y_REF, remap.CloneRef(rotY));
	euler->ReplaceReference(EULER_Z_REF, remap.CloneRef(rotZ));
	euler->order = order;

	JointParams *jp = (JointParams*)GetProperty(PROPID_JOINTPARAMS);
    if (jp) {
		JointParams *jp2 = new JointParams(*jp);
		euler->SetProperty(PROPID_JOINTPARAMS,jp2);
		}
	return euler;
	}



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

void EulerRotation::GetClassName(TSTR& s)
	{		
	TSTR format(GetString(IDS_RB_EULERNAME));
	s.printf(format,GetString(eulerIDs[order]));
	}

void EulerRotation::Copy(Control *from)
	{
	if (from->ClassID()==ClassID()) {
		EulerRotation *ctrl = (EulerRotation*)from;
		ReplaceReference(EULER_X_REF,ctrl->rotX);
		ReplaceReference(EULER_Y_REF,ctrl->rotY);
		ReplaceReference(EULER_Z_REF,ctrl->rotZ);
		curval = ctrl->curval;
		ivalid = ctrl->ivalid;
		order  = ctrl->order;
	} else {		
		Quat v;
		Interval iv;
		int num;		
		if (num=from->NumKeys()) {
			SuspendAnimate();
			AnimateOn();
			for (int i=0; i<num; i++) {
				TimeValue t = from->GetKeyTime(i);
				from->GetValue(t,&v,iv);
				SetValue(t,&v,TRUE,CTRL_ABSOLUTE);	
				}
			ResumeAnimate();
		} else {
			from->GetValue(0,&v,ivalid);
			SetValue(0,&v,TRUE,CTRL_ABSOLUTE);
			}
		}
	NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
	}

void EulerRotation::Update(TimeValue t)
	{
	if (!ivalid.InInterval(t)) {
		ivalid = FOREVER;
		Point3 ang(0,0,0);
		if (rotX) rotX->GetValue(t,&ang.x,ivalid);
		if (rotY) rotY->GetValue(t,&ang.y,ivalid);
		if (rotZ) rotZ->GetValue(t,&ang.z,ivalid);
		
		// This could be optimized.
		//Matrix3 tm(1);
		//tm.RotateX(x);
		//tm.RotateY(y);
		//tm.RotateZ(z);
		//curval = Quat(tm);		
		Matrix3 tm(1);
		for (int i=0; i<3; i++) {
			switch (orderings[order][i]) {
				case 0: tm.RotateX(ang[i]); break;
				case 1: tm.RotateY(ang[i]); break;
				case 2: tm.RotateZ(ang[i]); break;
				}
			}
		curval = Quat(tm);
		//EulerToQuat(ang, curval, order);
		}
	}

void EulerRotation::ChangeOrdering(int newOrder)
	{
	order = newOrder;
	ivalid.SetEmpty();
	NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
	NotifyDependents(FOREVER,PART_ALL,REFMSG_SUBANIM_STRUCTURE_CHANGED);
	ip->RedrawViews(ip->GetTime());
	}

void EulerRotation::SetValue(TimeValue t, void *val, int commit, GetSetMethod method)
	{
	Quat v;
	Update(t);
	if (method==CTRL_RELATIVE) {
		v = curval * Quat(*((AngAxis*)val));
	} else {
		v = *((Quat*)val);
		}
		
	float ang[3];
	//QuatToEuler(v,ang);
	Matrix3 tm;
	v.MakeMatrix(tm);
	MatrixToEuler(tm,ang, order);
	
	// RB: this gives the incorrect sign sometimes...
	//QuatToEuler(v, ang, order);		

	if (rotX) rotX->SetValue(t,&ang[0]);
	if (rotY) rotY->SetValue(t,&ang[1]);
	if (rotZ) rotZ->SetValue(t,&ang[2]);
	ivalid.SetEmpty();
	NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
	}

void EulerRotation::GetValue(TimeValue t, void *val, Interval &valid, GetSetMethod method)
	{		
	Update(t);
	valid &= ivalid;			 
	if (method==CTRL_RELATIVE) {
  		Matrix3 *mat = (Matrix3*)val;		
		PreRotateMatrix(*mat,curval);		
	} else {
		*((Quat*)val) = curval;
		}
	}

void EulerRotation::CommitValue(TimeValue t)
	{
	if (rotX) rotX->CommitValue(t);
	if (rotY) rotY->CommitValue(t);
	if (rotZ) rotZ->CommitValue(t);
	}

void EulerRotation::RestoreValue(TimeValue t)
	{
	if (rotX) rotX->RestoreValue(t);
	if (rotY) rotY->RestoreValue(t);
	if (rotZ) rotZ->RestoreValue(t);
	}

RefTargetHandle EulerRotation::GetReference(int i)
	{
	switch (i) {
		case EULER_X_REF: return rotX;
		case EULER_Y_REF: return rotY;
		case EULER_Z_REF: return rotZ;
		default: return NULL;
		}
	}

void EulerRotation::SetReference(int i, RefTargetHandle rtarg)
	{
	switch (i) {
		case EULER_X_REF: rotX = (Control*)rtarg; break;
		case EULER_Y_REF: rotY = (Control*)rtarg; break;
		case EULER_Z_REF: rotZ = (Control*)rtarg; break;
		}
	}

Animatable* EulerRotation::SubAnim(int i)
	{
	return GetReference(i);
	}

TSTR EulerRotation::SubAnimName(int i)
	{	
	switch (i) {
		case EULER_X_REF: return GetString(xyzRotIDs[orderings[order][0]]);
		case EULER_Y_REF: return GetString(xyzRotIDs[orderings[order][1]]);
		case EULER_Z_REF: return GetString(xyzRotIDs[orderings[order][2]]);
		default: return _T("");
		}
	}

RefResult EulerRotation::NotifyRefChanged(
		Interval iv, 
		RefTargetHandle hTarg, 
		PartID& partID, 
		RefMessage msg) 
	{
	switch (msg) {
		case REFMSG_CHANGE:
			ivalid.SetEmpty();
			break;
		case REFMSG_TARGET_DELETED:
			if (rotX == hTarg) rotX = NULL;
			if (rotY == hTarg) rotY = NULL;
			if (rotZ == hTarg) rotZ = NULL; 
			break;
		case REFMSG_GET_CONTROL_DIM: {
			ParamDimension **dim = (ParamDimension **)partID;
			assert(dim);
			*dim = stdAngleDim;
			}
		}
	return REF_SUCCEED;
	}

BOOL EulerRotation::AssignController(Animatable *control,int subAnim)
	{	
	switch (subAnim) {
		case EULER_X_REF:
			ReplaceReference(EULER_X_REF,(RefTargetHandle)control);
			break;
		case EULER_Y_REF:
			ReplaceReference(EULER_Y_REF,(RefTargetHandle)control);
			break;
		case EULER_Z_REF:
			ReplaceReference(EULER_Z_REF,(RefTargetHandle)control);
			break;
		}
	NotifyDependents(FOREVER,0,REFMSG_CONTROLREF_CHANGE,TREE_VIEW_CLASS_ID,FALSE);
	NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);	
	return TRUE;
	}

void EulerRotation::AddNewKey(TimeValue t,DWORD flags)
	{
	if (rotX) rotX->AddNewKey(t,flags);
	if (rotY) rotY->AddNewKey(t,flags);
	if (rotZ) rotZ->AddNewKey(t,flags);
	}

int EulerRotation::NumKeys()
	{
	int num = 0;
	if (rotX) num += rotX->NumKeys(); 
	if (rotY) num += rotY->NumKeys();
	if (rotZ) num += rotZ->NumKeys();
	return num;
	}

TimeValue EulerRotation::GetKeyTime(int index)
	{
	int onum,num = 0;
	if (rotX) num += rotX->NumKeys(); 
	if (index < num) return rotX->GetKeyTime(index);
	onum = num;
	if (rotY) num += rotY->NumKeys(); 
	if (index < num) return rotY->GetKeyTime(index-onum);
	onum = num;
	if (rotZ) num += rotZ->NumKeys(); 
	if (index < num) return rotZ->GetKeyTime(index-onum);
	return 0;
	}

void EulerRotation::CopyKeysFromTime(TimeValue src,TimeValue dst,DWORD flags)
	{
	if (rotX) rotX->CopyKeysFromTime(src,dst,flags);
	if (rotY) rotY->CopyKeysFromTime(src,dst,flags);
	if (rotZ) rotZ->CopyKeysFromTime(src,dst,flags);
	}

BOOL EulerRotation::IsKeyAtTime(TimeValue t,DWORD flags)
	{
	if (rotX && rotX->IsKeyAtTime(t,flags)) return TRUE;
	if (rotY && rotY->IsKeyAtTime(t,flags)) return TRUE;
	if (rotZ && rotZ->IsKeyAtTime(t,flags)) return TRUE;
	return FALSE;
	}

void EulerRotation::DeleteKeyAtTime(TimeValue t)
	{
	if (rotX) rotX->DeleteKeyAtTime(t);
	if (rotY) rotY->DeleteKeyAtTime(t);
	if (rotZ) rotZ->DeleteKeyAtTime(t);
	}

BOOL EulerRotation::GetNextKeyTime(TimeValue t,DWORD flags,TimeValue &nt)
	{
	TimeValue at,tnear;
	BOOL tnearInit = FALSE;
	
	if (rotX && rotX->GetNextKeyTime(t,flags,at)) {
		if (!tnearInit) {
			tnear = at;
			tnearInit = TRUE;
		} else 
		if (ABS(at-t) < ABS(tnear-t)) tnear = at;
		}

	if (rotY && rotY->GetNextKeyTime(t,flags,at)) {
		if (!tnearInit) {
			tnear = at;
			tnearInit = TRUE;
		} else 
		if (ABS(at-t) < ABS(tnear-t)) tnear = at;
		}

	if (rotZ && rotZ->GetNextKeyTime(t,flags,at)) {
		if (!tnearInit) {
			tnear = at;
			tnearInit = TRUE;
		} else 
		if (ABS(at-t) < ABS(tnear-t)) tnear = at;
		}
	
	if (tnearInit) {
		nt = tnear;
		return TRUE;
	} else {
		return FALSE;
		}
	}
		

void EulerRotation::BeginEditParams( IObjParam *ip, ULONG flags,Animatable *prev )
	{
	if (flags&BEGIN_EDIT_HIERARCHY) {
		JointParamsEuler *jp = (JointParamsEuler*)GetProperty(PROPID_JOINTPARAMS);
		InterpCtrlUI *ui;	

		if (!jp) {
			jp = new JointParamsEuler();
			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_STDJOINTPARAMS),
				(int (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,long))JointParamDlgProc,
				GetString(IDS_RB_ROTJOINTPARAMS), 
				(LPARAM)new JointDlgData(ui,jp),f);	
			}
	
		SetDlgItemText(ui->hParams,IDC_XAXIS_LABEL,
			GetString(xyzAxisIDs[orderings[order][0]]));
		SetDlgItemText(ui->hParams,IDC_YAXIS_LABEL,
			GetString(xyzAxisIDs[orderings[order][1]]));
		SetDlgItemText(ui->hParams,IDC_ZAXIS_LABEL,
			GetString(xyzAxisIDs[orderings[order][2]]));

		SetProperty(PROPID_INTERPUI,ui);
		editControl = this;
		beginFlags = flags;
	} else 
	if (flags&BEGIN_EDIT_MOTION) {
		this->ip = ip;

		if (dlg) {
			dlg->BeginingEdit(this,ip,(EulerRotation*)prev);
			dlg->Init();
		} else {
			dlg = new EulerDlg(this,ip);	
			}
		}
	}

void EulerRotation::EndEditParams( IObjParam *ip, ULONG flags,Animatable *next )
	{	
	EulerRotation *cont=NULL;
	if (next && next->ClassID()==ClassID()) {
		cont = (EulerRotation*)next;
		}

	if (dlg) {
		if (cont) {
			dlg->EndingEdit(cont);
		} else {
			delete dlg;
			dlg = NULL;
			}
	} else {
		if (cont) return;
		
		editControl = NULL;
		beginFlags = 0;

		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);
				}
			}
		}
	}

int EulerRotation::SetProperty(ULONG id, void *data)
	{
	if (id==PROPID_JOINTPARAMS) {		
		if (!data) {
			int index = aprops.FindProperty(id);
			if (index>=0) {
				aprops.Delete(index,1);
				}
		} else {
			JointParamsEuler *jp = (JointParamsEuler*)GetProperty(id);
			if (jp) {
				*jp = *((JointParamsEuler*)data);
				delete (JointParamsEuler*)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* EulerRotation::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);
		}
	}


#define JOINTPARAMEULER_CHUNK	0x1002
#define ORDER_CHUNK				0x1003

IOResult EulerRotation::Save(ISave *isave)
	{	
	ULONG nb;
	JointParamsEuler *jp = (JointParamsEuler*)GetProperty(PROPID_JOINTPARAMS);
	if (jp) {
		isave->BeginChunk(JOINTPARAMEULER_CHUNK);
		jp->Save(isave);
		isave->EndChunk();
		}

	isave->BeginChunk(ORDER_CHUNK);
	isave->Write(&order,sizeof(order),&nb);
	isave->EndChunk();

	return IO_OK;
	}

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

			case JOINTPARAMEULER_CHUNK: {
				JointParamsEuler *jp = new JointParamsEuler;
				jp->Load(iload);
				jp->flags |= JNT_LIMITEXACT;
				SetProperty(PROPID_JOINTPARAMS,jp);
				break;
				}
			}		
		iload->CloseChunk();
		if (res!=IO_OK)  return res;
		}
	return IO_OK;
	}


void EulerRotation::EnumIKParams(IKEnumCallback &callback)
	{
	JointParamsEuler *jp = (JointParamsEuler*)GetProperty(PROPID_JOINTPARAMS);
	for (int i=2; i>=0; i--) {
		if (!jp || jp->Active(i)) {
			callback.proc(this,i);
			}
		}
	}

BOOL EulerRotation::CompDeriv(TimeValue t,Matrix3& ptm,IKDeriv& derivs,DWORD flags)
	{
	JointParamsEuler *jp = (JointParamsEuler*)GetProperty(PROPID_JOINTPARAMS);
	Quat q;
	Interval valid;
	Point3 a(0,0,0);

	if (rotX) rotX->GetValue(t,&a[0],valid);
	if (rotY) rotY->GetValue(t,&a[1],valid);
	if (rotZ) rotZ->GetValue(t,&a[2],valid);

	for (int i=2; i>=0; i--) {
		if (!jp || jp->Active(i)) {
			for (int j=0; j<derivs.NumEndEffectors(); j++) {
				Point3 r = derivs.EndEffectorPos(j) - ptm.GetTrans();	
		
				Point3 axis = ptm.GetRow(orderings[order][i]);
				if (!(ptm.GetIdentFlags()&SCL_IDENT)) {
					axis = Normalize(axis);
					if (ptm.Parity()) axis = -axis;
					}

				if (flags&POSITION_DERIV) {
					derivs.DP(CrossProd(axis,r),j);
					}
				if (flags&ROTATION_DERIV) {
					derivs.DR(axis,j);
					}
				}
			derivs.NextDOF();			
			}
		switch (orderings[order][i]) {
			case 0: ptm.PreRotateX(a[i]); break;
			case 1: ptm.PreRotateY(a[i]); break;
			case 2: ptm.PreRotateZ(a[i]); break;
			}
		}	
	return TRUE;
	}

#define MAX_IKROT	DegToRad(4.0f)
#define SGN(a)	(a<0?-1:1)

float EulerRotation::IncIKParam(TimeValue t,int index,float delta)
	{
	JointParamsEuler *jp = (JointParamsEuler*)GetProperty(PROPID_JOINTPARAMS);
	if ((float)fabs(delta)>MAX_IKROT) delta = MAX_IKROT * SGN(delta);
	
	if (jp) {
		float v=0.0f;		
		if (jp->Limited(index) || jp->Spring(index)) {
			Interval valid;
			switch (index) {
				case 0: if (rotX) rotX->GetValue(t,&v,valid); break;
				case 1: if (rotY) rotY->GetValue(t,&v,valid); break;
				case 2: if (rotZ) rotZ->GetValue(t,&v,valid); break;
				}
			}
		delta = jp->ConstrainInc(index,v,delta);
		}
	switch (index) {
		case 0: if (rotX) rotX->SetValue(t,&delta,FALSE,CTRL_RELATIVE); break;
		case 1: if (rotY) rotY->SetValue(t,&delta,FALSE,CTRL_RELATIVE); break;
		case 2: if (rotZ) rotZ->SetValue(t,&delta,FALSE,CTRL_RELATIVE); break;
		}	
	return delta;	
	}

void EulerRotation::ClearIKParam(Interval iv,int index) 
	{
	switch (index) {
		case 0: if (rotX) rotX->DeleteTime(iv,TIME_INCRIGHT|TIME_NOSLIDE); break;
		case 1: if (rotY) rotY->DeleteTime(iv,TIME_INCRIGHT|TIME_NOSLIDE); break;
		case 2: if (rotZ) rotZ->DeleteTime(iv,TIME_INCRIGHT|TIME_NOSLIDE); break;
		}
	}

void EulerRotation::MirrorIKConstraints(int axis,int which)
	{
	JointParamsEuler *jp = (JointParamsEuler*)GetProperty(PROPID_JOINTPARAMS);
	if (jp) jp->MirrorConstraints(axis);
	}

void EulerRotation::EnableORTs(BOOL enable)
	{
	if (rotX) rotX->EnableORTs(enable);
	if (rotY) rotY->EnableORTs(enable);
	if (rotZ) rotZ->EnableORTs(enable);
	}

BOOL EulerRotation::CanCopyIKParams(int which)
	{
	return ::CanCopyIKParams(this,which);
	}

IKClipObject *EulerRotation::CopyIKParams(int which)
	{
	return ::CopyIKParams(this,which);
	}

BOOL EulerRotation::CanPasteIKParams(IKClipObject *co,int which)
	{
	return ::CanPasteIKParams(this,co,which);
	}

void EulerRotation::PasteIKParams(IKClipObject *co,int which)
	{
	::PasteIKParams(this,co,which);
	}

void JointParamsEuler::SpinnerChange(
		InterpCtrlUI *ui,WORD id,ISpinnerControl *spin,BOOL interactive)
	{
	EulerRotation *c = (EulerRotation*)ui->cont;
	Point3 a(0,0,0);
	BOOL set = FALSE;
	Interval valid;

	if (c->rotX) c->rotX->GetValue(ui->ip->GetTime(),&a[0],valid);
	if (c->rotY) c->rotY->GetValue(ui->ip->GetTime(),&a[1],valid);
	if (c->rotZ) c->rotZ->GetValue(ui->ip->GetTime(),&a[2],valid);

	switch (id) {
		case IDC_XFROMSPIN:
			a[0] = min[0] = DegToRad(spin->GetFVal()); 
			set = TRUE;
			break;
		case IDC_XTOSPIN:
			a[0] = max[0] = DegToRad(spin->GetFVal());
			set = TRUE;
			break;
		case IDC_XSPRINGSPIN:
			a[0] = spring[0] = DegToRad(spin->GetFVal());
			set = TRUE;
			break;
		
		case IDC_YFROMSPIN:
			a[1] = min[1] = DegToRad(spin->GetFVal()); 
			set = TRUE;
			break;
		case IDC_YTOSPIN:
			a[1] = max[1] = DegToRad(spin->GetFVal());
			set = TRUE;
			break;
		case IDC_YSPRINGSPIN:
			a[0] = spring[1] = DegToRad(spin->GetFVal());
			set = TRUE;
			break;
		
		case IDC_ZFROMSPIN:
			a[2] = min[2] = DegToRad(spin->GetFVal()); 
			set = TRUE;
			break;
		case IDC_ZTOSPIN:
			a[2] = max[2] = DegToRad(spin->GetFVal());
			set = TRUE;
			break;
		case IDC_ZSPRINGSPIN:
			a[2] = spring[0] = DegToRad(spin->GetFVal());
			set = TRUE;
			break;
		
		case IDC_XDAMPINGSPIN:
			damping[0] = spin->GetFVal(); break;		
		case IDC_YDAMPINGSPIN:
			damping[1] = spin->GetFVal(); break;		
		case IDC_ZDAMPINGSPIN:
			damping[2] = spin->GetFVal(); break;

		case IDC_XSPRINGTENSSPIN:
			stens[0] = spin->GetFVal()/SPRINGTENS_UI; break;
		case IDC_YSPRINGTENSSPIN:
			stens[1] = spin->GetFVal()/SPRINGTENS_UI; break;
		case IDC_ZSPRINGTENSSPIN:
			stens[2] = spin->GetFVal()/SPRINGTENS_UI; break;
		}
	
	if (set && interactive) {		 		
 		if (c->rotX) c->rotX->SetValue(ui->ip->GetTime(),&a[0],TRUE,CTRL_ABSOLUTE);
		if (c->rotY) c->rotY->SetValue(ui->ip->GetTime(),&a[1],TRUE,CTRL_ABSOLUTE);
		if (c->rotZ) c->rotZ->SetValue(ui->ip->GetTime(),&a[2],TRUE,CTRL_ABSOLUTE);
		ui->ip->RedrawViews(ui->ip->GetTime(),REDRAW_INTERACTIVE);
		}
	}



