Loading [MathJax]/extensions/TeX/AMSmath.js
Radium Engine  1.5.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
SkeletonBasedAnimationUI.cpp
1 #include "ui_SkeletonBasedAnimationUI.h"
2 #include <Gui/SkeletonBasedAnimation/SkeletonBasedAnimationUI.hpp>
3 
4 #include <QFileDialog>
5 #include <QSettings>
6 
7 #include <fstream>
8 #include <iostream>
9 
10 #include <Core/Animation/KeyFramedValueController.hpp>
11 #include <Core/Animation/KeyFramedValueInterpolators.hpp>
12 #include <Core/CoreMacros.hpp>
13 #include <Core/Utils/StringUtils.hpp>
14 
15 #include <Engine/RadiumEngine.hpp>
16 #include <Engine/Scene/Entity.hpp>
17 #include <Engine/Scene/SkeletonBasedAnimationSystem.hpp>
18 #include <Engine/Scene/SkeletonComponent.hpp>
19 #include <Engine/Scene/SkinningComponent.hpp>
20 
21 #include <Gui/Timeline/Timeline.hpp>
22 
23 using namespace Ra::Core::Animation;
24 
25 namespace Ra::Gui {
26 
27 SkeletonBasedAnimationUI::SkeletonBasedAnimationUI(
28  Engine::Scene::SkeletonBasedAnimationSystem* system,
29  Timeline* timeline,
30  QWidget* parent ) :
31  QFrame( parent ),
32  ui( new Ui::SkeletonBasedAnimationUI ),
33  m_system( system ),
34  m_timeline( timeline ) {
35  ui->setupUi( this );
36  connect(
37  ui->actionXray, &QAction::toggled, this, &SkeletonBasedAnimationUI::on_m_xray_clicked );
38  connect( ui->actionXray,
39  &QAction::toggled,
40  this,
41  &SkeletonBasedAnimationUI::on_m_showSkeleton_toggled );
42 }
43 
44 SkeletonBasedAnimationUI::~SkeletonBasedAnimationUI() {
45  delete ui;
46 }
47 
48 void SkeletonBasedAnimationUI::selectionChanged( const Engine::Scene::ItemEntry& entry ) {
49  m_selection = entry;
50  m_currentSkeleton = nullptr;
51  m_currentSkinnings.clear();
52  ui->actionLBS->setEnabled( false );
53  ui->actionDQS->setEnabled( false );
54  ui->actionCoR->setEnabled( false );
55  ui->tabWidget->setEnabled( false );
56  ui->m_skinning->setEnabled( false );
57  if ( m_selection.m_entity == nullptr ) { return; }
58  for ( auto& comp : m_selection.m_entity->getComponents() ) {
59  if ( auto curSkel = dynamic_cast<Engine::Scene::SkeletonComponent*>( comp.get() ) ) {
60  // register current Skeleton component
61  m_currentSkeleton = curSkel;
62  // update the ui accordingly
63  ui->tabWidget->setEnabled( true );
64  ui->actionXray->setChecked( m_currentSkeleton->isXray() );
65  ui->m_speed->setValue( double( m_currentSkeleton->getSpeed() ) );
66  ui->m_autoRepeat->setChecked( m_currentSkeleton->isAutoRepeat() );
67  ui->m_pingPong->setChecked( m_currentSkeleton->isPingPong() );
68  ui->m_currentAnimation->blockSignals( true );
69  ui->m_currentAnimation->clear();
70  for ( size_t i = 0; i < m_currentSkeleton->getAnimationCount(); ++i ) {
71  ui->m_currentAnimation->addItem( "#" + QString::number( i ) );
72  }
73  ui->m_currentAnimation->blockSignals( false );
74  ui->m_currentAnimation->setCurrentIndex( int( m_currentSkeleton->getAnimationId() ) );
75  ui->m_xray->setChecked( m_currentSkeleton->isXray() );
76  ui->m_showSkeleton->setChecked( m_currentSkeleton->isShowingSkeleton() );
77  ui->m_manipulation->setCurrentIndex(
78  int( m_currentSkeleton->getManipulationScheme() ) );
79  }
80  if ( auto skinComp = dynamic_cast<Engine::Scene::SkinningComponent*>( comp.get() ) ) {
81  // register the current skinning component
82  m_currentSkinnings.emplace_back( skinComp );
83  // update the ui accordingly
84  ui->m_skinning->setEnabled( true );
85  ui->actionLBS->setEnabled( true );
86  ui->actionDQS->setEnabled( true );
87  ui->actionCoR->setEnabled( true );
88  ui->m_skinningMethod->setEnabled( true );
89  ui->m_skinningMethod->setCurrentIndex( int( skinComp->getSkinningType() ) );
90  on_m_skinningMethod_currentIndexChanged( int( skinComp->getSkinningType() ) );
91  ui->m_showWeights->setEnabled( true );
92  ui->m_showWeights->setChecked( skinComp->isShowingWeights() );
93  ui->m_weightsType->setEnabled( true );
94  ui->m_weightsType->setCurrentIndex( int( skinComp->getWeightsType() ) );
95  ui->m_normalSkinning->setEnabled( true );
96  ui->m_normalSkinning->setCurrentIndex( int( skinComp->getNormalSkinning() ) );
97  }
98  }
99 
100  // deal with bone selection for weights display
101  if ( m_currentSkeleton != nullptr ) {
102  auto BM = *m_currentSkeleton->getBoneRO2idx();
103  auto b_it = BM.find( m_selection.m_roIndex );
104  if ( b_it != BM.end() ) {
105  for ( auto skin : m_currentSkinnings ) {
106  skin->setWeightBone( b_it->second );
107  }
108  }
109  }
110 
111  askForUpdate();
112 }
113 
114 int SkeletonBasedAnimationUI::getActionNb() {
115  return 6;
116 }
117 
118 QAction* SkeletonBasedAnimationUI::getAction( int i ) {
119  switch ( i ) {
120  case 0:
121  return ui->actionXray;
122  case 1:
123  return ui->actionLBS;
124  case 2:
125  return ui->actionDQS;
126  case 3:
127  return ui->actionCoR;
128  default:
129  return nullptr;
130  }
131 }
132 
133 void SkeletonBasedAnimationUI::postLoadFile( Engine::Scene::Entity* entity ) {
134  // reset current data and ui
135  m_selection = Engine::Scene::ItemEntry();
136  m_currentSkeleton = nullptr;
137  m_currentSkinnings.clear();
138  ui->tabWidget->setEnabled( false );
139  ui->m_skinning->setEnabled( false );
140  if ( !m_timeline ) { return; }
141  // register the animation keyframes of the first animation into the timeline
142  auto c = std::find_if(
143  entity->getComponents().begin(), entity->getComponents().end(), []( const auto& cmpt ) {
144  return ( dynamic_cast<Ra::Engine::Scene::SkeletonComponent*>( cmpt.get() ) != nullptr );
145  } );
146  if ( c != entity->getComponents().end() ) {
147  auto skel = static_cast<Ra::Engine::Scene::SkeletonComponent*>( ( *c ).get() );
148  auto& anim = skel->getAnimation( skel->getAnimationId() );
149  const auto& boneMap = skel->getBoneRO2idx();
150  for ( size_t j = 0; j < anim.size(); ++j ) {
151  auto it = std::find_if(
152  boneMap->begin(), boneMap->end(), [j]( const auto& b ) { return b.second == j; } );
153  if ( it == boneMap->end() ) { continue; } // end bone
154 
155  m_timeline->registerKeyFramedValue(
156  it->first,
158  &anim[j],
159  "Animation_" + skel->getSkeleton()->getLabel( uint( j ) ),
160  [&anim, j, skel]( const Scalar& t ) {
161  anim[j].insertKeyFrame(
162  t, skel->getSkeleton()->getPose( HandleArray::SpaceType::LOCAL )[j] );
163  } ) ); // no update callback here
164  }
165  }
166  // update the timeline display interval to the bounding interval of all anims
167  Scalar startTime = std::numeric_limits<Scalar>::max();
168  Scalar endTime = 0;
169  for ( const auto& animComp : entity->getComponents() ) {
170  if ( auto skel = dynamic_cast<Ra::Engine::Scene::SkeletonComponent*>( animComp.get() ) ) {
171  auto [s, e] = skel->getAnimationTimeInterval();
172  startTime = std::min( startTime, s );
173  endTime = std::max( endTime, e );
174  }
175  }
176  m_timeline->onChangeStart( startTime );
177  m_timeline->onChangeEnd( endTime );
178 }
179 
180 void SkeletonBasedAnimationUI::on_actionXray_triggered( bool checked ) {
181  ui->m_xray->setChecked( checked );
182  on_m_xray_clicked( checked );
183 }
184 
185 void SkeletonBasedAnimationUI::on_m_speed_valueChanged( double arg1 ) {
186  CORE_ASSERT( m_currentSkeleton, "Null SkeletonComponent." );
187  m_currentSkeleton->setSpeed( Scalar( arg1 ) );
188  askForUpdate();
189 }
190 
191 void SkeletonBasedAnimationUI::on_m_pingPong_toggled( bool checked ) {
192  CORE_ASSERT( m_currentSkeleton, "Null SkeletonComponent." );
193  m_currentSkeleton->pingPong( checked );
194  askForUpdate();
195 }
196 
197 void SkeletonBasedAnimationUI::on_m_autoRepeat_toggled( bool checked ) {
198  CORE_ASSERT( m_currentSkeleton, "Null SkeletonComponent." );
199  m_currentSkeleton->autoRepeat( checked );
200  askForUpdate();
201 }
202 
203 void SkeletonBasedAnimationUI::on_m_currentAnimation_currentIndexChanged( int index ) {
204  if ( !m_timeline ) { return; }
205 
206  m_currentSkeleton->useAnimation( uint( index ) );
207  auto& anim = m_currentSkeleton->getAnimation( uint( index ) );
208  const auto& boneMap = m_currentSkeleton->getBoneRO2idx();
209  auto skel = m_currentSkeleton->getSkeleton();
210  for ( size_t j = 0; j < anim.size(); ++j ) {
211  auto it = std::find_if(
212  boneMap->begin(), boneMap->end(), [j]( const auto& b ) { return b.second == j; } );
213  if ( it == boneMap->end() ) { continue; } // end bone
214  m_timeline->unregisterKeyFramedValue( it->first,
215  "Animation_" + skel->getLabel( uint( j ) ) );
216  m_timeline->registerKeyFramedValue(
217  it->first,
219  &anim[j],
220  "Animation_" + skel->getLabel( uint( j ) ),
221  [&anim, j, skel]( const Scalar& t ) {
222  anim[j].insertKeyFrame( t, skel->getPose( HandleArray::SpaceType::LOCAL )[j] );
223  } ) ); // no update callback here
224  }
225  // ask the animation system to update w.r.t. the animation
226  m_system->enforceUpdate();
227  // update the animation keyframes display in the timeline
228  m_timeline->selectionChanged( m_selection );
229 
230  askForUpdate();
231 }
232 
233 void SkeletonBasedAnimationUI::on_m_newAnim_clicked() {
234  CORE_ASSERT( m_currentSkeleton, "Null SkeletonComponent." );
235  const int num = ui->m_currentAnimation->count();
236  m_currentSkeleton->addNewAnimation();
237  ui->m_currentAnimation->addItem( "#" + QString::number( num ) );
238  ui->m_currentAnimation->setCurrentIndex( num );
239  askForUpdate();
240 }
241 
242 void SkeletonBasedAnimationUI::on_m_removeAnim_clicked() {
243  if ( ui->m_currentAnimation->count() == 1 ) { return; }
244 
245  CORE_ASSERT( m_currentSkeleton, "Null SkeletonComponent." );
246  const int removeIndex = ui->m_currentAnimation->currentIndex();
247  m_currentSkeleton->removeAnimation( size_t( removeIndex ) );
248  ui->m_currentAnimation->removeItem( removeIndex );
249  ui->m_currentAnimation->setCurrentIndex( int( m_currentSkeleton->getAnimationId() ) );
250  askForUpdate();
251 }
252 
253 void SkeletonBasedAnimationUI::on_m_loadAnim_clicked() {
254  QSettings settings;
255  QString path = settings.value( "Animation::path", QDir::homePath() ).toString();
256  path = QFileDialog::getOpenFileName( nullptr, "Load Animation", path, "*.rdma" );
257  if ( path.size() == 0 ) { return; }
258  settings.setValue( "Animation::path", path );
259  ui->m_animFile->setText( path.split( '/' ).last() );
260 
261  // load the animation into the skeleton component
262  auto& anim = m_currentSkeleton->addNewAnimation();
263  std::ifstream file( path.toStdString(), std::ios::binary );
264  size_t n;
265  Scalar t;
266  Ra::Core::Transform T;
267  for ( auto& bAnim : anim ) {
268  file.read( reinterpret_cast<char*>( &n ), sizeof( n ) );
269  for ( size_t i = 0; i < n; ++i ) {
270  file.read( reinterpret_cast<char*>( &t ), sizeof( t ) );
271  file.read( reinterpret_cast<char*>( &T ), sizeof( T ) );
272  bAnim.insertKeyFrame( t, T );
273  }
274  }
275 
276  // update the ui and set the loaded animation as the one used
277  const int num = ui->m_currentAnimation->count();
278  ui->m_currentAnimation->addItem( "#" + QString::number( num ) );
279  ui->m_currentAnimation->setCurrentIndex( num );
280 
281  // request update
282  askForUpdate();
283 }
284 
285 void SkeletonBasedAnimationUI::on_m_saveAnim_clicked() {
286  QSettings settings;
287  QString path = settings.value( "Animation::path", QDir::homePath() ).toString();
288  path = QFileDialog::getSaveFileName( nullptr, "Load Animation", path, "*.rdma" );
289  if ( path.size() == 0 ) { return; }
290  settings.setValue( "Animation::path", path );
291  ui->m_animFile->setText( path.split( '/' ).last() );
292 
293  const auto& anim = m_currentSkeleton->getAnimation( m_currentSkeleton->getAnimationId() );
294  std::ofstream file( path.toStdString(), std::ios::trunc | std::ios::binary );
295  // Todo: deal with anim timestep when used
296  size_t n;
297  for ( const auto& bAnim : anim ) {
298  n = bAnim.size();
299  file.write( reinterpret_cast<const char*>( &n ), sizeof( n ) );
300  for ( const auto t : bAnim.getTimes() ) {
301  file.write( reinterpret_cast<const char*>( &t ), sizeof( t ) );
302  auto kf = bAnim.at( t, linearInterpolate<Ra::Core::Transform> );
303  file.write( reinterpret_cast<const char*>( &kf ), sizeof( kf ) );
304  }
305  }
306 }
307 
308 void SkeletonBasedAnimationUI::on_m_xray_clicked( bool checked ) {
309  m_system->setXray( checked );
310  askForUpdate();
311 }
312 
313 void SkeletonBasedAnimationUI::on_m_showSkeleton_toggled( bool checked ) {
314  m_system->toggleSkeleton( checked );
315  askForUpdate();
316 }
317 
318 void SkeletonBasedAnimationUI::on_m_manipulation_currentIndexChanged( int index ) {
319  m_currentSkeleton->setManipulationScheme( Skeleton::Manipulation( index ) );
320  askForUpdate();
321 }
322 
323 void SkeletonBasedAnimationUI::on_m_skinningMethod_currentIndexChanged( int newType ) {
324  CORE_ASSERT( newType >= 0 && newType < 5, "Invalid Skinning Type" );
325  using SkinningType = Engine::Scene::SkinningComponent::SkinningType;
326  auto type = SkinningType( newType );
327  for ( auto skin : m_currentSkinnings ) {
328  skin->setSkinningType( type );
329  }
330  switch ( type ) {
331  case SkinningType::LBS: {
332  on_actionLBS_triggered();
333  break;
334  }
335  case SkinningType::DQS: {
336  on_actionDQS_triggered();
337  break;
338  }
339  case SkinningType::COR: {
340  on_actionCoR_triggered();
341  break;
342  }
343  default: {
344  break;
345  }
346  }
347 }
348 
349 void SkeletonBasedAnimationUI::on_m_showWeights_toggled( bool checked ) {
350  for ( auto skin : m_currentSkinnings ) {
351  skin->showWeights( checked );
352  }
353  askForUpdate();
354 }
355 
356 void SkeletonBasedAnimationUI::on_m_weightsType_currentIndexChanged( int newType ) {
357  for ( auto skin : m_currentSkinnings ) {
358  skin->showWeightsType( Engine::Scene::SkinningComponent::WeightType( newType ) );
359  }
360  askForUpdate();
361 }
362 
363 void SkeletonBasedAnimationUI::on_m_normalSkinning_currentIndexChanged( int newType ) {
364  for ( auto skin : m_currentSkinnings ) {
365  skin->setNormalSkinning( Engine::Scene::SkinningComponent::NormalSkinning( newType ) );
366  }
367  askForUpdate();
368 }
369 
370 void SkeletonBasedAnimationUI::on_actionLBS_triggered() {
372  ui->m_skinningMethod->setCurrentIndex( int( SkinningType::LBS ) );
373  ui->actionLBS->setChecked( true );
374  ui->actionDQS->setChecked( false );
375  ui->actionCoR->setChecked( false );
376  askForUpdate();
377 }
378 
379 void SkeletonBasedAnimationUI::on_actionDQS_triggered() {
381  ui->m_skinningMethod->setCurrentIndex( int( SkinningType::DQS ) );
382  ui->actionLBS->setChecked( false );
383  ui->actionDQS->setChecked( true );
384  ui->actionCoR->setChecked( false );
385  askForUpdate();
386 }
387 
388 void SkeletonBasedAnimationUI::on_actionCoR_triggered() {
390  ui->m_skinningMethod->setCurrentIndex( int( SkinningType::COR ) );
391  ui->actionLBS->setChecked( false );
392  ui->actionDQS->setChecked( false );
393  ui->actionCoR->setChecked( true );
394  askForUpdate();
395 }
396 
397 } // namespace Ra::Gui
const Animation & getAnimation(const size_t i) const
Return the i -th animation.
SkinningType
The Geometric Skinning Method.