// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// skeleton_scale_dlg.cpp : implementation file
//

#include "std_afx.h"
#include "object_viewer.h"
#include "skeleton_scale_dlg.h"
#include "nel/3d/skeleton_shape.h"
#include "nel/misc/algo.h"
#include "main_frame.h"


// ***************************************************************************
#define NL_SSD_SLIDER_SIZE			1000
#define NL_SSD_SLIDER_THRESHOLD		0.03f
#define NL_SSD_SLIDER_SCALE			2


/////////////////////////////////////////////////////////////////////////////
// CSkeletonScaleDlg dialog


CSkeletonScaleDlg::CSkeletonScaleDlg(CObjectViewer *viewer, CWnd* pParent /*=NULL*/)
	: CDialog(CSkeletonScaleDlg::IDD, pParent) ,_ObjViewer(viewer)
{
	//{{AFX_DATA_INIT(CSkeletonScaleDlg)
	_StaticFileName = _T("");
	_EditBoneSX = _T("");
	_EditBoneSY = _T("");
	_EditBoneSZ = _T("");
	_EditSkinSX = _T("");
	_EditSkinSY = _T("");
	_EditSkinSZ = _T("");
	//}}AFX_DATA_INIT

	// Init Scale Sliders ptrs
	nlassert(SidCount==6);
	_ScaleSliders[SidBoneX]= &_SliderBoneX;
	_ScaleSliders[SidBoneY]= &_SliderBoneY;
	_ScaleSliders[SidBoneZ]= &_SliderBoneZ;
	_ScaleSliders[SidSkinX]= &_SliderSkinX;
	_ScaleSliders[SidSkinY]= &_SliderSkinY;
	_ScaleSliders[SidSkinZ]= &_SliderSkinZ;
	_ScaleEdits[SidBoneX]= &_EditBoneSX;
	_ScaleEdits[SidBoneY]= &_EditBoneSY;
	_ScaleEdits[SidBoneZ]= &_EditBoneSZ;
	_ScaleEdits[SidSkinX]= &_EditSkinSX;
	_ScaleEdits[SidSkinY]= &_EditSkinSY;
	_ScaleEdits[SidSkinZ]= &_EditSkinSZ;
	_StaticScaleMarkers[SidBoneX]= &_StaticScaleMarkerBoneSX;
	_StaticScaleMarkers[SidBoneY]= &_StaticScaleMarkerBoneSY;
	_StaticScaleMarkers[SidBoneZ]= &_StaticScaleMarkerBoneSZ;
	_StaticScaleMarkers[SidSkinX]= &_StaticScaleMarkerSkinSX;
	_StaticScaleMarkers[SidSkinY]= &_StaticScaleMarkerSkinSY;
	_StaticScaleMarkers[SidSkinZ]= &_StaticScaleMarkerSkinSZ;
	

	_SliderEdited= SidNone;
	_SaveDirty= false;

	// avoid realloc
	_UndoQueue.resize(MaxUndoRedo);
	_RedoQueue.resize(MaxUndoRedo);
	_UndoQueue.clear();
	_RedoQueue.clear();

	_BoneBBoxNeedRecompute= false;
}

CSkeletonScaleDlg::~CSkeletonScaleDlg()
{
}


void CSkeletonScaleDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CSkeletonScaleDlg)
	DDX_Control(pDX, IDC_SSD_SLIDER_SKIN_SZ, _SliderSkinZ);
	DDX_Control(pDX, IDC_SSD_SLIDER_SKIN_SY, _SliderSkinY);
	DDX_Control(pDX, IDC_SSD_SLIDER_SKIN_SX, _SliderSkinX);
	DDX_Control(pDX, IDC_SSD_SLIDER_BONE_SZ, _SliderBoneZ);
	DDX_Control(pDX, IDC_SSD_SLIDER_BONE_SY, _SliderBoneY);
	DDX_Control(pDX, IDC_SSD_SLIDER_BONE_SX, _SliderBoneX);
	DDX_Control(pDX, IDC_SSD_LIST, _BoneList);
	DDX_Text(pDX, IDC_SSD_STATIC_FILENAME, _StaticFileName);
	DDX_Text(pDX, IDC_SSD_EDIT_BONE_SX, _EditBoneSX);
	DDX_Text(pDX, IDC_SSD_EDIT_BONE_SY, _EditBoneSY);
	DDX_Text(pDX, IDC_SSD_EDIT_BONE_SZ, _EditBoneSZ);
	DDX_Text(pDX, IDC_SSD_EDIT_SKIN_SX, _EditSkinSX);
	DDX_Text(pDX, IDC_SSD_EDIT_SKIN_SY, _EditSkinSY);
	DDX_Text(pDX, IDC_SSD_EDIT_SKIN_SZ, _EditSkinSZ);
	DDX_Control(pDX, IDC_SSD_STATIC_SKIN_SZ, _StaticScaleMarkerSkinSZ);
	DDX_Control(pDX, IDC_SSD_STATIC_SKIN_SY, _StaticScaleMarkerSkinSY);
	DDX_Control(pDX, IDC_SSD_STATIC_SKIN_SX, _StaticScaleMarkerSkinSX);
	DDX_Control(pDX, IDC_SSD_STATIC_BONE_SZ, _StaticScaleMarkerBoneSZ);
	DDX_Control(pDX, IDC_SSD_STATIC_BONE_SY, _StaticScaleMarkerBoneSY);
	DDX_Control(pDX, IDC_SSD_STATIC_BONE_SX, _StaticScaleMarkerBoneSX);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CSkeletonScaleDlg, CDialog)
	//{{AFX_MSG_MAP(CSkeletonScaleDlg)
	ON_WM_DESTROY()
	ON_WM_VSCROLL()
	ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_BONE_SX, OnReleasedcaptureSsdSliderBoneSx)
	ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_BONE_SY, OnReleasedcaptureSsdSliderBoneSy)
	ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_BONE_SZ, OnReleasedcaptureSsdSliderBoneSz)
	ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_SKIN_SX, OnReleasedcaptureSsdSliderSkinSx)
	ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_SKIN_SY, OnReleasedcaptureSsdSliderSkinSy)
	ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_SKIN_SZ, OnReleasedcaptureSsdSliderSkinSz)
	ON_EN_CHANGE(IDC_SSD_EDIT_BONE_SX, OnChangeSsdEditBoneSx)
	ON_EN_CHANGE(IDC_SSD_EDIT_BONE_SY, OnChangeSsdEditBoneSy)
	ON_EN_CHANGE(IDC_SSD_EDIT_BONE_SZ, OnChangeSsdEditBoneSz)
	ON_EN_CHANGE(IDC_SSD_EDIT_SKIN_SX, OnChangeSsdEditSkinSx)
	ON_EN_CHANGE(IDC_SSD_EDIT_SKIN_SY, OnChangeSsdEditSkinSy)
	ON_EN_CHANGE(IDC_SSD_EDIT_SKIN_SZ, OnChangeSsdEditSkinSz)
	ON_EN_KILLFOCUS(IDC_SSD_EDIT_BONE_SX, OnKillfocusSsdEditBoneSx)
	ON_EN_KILLFOCUS(IDC_SSD_EDIT_BONE_SY, OnKillfocusSsdEditBoneSy)
	ON_EN_KILLFOCUS(IDC_SSD_EDIT_BONE_SZ, OnKillfocusSsdEditBoneSz)
	ON_EN_KILLFOCUS(IDC_SSD_EDIT_SKIN_SX, OnKillfocusSsdEditSkinSx)
	ON_EN_KILLFOCUS(IDC_SSD_EDIT_SKIN_SY, OnKillfocusSsdEditSkinSy)
	ON_EN_KILLFOCUS(IDC_SSD_EDIT_SKIN_SZ, OnKillfocusSsdEditSkinSz)
	ON_EN_SETFOCUS(IDC_SSD_EDIT_BONE_SX, OnSetfocusSsdEditBoneSx)
	ON_EN_SETFOCUS(IDC_SSD_EDIT_BONE_SY, OnSetfocusSsdEditBoneSy)
	ON_EN_SETFOCUS(IDC_SSD_EDIT_BONE_SZ, OnSetfocusSsdEditBoneSz)
	ON_EN_SETFOCUS(IDC_SSD_EDIT_SKIN_SX, OnSetfocusSsdEditSkinSx)
	ON_EN_SETFOCUS(IDC_SSD_EDIT_SKIN_SY, OnSetfocusSsdEditSkinSy)
	ON_EN_SETFOCUS(IDC_SSD_EDIT_SKIN_SZ, OnSetfocusSsdEditSkinSz)
	ON_LBN_SELCHANGE(IDC_SSD_LIST, OnSelchangeSsdList)
	ON_BN_CLICKED(IDC_SSD_BUTTON_UNDO, OnSsdButtonUndo)
	ON_BN_CLICKED(IDC_SSD_BUTTON_REDO, OnSsdButtonRedo)
	ON_BN_CLICKED(IDC_SSD_BUTTON_SAVE, OnSsdButtonSave)
	ON_BN_CLICKED(IDC_SSD_BUTTON_SAVEAS, OnSsdButtonSaveas)
	ON_BN_CLICKED(IDC_SSD_BUTTON_MIRROR, OnSsdButtonMirror)
	ON_BN_CLICKED(IDC_SSD_BUTTON_SAVE_SCALE, OnSsdButtonSaveScale)
	ON_BN_CLICKED(IDC_SSD_BUTTON_LOAD_SCALE, OnSsdButtonLoadScale)
	ON_WM_CLOSE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSkeletonScaleDlg message handlers

void CSkeletonScaleDlg::OnDestroy() 
{
	setRegisterWindowState (this, REGKEY_SKELETON_SCALE_DLG);		
	CDialog::OnDestroy();	
}



// ***************************************************************************
void		CSkeletonScaleDlg::setSkeletonToEdit(NL3D::CSkeletonModel *skel, const std::string &fileName)
{
	uint	i;

	_SkeletonModel= skel;
	_SkeletonFileName= fileName;


	// **** Setup File name
	_StaticFileName= fileName.c_str();

	// **** Setup Bone mirror
	_Bones.clear();
	if(_SkeletonModel)
	{
		_Bones.resize(_SkeletonModel->Bones.size());
		// copy from skel to mirror
		applySkeletonToMirror();
	}

	// **** reset bone bbox here
	_BoneBBoxes.clear();
	_BoneBBoxes.resize(_Bones.size());
	// delegate to drawSelection(), cause skins not still bound
	_BoneBBoxNeedRecompute= true;

	// **** Setup Bone List
	_BoneList.ResetContent();
	if(_SkeletonModel)
	{
		for(uint i=0;i<_SkeletonModel->Bones.size();i++)
		{
			const std::string		tabStr= "   ";
			std::string		name= _SkeletonModel->Bones[i].getBoneName();
			// append a tab for easy hierarchy
			uint	boneId= i;
			while((boneId=_SkeletonModel->Bones[boneId].getFatherId())!=-1)
				name= tabStr + name;
			// append to the list
			_BoneList.AddString(name.c_str());
		}
	}


	// **** unselect all widgets
	for(i=0;i<SidCount;i++)
	{
		_ScaleSliders[i]->SetRange(0, NL_SSD_SLIDER_SIZE);
		_ScaleSliders[i]->SetPos(NL_SSD_SLIDER_SIZE/2);
		*_ScaleEdits[i]= "";
		_StaticScaleMarkers[i]->SetWindowText("100%");
	}
	// ensure no problem with scale and ALT-TAB
	_SliderEdited= SidNone;
	

	// **** clean undo/redo
	_UndoQueue.clear();
	_RedoQueue.clear();
	refreshUndoRedoView();


	// **** clear save button
	_SaveDirty= false;
	refreshSaveButton();


	// refresh
	UpdateData(FALSE);
}


// ***************************************************************************
BOOL CSkeletonScaleDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	setSkeletonToEdit(NULL, "");
	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}



// ***************************************************************************
void CSkeletonScaleDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	CSliderCtrl	*sliderCtrl= (CSliderCtrl*)pScrollBar;
	TScaleId	sliderId= getScaleIdFromSliderCtrl(sliderCtrl);
	if(sliderId!=SidNone && nSBCode==SB_THUMBPOSITION || nSBCode==SB_THUMBTRACK)
	{
		// If the user press ALT-Tab while dragging an old slider, the old slider is not released.
		// ThereFore, release the old one now
		if(_SliderEdited!=SidNone && _SliderEdited!=sliderId)
		{
			onSliderReleased(_SliderEdited);
		}
		
		// if begin of slide, bkup state
		if(_SliderEdited==SidNone)
		{
			_SliderEdited= sliderId;
			// Bkup all scales (dont bother selected bones or which scale is edited...)
			_BkupBones= _Bones;
		}
		
		//applyScale
		applyScaleSlider(nPos);
	}
	else
		CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
}

// ***************************************************************************
void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderBoneSx(NMHDR* pNMHDR, LRESULT* pResult) 
{
	onSliderReleased(SidBoneX);
	*pResult = 0;
}
void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderBoneSy(NMHDR* pNMHDR, LRESULT* pResult) 
{
	onSliderReleased(SidBoneY);
	*pResult = 0;
}
void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderBoneSz(NMHDR* pNMHDR, LRESULT* pResult) 
{
	onSliderReleased(SidBoneZ);
	*pResult = 0;
}
void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderSkinSx(NMHDR* pNMHDR, LRESULT* pResult) 
{
	onSliderReleased(SidSkinX);
	*pResult = 0;
}
void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderSkinSy(NMHDR* pNMHDR, LRESULT* pResult) 
{
	onSliderReleased(SidSkinY);
	*pResult = 0;
}
void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderSkinSz(NMHDR* pNMHDR, LRESULT* pResult) 
{
	onSliderReleased(SidSkinZ);
	*pResult = 0;
}

void		CSkeletonScaleDlg::onSliderReleased(TScaleId sid)
{
	// Get value from slider
	sint	value= _ScaleSliders[sid]->GetPos();
	
	// If the user press ALT-Tab while dragging an old slider, the old slider is not released.
	// ThereFore, release the old one now
	if(_SliderEdited!=SidNone && _SliderEdited!=sid)
	{
		onSliderReleased(_SliderEdited);
	}
	
	//applyScale
	if(_SliderEdited==SidNone)
	{
		_SliderEdited= sid;
		// Bkup all scales (dont bother selected bones or which scale is edited...)
		_BkupBones= _Bones;
	}

	// apply the final value
	applyScaleSlider(value);
	
	// And reset the slider
	_ScaleSliders[_SliderEdited]->SetPos(NL_SSD_SLIDER_SIZE/2);
	_StaticScaleMarkers[_SliderEdited]->SetWindowText("100%");
	_SliderEdited= SidNone;

	// push an undo/redo only at release of slide. push the value of scale before slide
	pushUndoState(_BkupBones, true);
}


// ***************************************************************************
void		CSkeletonScaleDlg::applyScaleSlider(sint scrollValue)
{
	// get scale beetween -1 and 1.
	float	scale= (NL_SSD_SLIDER_SIZE/2-scrollValue)/(float)(NL_SSD_SLIDER_SIZE/2);
	NLMISC::clamp(scale, -1.f, 1.f);
	float	factor;
	
	// no scale
	if(fabs(scale)<NL_SSD_SLIDER_THRESHOLD)
		factor=1;
	// scale down
	else if(scale<0)
	{
		float	minv= 1.0f / NL_SSD_SLIDER_SCALE;
		factor= minv*(-scale) + 1.0f*(1+scale);
	}
	// scale up
	else
	{
		float	maxv= NL_SSD_SLIDER_SCALE;
		factor= maxv*(scale) + 1.0f*(1-scale);
	}
	
	// Apply the noise to each selected bones
	for(uint i=0;i<_Bones.size();i++)
	{
		CBoneMirror	&bone= _Bones[i];
		CBoneMirror	&bkup= _BkupBones[i];
		if(bone.Selected)
		{
			// apply to the scaled component
			switch(_SliderEdited)
			{
			case SidBoneX:	bone.BoneScale.x= bkup.BoneScale.x *factor; break;
			case SidBoneY:	bone.BoneScale.y= bkup.BoneScale.y *factor; break;
			case SidBoneZ:	bone.BoneScale.z= bkup.BoneScale.z *factor; break;
			case SidSkinX:	bone.SkinScale.x= bkup.SkinScale.x *factor; break;
			case SidSkinY:	bone.SkinScale.y= bkup.SkinScale.y *factor; break;
			case SidSkinZ:	bone.SkinScale.z= bkup.SkinScale.z *factor; break;
			};
			// round result
			roundClampScale(bone.BoneScale);
			roundClampScale(bone.SkinScale);
		}
	}

	// update the skeleton view
	applyMirrorToSkeleton();

	// refresh text views
	refreshTextViews();

	// update marker text
	char	str[256];
	sprintf(str, "%d%%", (sint)(factor*100));
	_StaticScaleMarkers[_SliderEdited]->SetWindowText(str);
}

// ***************************************************************************
void		CSkeletonScaleDlg::applyMirrorToSkeleton()
{
	if(_SkeletonModel)
	{
		nlassert(_SkeletonModel->Bones.size()==_Bones.size());
		for(uint i=0;i<_Bones.size();i++)
		{
			// unmul from precision
			_SkeletonModel->Bones[i].setScale(_Bones[i].BoneScale / NL_SSD_SCALE_PRECISION);
			_SkeletonModel->Bones[i].setSkinScale(_Bones[i].SkinScale / NL_SSD_SCALE_PRECISION);
		}
	}
}


// ***************************************************************************
void		CSkeletonScaleDlg::applySkeletonToMirror()
{
	if(_SkeletonModel)
	{
		nlassert(_SkeletonModel->Bones.size()==_Bones.size());
		for(uint i=0;i<_SkeletonModel->Bones.size();i++)
		{
			// mul by precision, and round
			_Bones[i].SkinScale= _SkeletonModel->Bones[i].getSkinScale() * NL_SSD_SCALE_PRECISION;
			_Bones[i].BoneScale= _SkeletonModel->Bones[i].getScale() * NL_SSD_SCALE_PRECISION;
			roundClampScale(_Bones[i].SkinScale);
			roundClampScale(_Bones[i].BoneScale);
		}
	}
}


// ***************************************************************************
void		CSkeletonScaleDlg::refreshTextViews()
{
	// 1.f for each component if multiple selection is different, else 0.f
	NLMISC::CVector		boneScaleDiff= NLMISC::CVector::Null;
	NLMISC::CVector		skinScaleDiff= NLMISC::CVector::Null;
	// valid if scale of each bone component is same
	NLMISC::CVector		boneScaleAll= NLMISC::CVector::Null;
	NLMISC::CVector		skinScaleAll= NLMISC::CVector::Null;
	bool		someSelected= false;
	
	// For all bones selected
	for(uint i=0;i<_Bones.size();i++)
	{
		CBoneMirror	&bone= _Bones[i];
		if(bone.Selected)
		{
			if(!someSelected)
			{
				someSelected= true;
				// just bkup
				boneScaleAll= bone.BoneScale;
				skinScaleAll= bone.SkinScale;
			}
			else
			{
				// compare each component, if different, flag
				if(boneScaleAll.x!= bone.BoneScale.x)	boneScaleDiff.x= 1.f;
				if(boneScaleAll.y!= bone.BoneScale.y)	boneScaleDiff.y= 1.f;
				if(boneScaleAll.z!= bone.BoneScale.z)	boneScaleDiff.z= 1.f;
				if(skinScaleAll.x!= bone.SkinScale.x)	skinScaleDiff.x= 1.f;
				if(skinScaleAll.y!= bone.SkinScale.y)	skinScaleDiff.y= 1.f;
				if(skinScaleAll.z!= bone.SkinScale.z)	skinScaleDiff.z= 1.f;
			}
		}
	}
	
	// if none selected, force empty text
	if(!someSelected)
	{
		boneScaleDiff.set(1.f,1.f,1.f);
		skinScaleDiff.set(1.f,1.f,1.f);
	}

	// All component refresh or only one refresh?
	nlassert(SidCount==6);
	refreshTextViewWithScale(SidBoneX, boneScaleAll.x, boneScaleDiff.x);
	refreshTextViewWithScale(SidBoneY, boneScaleAll.y, boneScaleDiff.y);
	refreshTextViewWithScale(SidBoneZ, boneScaleAll.z, boneScaleDiff.z);
	refreshTextViewWithScale(SidSkinX, skinScaleAll.x, skinScaleDiff.x);
	refreshTextViewWithScale(SidSkinY, skinScaleAll.y, skinScaleDiff.y);
	refreshTextViewWithScale(SidSkinZ, skinScaleAll.z, skinScaleDiff.z);

	// refresh
	UpdateData(FALSE);
}


// ***************************************************************************
void		CSkeletonScaleDlg::refreshTextViewWithScale(TScaleId sid, float scale, float diff)
{
	// if different values selected, diff
	if(diff)
	{
		*_ScaleEdits[sid]= "";
	}
	// else display text
	else
	{
		char	str[256];
		// ensure correct display with no precision problem
		sint	value= uint(floor(scale+0.5f));
		sint	whole= value*100/NL_SSD_SCALE_PRECISION;
		sint	decimal= value - whole*(NL_SSD_SCALE_PRECISION/100);
		sprintf(str, "%d.%d", whole, decimal);
		*_ScaleEdits[sid]= str;
	}
}


// ***************************************************************************
void		CSkeletonScaleDlg::roundClampScale(NLMISC::CVector &v) const
{
	v.x+= 0.5f;
	v.y+= 0.5f;
	v.z+= 0.5f;
	v.x= (float)floor(v.x);
	v.y= (float)floor(v.y);
	v.z= (float)floor(v.z);
	// Minimum is 1 (avoid 0 scale)
	v.maxof(v, NLMISC::CVector(1.f,1.f,1.f));
}

// ***************************************************************************
CSkeletonScaleDlg::TScaleId	CSkeletonScaleDlg::getScaleIdFromSliderCtrl(CSliderCtrl	*sliderCtrl) const
{
	for(uint i=0;i<SidCount;i++)
	{
		if(sliderCtrl==_ScaleSliders[i])
			return (TScaleId)i;
	}

	return SidNone;
}


// ***************************************************************************
CSkeletonScaleDlg::TScaleId	CSkeletonScaleDlg::getScaleIdFromEditId(UINT ctrlId) const
{
	nlctassert(SidCount==6);
	if(ctrlId==IDC_SSD_EDIT_BONE_SX)	return SidBoneX;
	if(ctrlId==IDC_SSD_EDIT_BONE_SY)	return SidBoneY;
	if(ctrlId==IDC_SSD_EDIT_BONE_SZ)	return SidBoneZ;
	if(ctrlId==IDC_SSD_EDIT_SKIN_SX)	return SidSkinX;
	if(ctrlId==IDC_SSD_EDIT_SKIN_SY)	return SidSkinY;
	if(ctrlId==IDC_SSD_EDIT_SKIN_SZ)	return SidSkinZ;
	
	return SidNone;
}


// ***************************************************************************
void CSkeletonScaleDlg::OnChangeSsdEditBoneSx() 
{
	onChangeEditText(IDC_SSD_EDIT_BONE_SX);
}
void CSkeletonScaleDlg::OnChangeSsdEditBoneSy() 
{
	onChangeEditText(IDC_SSD_EDIT_BONE_SY);
}
void CSkeletonScaleDlg::OnChangeSsdEditBoneSz() 
{
	onChangeEditText(IDC_SSD_EDIT_BONE_SZ);
}
void CSkeletonScaleDlg::OnChangeSsdEditSkinSx() 
{
	onChangeEditText(IDC_SSD_EDIT_SKIN_SX);
}
void CSkeletonScaleDlg::OnChangeSsdEditSkinSy() 
{
	onChangeEditText(IDC_SSD_EDIT_SKIN_SY);
}
void CSkeletonScaleDlg::OnChangeSsdEditSkinSz() 
{
	onChangeEditText(IDC_SSD_EDIT_SKIN_SZ);
}


static	void concatEdit2Lines(CEdit &edit)
{
	const	uint lineLen= 1000;
	uint	n;
	// retrieve the 2 lines.
	char	tmp0[2*lineLen];
	char	tmp1[lineLen];
	n= edit.GetLine(0, tmp0, lineLen);	tmp0[n]= 0;
	n= edit.GetLine(1, tmp1, lineLen);	tmp1[n]= 0;
	// concat and update the CEdit.
	edit.SetWindowText(strcat(tmp0, tmp1));
}


void CSkeletonScaleDlg::onChangeEditText(UINT ctrlId)
{
	CEdit *ce = (CEdit*)GetDlgItem(ctrlId);
	if(ce)
	{
		UpdateData();	
		// Trick to track "Enter" keypress: CEdit are multiline. If GetLineCount()>1, then
		// user has press enter.
		if(ce->GetLineCount()>1)
		{
			// must ccat 2 lines of the CEdit.
			concatEdit2Lines(*ce);
			// update text members
			UpdateData(FALSE);
			UpdateData();

			// update scale values from the CEdit
			updateScalesFromText(ctrlId);

			// then re-update CEdit from scale values
			refreshTextViews();
		}
	}
}


// ***************************************************************************
void CSkeletonScaleDlg::OnKillfocusSsdEditBoneSx() 
{
	onQuitEditText(IDC_SSD_EDIT_BONE_SX);
}
void CSkeletonScaleDlg::OnKillfocusSsdEditBoneSy() 
{
	onQuitEditText(IDC_SSD_EDIT_BONE_SY);
}
void CSkeletonScaleDlg::OnKillfocusSsdEditBoneSz() 
{
	onQuitEditText(IDC_SSD_EDIT_BONE_SZ);
}
void CSkeletonScaleDlg::OnKillfocusSsdEditSkinSx() 
{
	onQuitEditText(IDC_SSD_EDIT_SKIN_SX);
}
void CSkeletonScaleDlg::OnKillfocusSsdEditSkinSy() 
{
	onQuitEditText(IDC_SSD_EDIT_SKIN_SY);
}
void CSkeletonScaleDlg::OnKillfocusSsdEditSkinSz() 
{
	onQuitEditText(IDC_SSD_EDIT_SKIN_SZ);
}


void CSkeletonScaleDlg::onQuitEditText(UINT ctrlId)
{
	CEdit *ce = (CEdit*)GetDlgItem(ctrlId);
	if(ce)
	{
		UpdateData();	
			
		// update scale values from the CEdit
		updateScalesFromText(ctrlId);
		
		// then re-update CEdit from scale values
		refreshTextViews();
	}
}


// ***************************************************************************
void CSkeletonScaleDlg::OnSetfocusSsdEditBoneSx() 
{
	onSelectEditText(IDC_SSD_EDIT_BONE_SX);
}
void CSkeletonScaleDlg::OnSetfocusSsdEditBoneSy() 
{
	onSelectEditText(IDC_SSD_EDIT_BONE_SY);
}
void CSkeletonScaleDlg::OnSetfocusSsdEditBoneSz() 
{
	onSelectEditText(IDC_SSD_EDIT_BONE_SZ);
}
void CSkeletonScaleDlg::OnSetfocusSsdEditSkinSx() 
{
	onSelectEditText(IDC_SSD_EDIT_SKIN_SX);
}
void CSkeletonScaleDlg::OnSetfocusSsdEditSkinSy() 
{
	onSelectEditText(IDC_SSD_EDIT_SKIN_SY);
}
void CSkeletonScaleDlg::OnSetfocusSsdEditSkinSz() 
{
	onSelectEditText(IDC_SSD_EDIT_SKIN_SZ);
}

void CSkeletonScaleDlg::onSelectEditText(UINT ctrlId)
{
	CEdit *ce = (CEdit*)GetDlgItem(ctrlId);
	if(ce)
	{
		ce->PostMessage(EM_SETSEL, 0, -1);	
		ce->Invalidate();
	}
}


// ***************************************************************************
void CSkeletonScaleDlg::updateScalesFromText(UINT ctrlId)
{
	TScaleId	sid= getScaleIdFromEditId(ctrlId);
	if(sid==SidNone)
		return;

	// get the scale info
	std::string	str= (const char*)(*_ScaleEdits[sid]);
	if(str.empty())
		return;
	float	f;
	sscanf(str.c_str(), "%f", &f);
	// edit a %age
	f*= NL_SSD_SCALE_PRECISION/100;
	f= (float)floor(f+0.5f);

	// bkup for undo
	static TBoneMirrorArray		precState;
	precState= _Bones;
	
	// For all bones selected, set the edited value
	for(uint i=0;i<_Bones.size();i++)
	{
		CBoneMirror	&bone= _Bones[i];
		if(bone.Selected)
		{
			switch(sid)
			{
			case SidBoneX:	bone.BoneScale.x= f; break;
			case SidBoneY:	bone.BoneScale.y= f; break;
			case SidBoneZ:	bone.BoneScale.z= f; break;
			case SidSkinX:	bone.SkinScale.x= f; break;
			case SidSkinY:	bone.SkinScale.y= f; break;
			case SidSkinZ:	bone.SkinScale.z= f; break;
			};
			// normalize
			roundClampScale(bone.BoneScale);
			roundClampScale(bone.SkinScale);
		}
	}

	// change => bkup for undo
	pushUndoState(precState, true);
	
	// mirror changed => update skeleton
	applyMirrorToSkeleton();
}



// ***************************************************************************
void CSkeletonScaleDlg::OnSelchangeSsdList() 
{
	UpdateData();

	// **** Retrieve List of selected bones.
	uint	count= _BoneList.GetSelCount();
	std::vector<int>	items;
	if(count)
	{
		items.resize(count);
		_BoneList.GetSelItems(count, &items[0]);
	}

	// **** update the Selection array
	// bkup for undo
	static TBoneMirrorArray		precState;
	precState= _Bones;

	// identify selected items in a set
	std::set<int>	selSet;
	uint	i;
	for(i=0;i<count;i++)
		selSet.insert(items[i]);

	// change selection of Bones
	for(i=0;i<_Bones.size();i++)
	{
		if(selSet.find(i)!=selSet.end())
			_Bones[i].Selected= true;
		else
			_Bones[i].Selected= false;
	}

	// **** undo-redo
	// selection change => no need to dirt save
	pushUndoState(precState, false);

	// **** refresh text views
	refreshTextViews();
}


// ***************************************************************************
void CSkeletonScaleDlg::OnSsdButtonUndo() 
{
	undo();
}

void CSkeletonScaleDlg::OnSsdButtonRedo() 
{
	redo();
}


// ***************************************************************************
void CSkeletonScaleDlg::OnSsdButtonSave() 
{
	// if no skeleton edited, quit
	if(!_SkeletonModel)
		return;

	// save the file
	NLMISC::COFile	f;
	if( f.open(_SkeletonFileName) )
	{
		if(saveCurrentInStream(f))
		{
			// no more need to save (done)
			_SaveDirty= false;
			refreshSaveButton();
		}
	}
	else
	{
		MessageBox("Failed to open file for write!");
	}
}

void CSkeletonScaleDlg::OnSsdButtonSaveas() 
{
	// if no skeleton edited, quit
	if(!_SkeletonModel)
		return;

	// choose the file
	CFileDialog fd(FALSE, "skel", _SkeletonFileName.c_str(), OFN_OVERWRITEPROMPT, "SkelFiles (*.skel)|*.skel|All Files (*.*)|*.*||", this) ;
	fd.m_ofn.lpstrTitle= "Save As Skeleton";
	if (fd.DoModal() == IDOK)
	{
		NLMISC::COFile	f;
		
		if( f.open((const char*)fd.GetPathName()) )
		{
			if(saveCurrentInStream(f))
			{
				// no more need to save (done)
				_SaveDirty= false;
				refreshSaveButton();
			}

			// bkup the valid fileName (new file edited)
			_SkeletonFileName= (const char*)fd.GetPathName();
			_StaticFileName= _SkeletonFileName.c_str();
			UpdateData(FALSE);
		}
		else
		{
			MessageBox("Failed to open file for write!");
		}
	}
}

// ***************************************************************************
bool CSkeletonScaleDlg::saveCurrentInStream(NLMISC::IStream &f)
{
	try
	{
		nlassert(_SkeletonModel);
		nlassert(_SkeletonModel->Shape);
		
		// Retrieve boneBase definition from the current skeleton
		std::vector<NL3D::CBoneBase>	boneBases;
		(NLMISC::safe_cast<NL3D::CSkeletonShape*>((NL3D::IShape*)_SkeletonModel->Shape))->retrieve(boneBases);
		
		// Copies bone scales from the model 
		nlassert(boneBases.size()==_SkeletonModel->Bones.size());
		for(uint i=0;i<_SkeletonModel->Bones.size();i++)
		{
			NL3D::CBone		&bone= _SkeletonModel->Bones[i];
			NL3D::CBoneBase	&boneBase= boneBases[i];

			boneBase.SkinScale= bone.getSkinScale();
			boneBase.DefaultScale= bone.getScale();
		}
		
		// build a new Skeleton shape
		NL3D::CSkeletonShape	*skelShape= new NL3D::CSkeletonShape;
		skelShape->build(boneBases);
		
		
		// save the vegetable
		NL3D::CShapeStream	ss;
		ss.setShapePointer(skelShape);
		ss.serial(f);
		delete skelShape;
	}
	catch(NLMISC::EStream &)
	{
		MessageBox("Failed to save file!");
		return false;
	}

	return true;
}


// ***************************************************************************
void		CSkeletonScaleDlg::pushUndoState(const TBoneMirrorArray &precState, bool dirtSave)
{
	// **** test if real change
	nlassert(precState.size()==_Bones.size());
	bool	change= false;
	for(uint i=0;i<_Bones.size();i++)
	{
		if( _Bones[i].BoneScale!=precState[i].BoneScale ||
			_Bones[i].SkinScale!=precState[i].SkinScale || 
			_Bones[i].Selected!=precState[i].Selected)
		{
			change= true;
			break;
		}
	}
	// no change? no op
	if(!change)
		return;
	

	// **** then bkup for undo
	// change => the redo list is lost
	_RedoQueue.clear();

	// if not enough space, the last undo is lost
	if(_UndoQueue.size()==MaxUndoRedo)
		_UndoQueue.pop_front();

	// add the precedent state to the undo queue
	_UndoQueue.push_back(precState);

	// refresh buttons
	refreshUndoRedoView();

	// refresh save button
	if(dirtSave && !_SaveDirty)
	{
		_SaveDirty= true;
		refreshSaveButton();
	}
}

// ***************************************************************************
void		CSkeletonScaleDlg::undo()
{
	nlassert(_UndoQueue.size()+_RedoQueue.size()<=MaxUndoRedo);

	// is some undoable
	if(_UndoQueue.size())
	{
		// current goes into the redo queue
		_RedoQueue.push_front(_Bones);
		// restore
		_Bones= _UndoQueue.back();
		// pop
		_UndoQueue.pop_back();

		// refresh display
		applyMirrorToSkeleton();
		refreshTextViews();
		applySelectionToView();

		// refresh buttons
		refreshUndoRedoView();

		// change => must save
		_SaveDirty= true;
		refreshSaveButton();
	}
}

// ***************************************************************************
void		CSkeletonScaleDlg::redo()
{
	nlassert(_UndoQueue.size()+_RedoQueue.size()<=MaxUndoRedo);

	// is some redoable
	if(_RedoQueue.size())
	{
		// current goes into the undo queue
		_UndoQueue.push_back(_Bones);
		// restore
		_Bones= _RedoQueue.front();
		// pop
		_RedoQueue.pop_front();
		
		// refresh display
		applyMirrorToSkeleton();
		refreshTextViews();
		applySelectionToView();

		// refresh buttons
		refreshUndoRedoView();

		// change => must save
		_SaveDirty= true;
		refreshSaveButton();
	}
}


// ***************************************************************************
void		CSkeletonScaleDlg::applySelectionToView()
{
	// update list box selection according to state
	nlassert(_Bones.size()==(uint)_BoneList.GetCount());
	for(uint i=0;i<_Bones.size();i++)
	{
		_BoneList.SetSel(i, _Bones[i].Selected);
	}
	UpdateData(FALSE);
}

// ***************************************************************************
void		CSkeletonScaleDlg::refreshUndoRedoView()
{
	CWnd	*wnd;
	wnd= GetDlgItem(IDC_SSD_BUTTON_UNDO);
	if(wnd)
		wnd->EnableWindow(!_UndoQueue.empty());
	wnd= GetDlgItem(IDC_SSD_BUTTON_REDO);
	if(wnd)
		wnd->EnableWindow(!_RedoQueue.empty());
}


// ***************************************************************************
void		CSkeletonScaleDlg::refreshSaveButton()
{
	// SaveAs is always available
	CWnd	*wnd= GetDlgItem(IDC_SSD_BUTTON_SAVE);
	if(wnd)
		wnd->EnableWindow(_SaveDirty);
}


// ***************************************************************************
sint CSkeletonScaleDlg::getBoneForMirror(uint boneId, std::string &mirrorName)
{
	sint	side= 0;
	std::string::size_type pos;
	nlassert(_SkeletonModel && boneId<_SkeletonModel->Bones.size());
	mirrorName= _SkeletonModel->Bones[boneId].getBoneName();

	if((pos= mirrorName.find(" R "))!=std::string::npos)
	{
		side= 1;
		mirrorName[pos+1]= 'L';
	}
	else if((pos= mirrorName.find(" L "))!=std::string::npos)
	{
		side= -1;
		mirrorName[pos+1]= 'R';
	}
	
	return side;
}


// ***************************************************************************
void CSkeletonScaleDlg::OnSsdButtonMirror() 
{
	// bkup for undo
	static TBoneMirrorArray		precState;
	precState= _Bones;
	nlassert(_SkeletonModel);

	// for each bone selected
	bool	change= false;
	for(uint i=0;i<_Bones.size();i++)
	{
		CBoneMirror	&bone= _Bones[i];
		if(bone.Selected)
		{
			// get the bone side and mirrored name
			std::string		mirrorName;
			sint	side= getBoneForMirror(i, mirrorName);
			// if not a "centered" bone
			if(side!=0)
			{
				// get the bone with mirrored name
				sint	mirrorId= _SkeletonModel->getBoneIdByName(mirrorName);
				if(mirrorId<0)
				{
					nlinfo("MirrorScale: Didn't found %s", mirrorName.c_str());
				}
				else
				{
					// copy setup from the dest bone
					nlassert(mirrorId<(sint)_Bones.size());
					_Bones[mirrorId].BoneScale= bone.BoneScale;
					_Bones[mirrorId].SkinScale= bone.SkinScale;
				}
			}
		}
	}

	// refresh display
	applyMirrorToSkeleton();
	refreshTextViews();
	
	// if some change, bkup for undo/redo
	pushUndoState(precState, true);
}



// ***************************************************************************
void	CSkeletonScaleDlg::drawSelection()
{
	if(!_SkeletonModel)
		return;

	nlassert(_SkeletonModel->Bones.size()==_Bones.size());
	
	// **** Need recompute Bones bbox?
	if(_BoneBBoxNeedRecompute)
	{
		_BoneBBoxNeedRecompute= false;

		// for all bones
		for(uint i=0;i<_SkeletonModel->Bones.size();i++)
		{
			NLMISC::CAABBox		boneBBox;
			bool				empty= true;
			
			// for all instances skinned
			const std::set<NL3D::CTransform*>		&skins= _SkeletonModel->getSkins();
			std::set<NL3D::CTransform*>::const_iterator		it;
			for(it=skins.begin();it!=skins.end();it++)
			{
				NL3D::CTransform	*skin= *it;
				NLMISC::CAABBox		skinBoneBBox;
				// if the skin is skinned to this bone
				if(skin->getSkinBoneBBox(skinBoneBBox, i))
				{
					// set or enlarge the bone bbox
					if(empty)
					{
						empty= false;
						boneBBox= skinBoneBBox;
					}
					else
					{
						boneBBox= NLMISC::CAABBox::computeAABBoxUnion(boneBBox, skinBoneBBox);
					}
				}
			}
			
			// if there is no skin influence on this bone, still display a tiny bbox, to see the scale
			const	float	defSize= 0.05f;
			if(empty)
			{
				boneBBox.setCenter(NLMISC::CVector::Null);
				boneBBox.setSize(NLMISC::CVector(defSize, defSize, defSize));
			}
			
			// assign
			_BoneBBoxes[i]= boneBBox;
		}
	}


	// **** Draw each selected bone
	for(uint i=0;i<_SkeletonModel->Bones.size();i++)
	{
		// if bone not selected, skip
		if(!_Bones[i].Selected)
			continue;

		// get the bone "Skin Matrix"
		NL3D::CBone		&bone= _SkeletonModel->Bones[i];
		// force compute of this bone
		if(!_SkeletonModel->isBoneComputed(i))
			_SkeletonModel->forceComputeBone(i);
		NLMISC::CMatrix		worldSkinMat= bone.getWorldMatrix();
		// scale with skin scale, because the localskeleton and world matrix do not have this scale
		worldSkinMat.scale(bone.getSkinScale());

		// Transform the local bbox in its associated matrix
		NLMISC::CMatrix		matBBox;
		NLMISC::CAABBox		bbox= _BoneBBoxes[i];
		matBBox.setPos(bbox.getCenter());
		matBBox.setRot(
			NLMISC::CVector::I * bbox.getSize().x, 
			NLMISC::CVector::J * bbox.getSize().y, 
			NLMISC::CVector::K * bbox.getSize().z);
		NLMISC::CMatrix		finalMat;
		finalMat.setMulMatrixNoProj(worldSkinMat, matBBox);

		//Draw a wired bbox with this bone
		NLMISC::CVector		corner= finalMat.getPos() - finalMat.getI()/2 - finalMat.getJ()/2 - finalMat.getK()/2;
		NL3D::IDriver		*driver= NL3D::CNELU::Driver;
		driver->setupModelMatrix(NLMISC::CMatrix::Identity);
		NL3D::CDRU::drawWiredBox(corner, finalMat.getI(), finalMat.getJ(), finalMat.getK(), NLMISC::CRGBA::White, *driver);
	}
}


// ***************************************************************************
void CSkeletonScaleDlg::OnSsdButtonSaveScale() 
{
	// if no skeleton edited, quit
	if(!_SkeletonModel)
		return;
	
	// choose the file
	std::string	defaultFileName= _SkeletonFileName;
	NLMISC::strFindReplace(defaultFileName, ".skel", ".scale");
	CFileDialog fd(FALSE, "scale", defaultFileName.c_str(), OFN_OVERWRITEPROMPT, "SkelScaleFiles (*.scale)|*.scale|All Files (*.*)|*.*||", this) ;
	fd.m_ofn.lpstrTitle= "Save As Skeleton Scale File";
	if (fd.DoModal() == IDOK)
	{
		NLMISC::COFile	f;
		if( f.open((const char*)fd.GetPathName()) )
		{
			saveSkelScaleInStream(f);
		}
		else
		{
			MessageBox("Failed to open file for write!");
		}
	}
}

// ***************************************************************************
void CSkeletonScaleDlg::OnSsdButtonLoadScale() 
{
	// if no skeleton edited, quit
	if(!_SkeletonModel)
		return;
	
	// choose the file
	std::string	defaultFileName= _SkeletonFileName;
	NLMISC::strFindReplace(defaultFileName, ".skel", ".scale");
	CFileDialog fd(TRUE, "scale", defaultFileName.c_str(), 0, "SkelScaleFiles (*.scale)|*.scale|All Files (*.*)|*.*||", this) ;
	fd.m_ofn.lpstrTitle= "Load a Skeleton Scale File";
	if (fd.DoModal() == IDOK)
	{
		NLMISC::CIFile	f;
		if( f.open((const char*)fd.GetPathName()) )
		{
			loadSkelScaleFromStream(f);
		}
		else
		{
			MessageBox("Failed to open file for read!");
		}
	}
}


// ***************************************************************************
struct CBoneScaleInfo
{
	std::string	Name;
	NLMISC::CVector	Scale;
	NLMISC::CVector	SkinScale;

	void	serial(NLMISC::IStream &f)
	{
		sint32	ver= f.serialVersion(0);
		f.serial(Name, Scale, SkinScale);
	}
};

// ***************************************************************************
bool	CSkeletonScaleDlg::saveSkelScaleInStream(NLMISC::IStream &f)
{
	try
	{
		nlassert(_SkeletonModel);
		
		// Copies bone scales from the model 
		std::vector<CBoneScaleInfo>	boneScales;
		boneScales.resize(_SkeletonModel->Bones.size());
		for(uint i=0;i<boneScales.size();i++)
		{
			NL3D::CBone		&bone= _SkeletonModel->Bones[i];
			CBoneScaleInfo	&boneScale= boneScales[i];
			
			// get scale info from current edited skeleton
			boneScale.Name= bone.getBoneName();
			boneScale.Scale= bone.getScale();
			boneScale.SkinScale= bone.getSkinScale();
		}
		
		// save the file
		sint32	ver= f.serialVersion(0);
		f.serialCont(boneScales);
	}
	catch(NLMISC::EStream &)
	{
		MessageBox("Failed to save file!");
		return false;
	}
	
	return true;
}

// ***************************************************************************
bool	CSkeletonScaleDlg::loadSkelScaleFromStream(NLMISC::IStream &f)
{
	try
	{
		nlassert(_SkeletonModel);
		
		// load the file
		sint32	ver= f.serialVersion(0);
		std::vector<CBoneScaleInfo>	boneScales;
		f.serialCont(boneScales);

		// apply to the current skeleton
		for(uint i=0;i<boneScales.size();i++)
		{
			sint32	boneId= _SkeletonModel->getBoneIdByName(boneScales[i].Name);
			if(boneId>=0 && boneId<(sint32)_SkeletonModel->Bones.size())
			{
				CBoneScaleInfo	&boneScale= boneScales[i];
				_SkeletonModel->Bones[boneId].setScale(boneScale.Scale);
				_SkeletonModel->Bones[boneId].setSkinScale(boneScale.SkinScale);
			}
		}

		// Bkup _Bones, for undo
		static TBoneMirrorArray		precState;
		precState= _Bones;
		
		// Then reapply to the mirror
		applySkeletonToMirror();

		// change => must save
		pushUndoState(precState, true);
		
		// and update display
		refreshTextViews();
	}
	catch(NLMISC::EStream &)
	{
		MessageBox("Failed to save file!");
		return false;
	}
	
	return true;
}


void CSkeletonScaleDlg::OnClose() 
{
	_ObjViewer->getMainFrame()->OnWindowSkeletonScale();
}