Added aqua_speed for rite geo 50 tryker
[ryzomcore.git] / nel / tools / 3d / object_viewer / skeleton_scale_dlg.cpp
blob9c1ba880910d7a27386d7f90a6e4c23b742dc0fc
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 // skeleton_scale_dlg.cpp : implementation file
23 #include "std_afx.h"
24 #include "object_viewer.h"
25 #include "skeleton_scale_dlg.h"
26 #include "nel/3d/skeleton_shape.h"
27 #include "nel/misc/algo.h"
28 #include "main_frame.h"
31 // ***************************************************************************
32 #define NL_SSD_SLIDER_SIZE 1000
33 #define NL_SSD_SLIDER_THRESHOLD 0.03f
34 #define NL_SSD_SLIDER_SCALE 2
37 /////////////////////////////////////////////////////////////////////////////
38 // CSkeletonScaleDlg dialog
41 CSkeletonScaleDlg::CSkeletonScaleDlg(CObjectViewer *viewer, CWnd* pParent /*=NULL*/)
42 : CDialog(CSkeletonScaleDlg::IDD, pParent) ,_ObjViewer(viewer)
44 //{{AFX_DATA_INIT(CSkeletonScaleDlg)
45 _StaticFileName = _T("");
46 _EditBoneSX = _T("");
47 _EditBoneSY = _T("");
48 _EditBoneSZ = _T("");
49 _EditSkinSX = _T("");
50 _EditSkinSY = _T("");
51 _EditSkinSZ = _T("");
52 //}}AFX_DATA_INIT
54 // Init Scale Sliders ptrs
55 nlassert(SidCount==6);
56 _ScaleSliders[SidBoneX]= &_SliderBoneX;
57 _ScaleSliders[SidBoneY]= &_SliderBoneY;
58 _ScaleSliders[SidBoneZ]= &_SliderBoneZ;
59 _ScaleSliders[SidSkinX]= &_SliderSkinX;
60 _ScaleSliders[SidSkinY]= &_SliderSkinY;
61 _ScaleSliders[SidSkinZ]= &_SliderSkinZ;
62 _ScaleEdits[SidBoneX]= &_EditBoneSX;
63 _ScaleEdits[SidBoneY]= &_EditBoneSY;
64 _ScaleEdits[SidBoneZ]= &_EditBoneSZ;
65 _ScaleEdits[SidSkinX]= &_EditSkinSX;
66 _ScaleEdits[SidSkinY]= &_EditSkinSY;
67 _ScaleEdits[SidSkinZ]= &_EditSkinSZ;
68 _StaticScaleMarkers[SidBoneX]= &_StaticScaleMarkerBoneSX;
69 _StaticScaleMarkers[SidBoneY]= &_StaticScaleMarkerBoneSY;
70 _StaticScaleMarkers[SidBoneZ]= &_StaticScaleMarkerBoneSZ;
71 _StaticScaleMarkers[SidSkinX]= &_StaticScaleMarkerSkinSX;
72 _StaticScaleMarkers[SidSkinY]= &_StaticScaleMarkerSkinSY;
73 _StaticScaleMarkers[SidSkinZ]= &_StaticScaleMarkerSkinSZ;
76 _SliderEdited= SidNone;
77 _SaveDirty= false;
79 // avoid realloc
80 _UndoQueue.resize(MaxUndoRedo);
81 _RedoQueue.resize(MaxUndoRedo);
82 _UndoQueue.clear();
83 _RedoQueue.clear();
85 _BoneBBoxNeedRecompute= false;
88 CSkeletonScaleDlg::~CSkeletonScaleDlg()
93 void CSkeletonScaleDlg::DoDataExchange(CDataExchange* pDX)
95 CDialog::DoDataExchange(pDX);
96 //{{AFX_DATA_MAP(CSkeletonScaleDlg)
97 DDX_Control(pDX, IDC_SSD_SLIDER_SKIN_SZ, _SliderSkinZ);
98 DDX_Control(pDX, IDC_SSD_SLIDER_SKIN_SY, _SliderSkinY);
99 DDX_Control(pDX, IDC_SSD_SLIDER_SKIN_SX, _SliderSkinX);
100 DDX_Control(pDX, IDC_SSD_SLIDER_BONE_SZ, _SliderBoneZ);
101 DDX_Control(pDX, IDC_SSD_SLIDER_BONE_SY, _SliderBoneY);
102 DDX_Control(pDX, IDC_SSD_SLIDER_BONE_SX, _SliderBoneX);
103 DDX_Control(pDX, IDC_SSD_LIST, _BoneList);
104 DDX_Text(pDX, IDC_SSD_STATIC_FILENAME, _StaticFileName);
105 DDX_Text(pDX, IDC_SSD_EDIT_BONE_SX, _EditBoneSX);
106 DDX_Text(pDX, IDC_SSD_EDIT_BONE_SY, _EditBoneSY);
107 DDX_Text(pDX, IDC_SSD_EDIT_BONE_SZ, _EditBoneSZ);
108 DDX_Text(pDX, IDC_SSD_EDIT_SKIN_SX, _EditSkinSX);
109 DDX_Text(pDX, IDC_SSD_EDIT_SKIN_SY, _EditSkinSY);
110 DDX_Text(pDX, IDC_SSD_EDIT_SKIN_SZ, _EditSkinSZ);
111 DDX_Control(pDX, IDC_SSD_STATIC_SKIN_SZ, _StaticScaleMarkerSkinSZ);
112 DDX_Control(pDX, IDC_SSD_STATIC_SKIN_SY, _StaticScaleMarkerSkinSY);
113 DDX_Control(pDX, IDC_SSD_STATIC_SKIN_SX, _StaticScaleMarkerSkinSX);
114 DDX_Control(pDX, IDC_SSD_STATIC_BONE_SZ, _StaticScaleMarkerBoneSZ);
115 DDX_Control(pDX, IDC_SSD_STATIC_BONE_SY, _StaticScaleMarkerBoneSY);
116 DDX_Control(pDX, IDC_SSD_STATIC_BONE_SX, _StaticScaleMarkerBoneSX);
117 //}}AFX_DATA_MAP
121 BEGIN_MESSAGE_MAP(CSkeletonScaleDlg, CDialog)
122 //{{AFX_MSG_MAP(CSkeletonScaleDlg)
123 ON_WM_DESTROY()
124 ON_WM_VSCROLL()
125 ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_BONE_SX, OnReleasedcaptureSsdSliderBoneSx)
126 ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_BONE_SY, OnReleasedcaptureSsdSliderBoneSy)
127 ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_BONE_SZ, OnReleasedcaptureSsdSliderBoneSz)
128 ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_SKIN_SX, OnReleasedcaptureSsdSliderSkinSx)
129 ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_SKIN_SY, OnReleasedcaptureSsdSliderSkinSy)
130 ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_SSD_SLIDER_SKIN_SZ, OnReleasedcaptureSsdSliderSkinSz)
131 ON_EN_CHANGE(IDC_SSD_EDIT_BONE_SX, OnChangeSsdEditBoneSx)
132 ON_EN_CHANGE(IDC_SSD_EDIT_BONE_SY, OnChangeSsdEditBoneSy)
133 ON_EN_CHANGE(IDC_SSD_EDIT_BONE_SZ, OnChangeSsdEditBoneSz)
134 ON_EN_CHANGE(IDC_SSD_EDIT_SKIN_SX, OnChangeSsdEditSkinSx)
135 ON_EN_CHANGE(IDC_SSD_EDIT_SKIN_SY, OnChangeSsdEditSkinSy)
136 ON_EN_CHANGE(IDC_SSD_EDIT_SKIN_SZ, OnChangeSsdEditSkinSz)
137 ON_EN_KILLFOCUS(IDC_SSD_EDIT_BONE_SX, OnKillfocusSsdEditBoneSx)
138 ON_EN_KILLFOCUS(IDC_SSD_EDIT_BONE_SY, OnKillfocusSsdEditBoneSy)
139 ON_EN_KILLFOCUS(IDC_SSD_EDIT_BONE_SZ, OnKillfocusSsdEditBoneSz)
140 ON_EN_KILLFOCUS(IDC_SSD_EDIT_SKIN_SX, OnKillfocusSsdEditSkinSx)
141 ON_EN_KILLFOCUS(IDC_SSD_EDIT_SKIN_SY, OnKillfocusSsdEditSkinSy)
142 ON_EN_KILLFOCUS(IDC_SSD_EDIT_SKIN_SZ, OnKillfocusSsdEditSkinSz)
143 ON_EN_SETFOCUS(IDC_SSD_EDIT_BONE_SX, OnSetfocusSsdEditBoneSx)
144 ON_EN_SETFOCUS(IDC_SSD_EDIT_BONE_SY, OnSetfocusSsdEditBoneSy)
145 ON_EN_SETFOCUS(IDC_SSD_EDIT_BONE_SZ, OnSetfocusSsdEditBoneSz)
146 ON_EN_SETFOCUS(IDC_SSD_EDIT_SKIN_SX, OnSetfocusSsdEditSkinSx)
147 ON_EN_SETFOCUS(IDC_SSD_EDIT_SKIN_SY, OnSetfocusSsdEditSkinSy)
148 ON_EN_SETFOCUS(IDC_SSD_EDIT_SKIN_SZ, OnSetfocusSsdEditSkinSz)
149 ON_LBN_SELCHANGE(IDC_SSD_LIST, OnSelchangeSsdList)
150 ON_BN_CLICKED(IDC_SSD_BUTTON_UNDO, OnSsdButtonUndo)
151 ON_BN_CLICKED(IDC_SSD_BUTTON_REDO, OnSsdButtonRedo)
152 ON_BN_CLICKED(IDC_SSD_BUTTON_SAVE, OnSsdButtonSave)
153 ON_BN_CLICKED(IDC_SSD_BUTTON_SAVEAS, OnSsdButtonSaveas)
154 ON_BN_CLICKED(IDC_SSD_BUTTON_MIRROR, OnSsdButtonMirror)
155 ON_BN_CLICKED(IDC_SSD_BUTTON_SAVE_SCALE, OnSsdButtonSaveScale)
156 ON_BN_CLICKED(IDC_SSD_BUTTON_LOAD_SCALE, OnSsdButtonLoadScale)
157 ON_WM_CLOSE()
158 //}}AFX_MSG_MAP
159 END_MESSAGE_MAP()
161 /////////////////////////////////////////////////////////////////////////////
162 // CSkeletonScaleDlg message handlers
164 void CSkeletonScaleDlg::OnDestroy()
166 setRegisterWindowState (this, REGKEY_SKELETON_SCALE_DLG);
167 CDialog::OnDestroy();
172 // ***************************************************************************
173 void CSkeletonScaleDlg::setSkeletonToEdit(NL3D::CSkeletonModel *skel, const std::string &fileName)
175 uint i;
177 _SkeletonModel= skel;
178 _SkeletonFileName= fileName;
181 // **** Setup File name
182 _StaticFileName= fileName.c_str();
184 // **** Setup Bone mirror
185 _Bones.clear();
186 if(_SkeletonModel)
188 _Bones.resize(_SkeletonModel->Bones.size());
189 // copy from skel to mirror
190 applySkeletonToMirror();
193 // **** reset bone bbox here
194 _BoneBBoxes.clear();
195 _BoneBBoxes.resize(_Bones.size());
196 // delegate to drawSelection(), cause skins not still bound
197 _BoneBBoxNeedRecompute= true;
199 // **** Setup Bone List
200 _BoneList.ResetContent();
201 if(_SkeletonModel)
203 for(uint i=0;i<_SkeletonModel->Bones.size();i++)
205 const std::string tabStr = " ";
206 std::string name = _SkeletonModel->Bones[i].getBoneName();
208 // append a tab for easy hierarchy
209 uint boneId = i;
211 while((boneId=_SkeletonModel->Bones[boneId].getFatherId())!=-1)
212 name = tabStr + name;
214 // append to the list
215 _BoneList.AddString(nlUtf8ToTStr(name));
220 // **** unselect all widgets
221 for(i=0;i<SidCount;i++)
223 _ScaleSliders[i]->SetRange(0, NL_SSD_SLIDER_SIZE);
224 _ScaleSliders[i]->SetPos(NL_SSD_SLIDER_SIZE/2);
225 _ScaleEdits[i]->Empty();
226 _StaticScaleMarkers[i]->SetWindowText(_T("100%"));
228 // ensure no problem with scale and ALT-TAB
229 _SliderEdited= SidNone;
232 // **** clean undo/redo
233 _UndoQueue.clear();
234 _RedoQueue.clear();
235 refreshUndoRedoView();
238 // **** clear save button
239 _SaveDirty= false;
240 refreshSaveButton();
243 // refresh
244 UpdateData(FALSE);
248 // ***************************************************************************
249 BOOL CSkeletonScaleDlg::OnInitDialog()
251 CDialog::OnInitDialog();
253 setSkeletonToEdit(NULL, "");
255 return TRUE; // return TRUE unless you set the focus to a control
256 // EXCEPTION: OCX Property Pages should return FALSE
261 // ***************************************************************************
262 void CSkeletonScaleDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
264 CSliderCtrl *sliderCtrl= (CSliderCtrl*)pScrollBar;
265 TScaleId sliderId= getScaleIdFromSliderCtrl(sliderCtrl);
266 if(sliderId!=SidNone && nSBCode==SB_THUMBPOSITION || nSBCode==SB_THUMBTRACK)
268 // If the user press ALT-Tab while dragging an old slider, the old slider is not released.
269 // ThereFore, release the old one now
270 if(_SliderEdited!=SidNone && _SliderEdited!=sliderId)
272 onSliderReleased(_SliderEdited);
275 // if begin of slide, bkup state
276 if(_SliderEdited==SidNone)
278 _SliderEdited= sliderId;
279 // Bkup all scales (dont bother selected bones or which scale is edited...)
280 _BkupBones= _Bones;
283 //applyScale
284 applyScaleSlider(nPos);
286 else
287 CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
290 // ***************************************************************************
291 void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderBoneSx(NMHDR* pNMHDR, LRESULT* pResult)
293 onSliderReleased(SidBoneX);
294 *pResult = 0;
296 void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderBoneSy(NMHDR* pNMHDR, LRESULT* pResult)
298 onSliderReleased(SidBoneY);
299 *pResult = 0;
301 void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderBoneSz(NMHDR* pNMHDR, LRESULT* pResult)
303 onSliderReleased(SidBoneZ);
304 *pResult = 0;
306 void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderSkinSx(NMHDR* pNMHDR, LRESULT* pResult)
308 onSliderReleased(SidSkinX);
309 *pResult = 0;
311 void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderSkinSy(NMHDR* pNMHDR, LRESULT* pResult)
313 onSliderReleased(SidSkinY);
314 *pResult = 0;
316 void CSkeletonScaleDlg::OnReleasedcaptureSsdSliderSkinSz(NMHDR* pNMHDR, LRESULT* pResult)
318 onSliderReleased(SidSkinZ);
319 *pResult = 0;
322 void CSkeletonScaleDlg::onSliderReleased(TScaleId sid)
324 // Get value from slider
325 sint value= _ScaleSliders[sid]->GetPos();
327 // If the user press ALT-Tab while dragging an old slider, the old slider is not released.
328 // ThereFore, release the old one now
329 if(_SliderEdited!=SidNone && _SliderEdited!=sid)
331 onSliderReleased(_SliderEdited);
334 //applyScale
335 if(_SliderEdited==SidNone)
337 _SliderEdited= sid;
338 // Bkup all scales (dont bother selected bones or which scale is edited...)
339 _BkupBones= _Bones;
342 // apply the final value
343 applyScaleSlider(value);
345 // And reset the slider
346 _ScaleSliders[_SliderEdited]->SetPos(NL_SSD_SLIDER_SIZE/2);
347 _StaticScaleMarkers[_SliderEdited]->SetWindowText(_T("100%"));
348 _SliderEdited= SidNone;
350 // push an undo/redo only at release of slide. push the value of scale before slide
351 pushUndoState(_BkupBones, true);
355 // ***************************************************************************
356 void CSkeletonScaleDlg::applyScaleSlider(sint scrollValue)
358 // get scale beetween -1 and 1.
359 float scale= (NL_SSD_SLIDER_SIZE/2-scrollValue)/(float)(NL_SSD_SLIDER_SIZE/2);
360 NLMISC::clamp(scale, -1.f, 1.f);
361 float factor;
363 // no scale
364 if(fabs(scale)<NL_SSD_SLIDER_THRESHOLD)
365 factor=1;
366 // scale down
367 else if(scale<0)
369 float minv= 1.0f / NL_SSD_SLIDER_SCALE;
370 factor= minv*(-scale) + 1.0f*(1+scale);
372 // scale up
373 else
375 float maxv= NL_SSD_SLIDER_SCALE;
376 factor= maxv*(scale) + 1.0f*(1-scale);
379 // Apply the noise to each selected bones
380 for(uint i=0;i<_Bones.size();i++)
382 CBoneMirror &bone= _Bones[i];
383 CBoneMirror &bkup= _BkupBones[i];
384 if(bone.Selected)
386 // apply to the scaled component
387 switch(_SliderEdited)
389 case SidBoneX: bone.BoneScale.x= bkup.BoneScale.x *factor; break;
390 case SidBoneY: bone.BoneScale.y= bkup.BoneScale.y *factor; break;
391 case SidBoneZ: bone.BoneScale.z= bkup.BoneScale.z *factor; break;
392 case SidSkinX: bone.SkinScale.x= bkup.SkinScale.x *factor; break;
393 case SidSkinY: bone.SkinScale.y= bkup.SkinScale.y *factor; break;
394 case SidSkinZ: bone.SkinScale.z= bkup.SkinScale.z *factor; break;
396 // round result
397 roundClampScale(bone.BoneScale);
398 roundClampScale(bone.SkinScale);
402 // update the skeleton view
403 applyMirrorToSkeleton();
405 // refresh text views
406 refreshTextViews();
408 // update marker text
409 std::string str = NLMISC::toString("%d%%", (sint)(factor*100));
410 _StaticScaleMarkers[_SliderEdited]->SetWindowText(nlUtf8ToTStr(str));
413 // ***************************************************************************
414 void CSkeletonScaleDlg::applyMirrorToSkeleton()
416 if(_SkeletonModel)
418 nlassert(_SkeletonModel->Bones.size()==_Bones.size());
419 for(uint i=0;i<_Bones.size();i++)
421 // unmul from precision
422 _SkeletonModel->Bones[i].setScale(_Bones[i].BoneScale / NL_SSD_SCALE_PRECISION);
423 _SkeletonModel->Bones[i].setSkinScale(_Bones[i].SkinScale / NL_SSD_SCALE_PRECISION);
429 // ***************************************************************************
430 void CSkeletonScaleDlg::applySkeletonToMirror()
432 if(_SkeletonModel)
434 nlassert(_SkeletonModel->Bones.size()==_Bones.size());
435 for(uint i=0;i<_SkeletonModel->Bones.size();i++)
437 // mul by precision, and round
438 _Bones[i].SkinScale= _SkeletonModel->Bones[i].getSkinScale() * NL_SSD_SCALE_PRECISION;
439 _Bones[i].BoneScale= _SkeletonModel->Bones[i].getScale() * NL_SSD_SCALE_PRECISION;
440 roundClampScale(_Bones[i].SkinScale);
441 roundClampScale(_Bones[i].BoneScale);
447 // ***************************************************************************
448 void CSkeletonScaleDlg::refreshTextViews()
450 // 1.f for each component if multiple selection is different, else 0.f
451 NLMISC::CVector boneScaleDiff= NLMISC::CVector::Null;
452 NLMISC::CVector skinScaleDiff= NLMISC::CVector::Null;
453 // valid if scale of each bone component is same
454 NLMISC::CVector boneScaleAll= NLMISC::CVector::Null;
455 NLMISC::CVector skinScaleAll= NLMISC::CVector::Null;
456 bool someSelected= false;
458 // For all bones selected
459 for(uint i=0;i<_Bones.size();i++)
461 CBoneMirror &bone= _Bones[i];
462 if(bone.Selected)
464 if(!someSelected)
466 someSelected= true;
467 // just bkup
468 boneScaleAll= bone.BoneScale;
469 skinScaleAll= bone.SkinScale;
471 else
473 // compare each component, if different, flag
474 if(boneScaleAll.x!= bone.BoneScale.x) boneScaleDiff.x= 1.f;
475 if(boneScaleAll.y!= bone.BoneScale.y) boneScaleDiff.y= 1.f;
476 if(boneScaleAll.z!= bone.BoneScale.z) boneScaleDiff.z= 1.f;
477 if(skinScaleAll.x!= bone.SkinScale.x) skinScaleDiff.x= 1.f;
478 if(skinScaleAll.y!= bone.SkinScale.y) skinScaleDiff.y= 1.f;
479 if(skinScaleAll.z!= bone.SkinScale.z) skinScaleDiff.z= 1.f;
484 // if none selected, force empty text
485 if(!someSelected)
487 boneScaleDiff.set(1.f,1.f,1.f);
488 skinScaleDiff.set(1.f,1.f,1.f);
491 // All component refresh or only one refresh?
492 nlassert(SidCount==6);
493 refreshTextViewWithScale(SidBoneX, boneScaleAll.x, boneScaleDiff.x);
494 refreshTextViewWithScale(SidBoneY, boneScaleAll.y, boneScaleDiff.y);
495 refreshTextViewWithScale(SidBoneZ, boneScaleAll.z, boneScaleDiff.z);
496 refreshTextViewWithScale(SidSkinX, skinScaleAll.x, skinScaleDiff.x);
497 refreshTextViewWithScale(SidSkinY, skinScaleAll.y, skinScaleDiff.y);
498 refreshTextViewWithScale(SidSkinZ, skinScaleAll.z, skinScaleDiff.z);
500 // refresh
501 UpdateData(FALSE);
505 // ***************************************************************************
506 void CSkeletonScaleDlg::refreshTextViewWithScale(TScaleId sid, float scale, float diff)
508 // if different values selected, diff
509 if(diff)
511 _ScaleEdits[sid]->Empty();
513 // else display text
514 else
516 char str[256];
517 // ensure correct display with no precision problem
518 sint value= uint(floor(scale+0.5f));
519 sint whole= value*100/NL_SSD_SCALE_PRECISION;
520 sint decimal= value - whole*(NL_SSD_SCALE_PRECISION/100);
521 sprintf(str, "%d.%d", whole, decimal);
522 *_ScaleEdits[sid]= str;
527 // ***************************************************************************
528 void CSkeletonScaleDlg::roundClampScale(NLMISC::CVector &v) const
530 v.x+= 0.5f;
531 v.y+= 0.5f;
532 v.z+= 0.5f;
533 v.x= (float)floor(v.x);
534 v.y= (float)floor(v.y);
535 v.z= (float)floor(v.z);
536 // Minimum is 1 (avoid 0 scale)
537 v.maxof(v, NLMISC::CVector(1.f,1.f,1.f));
540 // ***************************************************************************
541 CSkeletonScaleDlg::TScaleId CSkeletonScaleDlg::getScaleIdFromSliderCtrl(CSliderCtrl *sliderCtrl) const
543 for(uint i=0;i<SidCount;i++)
545 if(sliderCtrl==_ScaleSliders[i])
546 return (TScaleId)i;
549 return SidNone;
553 // ***************************************************************************
554 CSkeletonScaleDlg::TScaleId CSkeletonScaleDlg::getScaleIdFromEditId(UINT ctrlId) const
556 nlctassert(SidCount==6);
557 if(ctrlId==IDC_SSD_EDIT_BONE_SX) return SidBoneX;
558 if(ctrlId==IDC_SSD_EDIT_BONE_SY) return SidBoneY;
559 if(ctrlId==IDC_SSD_EDIT_BONE_SZ) return SidBoneZ;
560 if(ctrlId==IDC_SSD_EDIT_SKIN_SX) return SidSkinX;
561 if(ctrlId==IDC_SSD_EDIT_SKIN_SY) return SidSkinY;
562 if(ctrlId==IDC_SSD_EDIT_SKIN_SZ) return SidSkinZ;
564 return SidNone;
568 // ***************************************************************************
569 void CSkeletonScaleDlg::OnChangeSsdEditBoneSx()
571 onChangeEditText(IDC_SSD_EDIT_BONE_SX);
573 void CSkeletonScaleDlg::OnChangeSsdEditBoneSy()
575 onChangeEditText(IDC_SSD_EDIT_BONE_SY);
577 void CSkeletonScaleDlg::OnChangeSsdEditBoneSz()
579 onChangeEditText(IDC_SSD_EDIT_BONE_SZ);
581 void CSkeletonScaleDlg::OnChangeSsdEditSkinSx()
583 onChangeEditText(IDC_SSD_EDIT_SKIN_SX);
585 void CSkeletonScaleDlg::OnChangeSsdEditSkinSy()
587 onChangeEditText(IDC_SSD_EDIT_SKIN_SY);
589 void CSkeletonScaleDlg::OnChangeSsdEditSkinSz()
591 onChangeEditText(IDC_SSD_EDIT_SKIN_SZ);
595 static void concatEdit2Lines(CEdit &edit)
597 const uint lineLen= 1000;
598 uint n;
599 // retrieve the 2 lines.
600 TCHAR tmp0[2*lineLen];
601 TCHAR tmp1[lineLen];
602 n= edit.GetLine(0, tmp0, lineLen); tmp0[n]= 0;
603 n= edit.GetLine(1, tmp1, lineLen); tmp1[n]= 0;
604 // concat and update the CEdit.
605 edit.SetWindowText(_tcscat(tmp0, tmp1));
609 void CSkeletonScaleDlg::onChangeEditText(UINT ctrlId)
611 CEdit *ce = (CEdit*)GetDlgItem(ctrlId);
612 if(ce)
614 UpdateData();
615 // Trick to track "Enter" keypress: CEdit are multiline. If GetLineCount()>1, then
616 // user has press enter.
617 if(ce->GetLineCount()>1)
619 // must ccat 2 lines of the CEdit.
620 concatEdit2Lines(*ce);
621 // update text members
622 UpdateData(FALSE);
623 UpdateData();
625 // update scale values from the CEdit
626 updateScalesFromText(ctrlId);
628 // then re-update CEdit from scale values
629 refreshTextViews();
635 // ***************************************************************************
636 void CSkeletonScaleDlg::OnKillfocusSsdEditBoneSx()
638 onQuitEditText(IDC_SSD_EDIT_BONE_SX);
640 void CSkeletonScaleDlg::OnKillfocusSsdEditBoneSy()
642 onQuitEditText(IDC_SSD_EDIT_BONE_SY);
644 void CSkeletonScaleDlg::OnKillfocusSsdEditBoneSz()
646 onQuitEditText(IDC_SSD_EDIT_BONE_SZ);
648 void CSkeletonScaleDlg::OnKillfocusSsdEditSkinSx()
650 onQuitEditText(IDC_SSD_EDIT_SKIN_SX);
652 void CSkeletonScaleDlg::OnKillfocusSsdEditSkinSy()
654 onQuitEditText(IDC_SSD_EDIT_SKIN_SY);
656 void CSkeletonScaleDlg::OnKillfocusSsdEditSkinSz()
658 onQuitEditText(IDC_SSD_EDIT_SKIN_SZ);
662 void CSkeletonScaleDlg::onQuitEditText(UINT ctrlId)
664 CEdit *ce = (CEdit*)GetDlgItem(ctrlId);
665 if(ce)
667 UpdateData();
669 // update scale values from the CEdit
670 updateScalesFromText(ctrlId);
672 // then re-update CEdit from scale values
673 refreshTextViews();
678 // ***************************************************************************
679 void CSkeletonScaleDlg::OnSetfocusSsdEditBoneSx()
681 onSelectEditText(IDC_SSD_EDIT_BONE_SX);
683 void CSkeletonScaleDlg::OnSetfocusSsdEditBoneSy()
685 onSelectEditText(IDC_SSD_EDIT_BONE_SY);
687 void CSkeletonScaleDlg::OnSetfocusSsdEditBoneSz()
689 onSelectEditText(IDC_SSD_EDIT_BONE_SZ);
691 void CSkeletonScaleDlg::OnSetfocusSsdEditSkinSx()
693 onSelectEditText(IDC_SSD_EDIT_SKIN_SX);
695 void CSkeletonScaleDlg::OnSetfocusSsdEditSkinSy()
697 onSelectEditText(IDC_SSD_EDIT_SKIN_SY);
699 void CSkeletonScaleDlg::OnSetfocusSsdEditSkinSz()
701 onSelectEditText(IDC_SSD_EDIT_SKIN_SZ);
704 void CSkeletonScaleDlg::onSelectEditText(UINT ctrlId)
706 CEdit *ce = (CEdit*)GetDlgItem(ctrlId);
707 if(ce)
709 ce->PostMessage(EM_SETSEL, 0, -1);
710 ce->Invalidate();
715 // ***************************************************************************
716 void CSkeletonScaleDlg::updateScalesFromText(UINT ctrlId)
718 TScaleId sid= getScaleIdFromEditId(ctrlId);
719 if(sid==SidNone)
720 return;
722 // get the scale info
723 std::string str = NLMISC::tStrToUtf8(*_ScaleEdits[sid]);
724 if(str.empty())
725 return;
726 float f;
727 sscanf(str.c_str(), "%f", &f);
728 // edit a %age
729 f*= NL_SSD_SCALE_PRECISION/100;
730 f= (float)floor(f+0.5f);
732 // bkup for undo
733 static TBoneMirrorArray precState;
734 precState= _Bones;
736 // For all bones selected, set the edited value
737 for(uint i=0;i<_Bones.size();i++)
739 CBoneMirror &bone= _Bones[i];
740 if(bone.Selected)
742 switch(sid)
744 case SidBoneX: bone.BoneScale.x= f; break;
745 case SidBoneY: bone.BoneScale.y= f; break;
746 case SidBoneZ: bone.BoneScale.z= f; break;
747 case SidSkinX: bone.SkinScale.x= f; break;
748 case SidSkinY: bone.SkinScale.y= f; break;
749 case SidSkinZ: bone.SkinScale.z= f; break;
751 // normalize
752 roundClampScale(bone.BoneScale);
753 roundClampScale(bone.SkinScale);
757 // change => bkup for undo
758 pushUndoState(precState, true);
760 // mirror changed => update skeleton
761 applyMirrorToSkeleton();
766 // ***************************************************************************
767 void CSkeletonScaleDlg::OnSelchangeSsdList()
769 UpdateData();
771 // **** Retrieve List of selected bones.
772 uint count= _BoneList.GetSelCount();
773 std::vector<int> items;
774 if(count)
776 items.resize(count);
777 _BoneList.GetSelItems(count, &items[0]);
780 // **** update the Selection array
781 // bkup for undo
782 static TBoneMirrorArray precState;
783 precState= _Bones;
785 // identify selected items in a set
786 std::set<int> selSet;
787 uint i;
788 for(i=0;i<count;i++)
789 selSet.insert(items[i]);
791 // change selection of Bones
792 for(i=0;i<_Bones.size();i++)
794 if(selSet.find(i)!=selSet.end())
795 _Bones[i].Selected= true;
796 else
797 _Bones[i].Selected= false;
800 // **** undo-redo
801 // selection change => no need to dirt save
802 pushUndoState(precState, false);
804 // **** refresh text views
805 refreshTextViews();
809 // ***************************************************************************
810 void CSkeletonScaleDlg::OnSsdButtonUndo()
812 undo();
815 void CSkeletonScaleDlg::OnSsdButtonRedo()
817 redo();
821 // ***************************************************************************
822 void CSkeletonScaleDlg::OnSsdButtonSave()
824 // if no skeleton edited, quit
825 if(!_SkeletonModel)
826 return;
828 // save the file
829 NLMISC::COFile f;
830 if( f.open(_SkeletonFileName) )
832 if(saveCurrentInStream(f))
834 // no more need to save (done)
835 _SaveDirty= false;
836 refreshSaveButton();
839 else
841 MessageBox(_T("Failed to open file for write!"));
845 void CSkeletonScaleDlg::OnSsdButtonSaveas()
847 // if no skeleton edited, quit
848 if(!_SkeletonModel)
849 return;
851 // choose the file
852 CFileDialog fd(FALSE, _T("skel"), nlUtf8ToTStr(_SkeletonFileName), OFN_OVERWRITEPROMPT, _T("SkelFiles (*.skel)|*.skel|All Files (*.*)|*.*||"), this);
853 fd.m_ofn.lpstrTitle = _T("Save As Skeleton");
854 if (fd.DoModal() == IDOK)
856 NLMISC::COFile f;
858 if (f.open(NLMISC::tStrToUtf8(fd.GetPathName())))
860 if(saveCurrentInStream(f))
862 // no more need to save (done)
863 _SaveDirty= false;
864 refreshSaveButton();
867 // bkup the valid fileName (new file edited)
868 _SkeletonFileName = NLMISC::tStrToUtf8(fd.GetPathName());
869 _StaticFileName= _SkeletonFileName.c_str();
870 UpdateData(FALSE);
872 else
874 MessageBox(_T("Failed to open file for write!"));
879 // ***************************************************************************
880 bool CSkeletonScaleDlg::saveCurrentInStream(NLMISC::IStream &f)
884 nlassert(_SkeletonModel);
885 nlassert(_SkeletonModel->Shape);
887 // Retrieve boneBase definition from the current skeleton
888 std::vector<NL3D::CBoneBase> boneBases;
889 (NLMISC::safe_cast<NL3D::CSkeletonShape*>((NL3D::IShape*)_SkeletonModel->Shape))->retrieve(boneBases);
891 // Copies bone scales from the model
892 nlassert(boneBases.size()==_SkeletonModel->Bones.size());
893 for(uint i=0;i<_SkeletonModel->Bones.size();i++)
895 NL3D::CBone &bone= _SkeletonModel->Bones[i];
896 NL3D::CBoneBase &boneBase= boneBases[i];
898 boneBase.SkinScale= bone.getSkinScale();
899 boneBase.DefaultScale= bone.getScale();
902 // build a new Skeleton shape
903 NL3D::CSkeletonShape *skelShape= new NL3D::CSkeletonShape;
904 skelShape->build(boneBases);
907 // save the vegetable
908 NL3D::CShapeStream ss;
909 ss.setShapePointer(skelShape);
910 ss.serial(f);
911 delete skelShape;
913 catch(const NLMISC::EStream &)
915 MessageBox(_T("Failed to save file!"));
916 return false;
919 return true;
923 // ***************************************************************************
924 void CSkeletonScaleDlg::pushUndoState(const TBoneMirrorArray &precState, bool dirtSave)
926 // **** test if real change
927 nlassert(precState.size()==_Bones.size());
928 bool change= false;
929 for(uint i=0;i<_Bones.size();i++)
931 if( _Bones[i].BoneScale!=precState[i].BoneScale ||
932 _Bones[i].SkinScale!=precState[i].SkinScale ||
933 _Bones[i].Selected!=precState[i].Selected)
935 change= true;
936 break;
939 // no change? no op
940 if(!change)
941 return;
944 // **** then bkup for undo
945 // change => the redo list is lost
946 _RedoQueue.clear();
948 // if not enough space, the last undo is lost
949 if(_UndoQueue.size()==MaxUndoRedo)
950 _UndoQueue.pop_front();
952 // add the precedent state to the undo queue
953 _UndoQueue.push_back(precState);
955 // refresh buttons
956 refreshUndoRedoView();
958 // refresh save button
959 if(dirtSave && !_SaveDirty)
961 _SaveDirty= true;
962 refreshSaveButton();
966 // ***************************************************************************
967 void CSkeletonScaleDlg::undo()
969 nlassert(_UndoQueue.size()+_RedoQueue.size()<=MaxUndoRedo);
971 // is some undoable
972 if(_UndoQueue.size())
974 // current goes into the redo queue
975 _RedoQueue.push_front(_Bones);
976 // restore
977 _Bones= _UndoQueue.back();
978 // pop
979 _UndoQueue.pop_back();
981 // refresh display
982 applyMirrorToSkeleton();
983 refreshTextViews();
984 applySelectionToView();
986 // refresh buttons
987 refreshUndoRedoView();
989 // change => must save
990 _SaveDirty= true;
991 refreshSaveButton();
995 // ***************************************************************************
996 void CSkeletonScaleDlg::redo()
998 nlassert(_UndoQueue.size()+_RedoQueue.size()<=MaxUndoRedo);
1000 // is some redoable
1001 if(_RedoQueue.size())
1003 // current goes into the undo queue
1004 _UndoQueue.push_back(_Bones);
1005 // restore
1006 _Bones= _RedoQueue.front();
1007 // pop
1008 _RedoQueue.pop_front();
1010 // refresh display
1011 applyMirrorToSkeleton();
1012 refreshTextViews();
1013 applySelectionToView();
1015 // refresh buttons
1016 refreshUndoRedoView();
1018 // change => must save
1019 _SaveDirty= true;
1020 refreshSaveButton();
1025 // ***************************************************************************
1026 void CSkeletonScaleDlg::applySelectionToView()
1028 // update list box selection according to state
1029 nlassert(_Bones.size()==(uint)_BoneList.GetCount());
1030 for(uint i=0;i<_Bones.size();i++)
1032 _BoneList.SetSel(i, _Bones[i].Selected);
1034 UpdateData(FALSE);
1037 // ***************************************************************************
1038 void CSkeletonScaleDlg::refreshUndoRedoView()
1040 CWnd *wnd;
1041 wnd= GetDlgItem(IDC_SSD_BUTTON_UNDO);
1042 if(wnd)
1043 wnd->EnableWindow(!_UndoQueue.empty());
1044 wnd= GetDlgItem(IDC_SSD_BUTTON_REDO);
1045 if(wnd)
1046 wnd->EnableWindow(!_RedoQueue.empty());
1050 // ***************************************************************************
1051 void CSkeletonScaleDlg::refreshSaveButton()
1053 // SaveAs is always available
1054 CWnd *wnd= GetDlgItem(IDC_SSD_BUTTON_SAVE);
1055 if(wnd)
1056 wnd->EnableWindow(_SaveDirty);
1060 // ***************************************************************************
1061 sint CSkeletonScaleDlg::getBoneForMirror(uint boneId, std::string &mirrorName)
1063 sint side= 0;
1064 std::string::size_type pos;
1065 nlassert(_SkeletonModel && boneId<_SkeletonModel->Bones.size());
1066 mirrorName= _SkeletonModel->Bones[boneId].getBoneName();
1068 if((pos= mirrorName.find(" R "))!=std::string::npos)
1070 side= 1;
1071 mirrorName[pos+1]= 'L';
1073 else if((pos= mirrorName.find(" L "))!=std::string::npos)
1075 side= -1;
1076 mirrorName[pos+1]= 'R';
1079 return side;
1083 // ***************************************************************************
1084 void CSkeletonScaleDlg::OnSsdButtonMirror()
1086 // bkup for undo
1087 static TBoneMirrorArray precState;
1088 precState= _Bones;
1089 nlassert(_SkeletonModel);
1091 // for each bone selected
1092 bool change= false;
1093 for(uint i=0;i<_Bones.size();i++)
1095 CBoneMirror &bone= _Bones[i];
1096 if(bone.Selected)
1098 // get the bone side and mirrored name
1099 std::string mirrorName;
1100 sint side= getBoneForMirror(i, mirrorName);
1101 // if not a "centered" bone
1102 if(side!=0)
1104 // get the bone with mirrored name
1105 sint mirrorId= _SkeletonModel->getBoneIdByName(mirrorName);
1106 if(mirrorId<0)
1108 nlinfo("MirrorScale: Didn't found %s", mirrorName.c_str());
1110 else
1112 // copy setup from the dest bone
1113 nlassert(mirrorId<(sint)_Bones.size());
1114 _Bones[mirrorId].BoneScale= bone.BoneScale;
1115 _Bones[mirrorId].SkinScale= bone.SkinScale;
1121 // refresh display
1122 applyMirrorToSkeleton();
1123 refreshTextViews();
1125 // if some change, bkup for undo/redo
1126 pushUndoState(precState, true);
1131 // ***************************************************************************
1132 void CSkeletonScaleDlg::drawSelection()
1134 if(!_SkeletonModel)
1135 return;
1137 nlassert(_SkeletonModel->Bones.size()==_Bones.size());
1139 // **** Need recompute Bones bbox?
1140 if(_BoneBBoxNeedRecompute)
1142 _BoneBBoxNeedRecompute= false;
1144 // for all bones
1145 for(uint i=0;i<_SkeletonModel->Bones.size();i++)
1147 NLMISC::CAABBox boneBBox;
1148 bool empty= true;
1150 // for all instances skinned
1151 const std::set<NL3D::CTransform*> &skins= _SkeletonModel->getSkins();
1152 std::set<NL3D::CTransform*>::const_iterator it;
1153 for(it=skins.begin();it!=skins.end();it++)
1155 NL3D::CTransform *skin= *it;
1156 NLMISC::CAABBox skinBoneBBox;
1157 // if the skin is skinned to this bone
1158 if(skin->getSkinBoneBBox(skinBoneBBox, i))
1160 // set or enlarge the bone bbox
1161 if(empty)
1163 empty= false;
1164 boneBBox= skinBoneBBox;
1166 else
1168 boneBBox= NLMISC::CAABBox::computeAABBoxUnion(boneBBox, skinBoneBBox);
1173 // if there is no skin influence on this bone, still display a tiny bbox, to see the scale
1174 const float defSize= 0.05f;
1175 if(empty)
1177 boneBBox.setCenter(NLMISC::CVector::Null);
1178 boneBBox.setSize(NLMISC::CVector(defSize, defSize, defSize));
1181 // assign
1182 _BoneBBoxes[i]= boneBBox;
1187 // **** Draw each selected bone
1188 for(uint i=0;i<_SkeletonModel->Bones.size();i++)
1190 // if bone not selected, skip
1191 if(!_Bones[i].Selected)
1192 continue;
1194 // get the bone "Skin Matrix"
1195 NL3D::CBone &bone= _SkeletonModel->Bones[i];
1196 // force compute of this bone
1197 if(!_SkeletonModel->isBoneComputed(i))
1198 _SkeletonModel->forceComputeBone(i);
1199 NLMISC::CMatrix worldSkinMat= bone.getWorldMatrix();
1200 // scale with skin scale, because the localskeleton and world matrix do not have this scale
1201 worldSkinMat.scale(bone.getSkinScale());
1203 // Transform the local bbox in its associated matrix
1204 NLMISC::CMatrix matBBox;
1205 NLMISC::CAABBox bbox= _BoneBBoxes[i];
1206 matBBox.setPos(bbox.getCenter());
1207 matBBox.setRot(
1208 NLMISC::CVector::I * bbox.getSize().x,
1209 NLMISC::CVector::J * bbox.getSize().y,
1210 NLMISC::CVector::K * bbox.getSize().z);
1211 NLMISC::CMatrix finalMat;
1212 finalMat.setMulMatrixNoProj(worldSkinMat, matBBox);
1214 //Draw a wired bbox with this bone
1215 NLMISC::CVector corner= finalMat.getPos() - finalMat.getI()/2 - finalMat.getJ()/2 - finalMat.getK()/2;
1216 NL3D::IDriver *driver= NL3D::CNELU::Driver;
1217 driver->setupModelMatrix(NLMISC::CMatrix::Identity);
1218 NL3D::CDRU::drawWiredBox(corner, finalMat.getI(), finalMat.getJ(), finalMat.getK(), NLMISC::CRGBA::White, *driver);
1223 // ***************************************************************************
1224 void CSkeletonScaleDlg::OnSsdButtonSaveScale()
1226 // if no skeleton edited, quit
1227 if(!_SkeletonModel)
1228 return;
1230 // choose the file
1231 std::string defaultFileName = _SkeletonFileName;
1232 NLMISC::strFindReplace(defaultFileName, ".skel", ".scale");
1234 CFileDialog fd(FALSE, _T("scale"), nlUtf8ToTStr(defaultFileName), OFN_OVERWRITEPROMPT, _T("SkelScaleFiles (*.scale)|*.scale|All Files (*.*)|*.*||"), this);
1235 fd.m_ofn.lpstrTitle = _T("Save As Skeleton Scale File");
1237 if (fd.DoModal() == IDOK)
1239 NLMISC::COFile f;
1240 if (f.open(NLMISC::tStrToUtf8(fd.GetPathName())))
1242 saveSkelScaleInStream(f);
1244 else
1246 MessageBox(_T("Failed to open file for write!"));
1251 // ***************************************************************************
1252 void CSkeletonScaleDlg::OnSsdButtonLoadScale()
1254 // if no skeleton edited, quit
1255 if(!_SkeletonModel)
1256 return;
1258 // choose the file
1259 std::string defaultFileName= _SkeletonFileName;
1260 NLMISC::strFindReplace(defaultFileName, ".skel", ".scale");
1262 CFileDialog fd(TRUE, _T("scale"), nlUtf8ToTStr(defaultFileName), 0, _T("SkelScaleFiles (*.scale)|*.scale|All Files (*.*)|*.*||"), this);
1263 fd.m_ofn.lpstrTitle= _T("Load a Skeleton Scale File");
1265 if (fd.DoModal() == IDOK)
1267 NLMISC::CIFile f;
1268 if (f.open(NLMISC::tStrToUtf8(fd.GetPathName())))
1270 loadSkelScaleFromStream(f);
1272 else
1274 MessageBox(_T("Failed to open file for read!"));
1280 // ***************************************************************************
1281 struct CBoneScaleInfo
1283 std::string Name;
1284 NLMISC::CVector Scale;
1285 NLMISC::CVector SkinScale;
1287 void serial(NLMISC::IStream &f)
1289 sint32 ver= f.serialVersion(0);
1290 f.serial(Name, Scale, SkinScale);
1294 // ***************************************************************************
1295 bool CSkeletonScaleDlg::saveSkelScaleInStream(NLMISC::IStream &f)
1299 nlassert(_SkeletonModel);
1301 // Copies bone scales from the model
1302 std::vector<CBoneScaleInfo> boneScales;
1303 boneScales.resize(_SkeletonModel->Bones.size());
1304 for(uint i=0;i<boneScales.size();i++)
1306 NL3D::CBone &bone= _SkeletonModel->Bones[i];
1307 CBoneScaleInfo &boneScale= boneScales[i];
1309 // get scale info from current edited skeleton
1310 boneScale.Name= bone.getBoneName();
1311 boneScale.Scale= bone.getScale();
1312 boneScale.SkinScale= bone.getSkinScale();
1315 // save the file
1316 sint32 ver= f.serialVersion(0);
1317 f.serialCont(boneScales);
1319 catch(const NLMISC::EStream &)
1321 MessageBox(_T("Failed to save file!"));
1322 return false;
1325 return true;
1328 // ***************************************************************************
1329 bool CSkeletonScaleDlg::loadSkelScaleFromStream(NLMISC::IStream &f)
1333 nlassert(_SkeletonModel);
1335 // load the file
1336 sint32 ver= f.serialVersion(0);
1337 std::vector<CBoneScaleInfo> boneScales;
1338 f.serialCont(boneScales);
1340 // apply to the current skeleton
1341 for(uint i=0;i<boneScales.size();i++)
1343 sint32 boneId= _SkeletonModel->getBoneIdByName(boneScales[i].Name);
1344 if(boneId>=0 && boneId<(sint32)_SkeletonModel->Bones.size())
1346 CBoneScaleInfo &boneScale= boneScales[i];
1347 _SkeletonModel->Bones[boneId].setScale(boneScale.Scale);
1348 _SkeletonModel->Bones[boneId].setSkinScale(boneScale.SkinScale);
1352 // Bkup _Bones, for undo
1353 static TBoneMirrorArray precState;
1354 precState= _Bones;
1356 // Then reapply to the mirror
1357 applySkeletonToMirror();
1359 // change => must save
1360 pushUndoState(precState, true);
1362 // and update display
1363 refreshTextViews();
1365 catch(const NLMISC::EStream &)
1367 MessageBox(_T("Failed to save file!"));
1368 return false;
1371 return true;
1375 void CSkeletonScaleDlg::OnClose()
1377 _ObjViewer->getMainFrame()->OnWindowSkeletonScale();