Radium Engine  1.5.20
Loading...
Searching...
No Matches
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
23using namespace Ra::Core::Animation;
24
25namespace Ra::Gui {
26
27SkeletonBasedAnimationUI::SkeletonBasedAnimationUI(
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
44SkeletonBasedAnimationUI::~SkeletonBasedAnimationUI() {
45 delete ui;
46}
47
48void 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
114int SkeletonBasedAnimationUI::getActionNb() {
115 return 6;
116}
117
118QAction* 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
133void 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
180void SkeletonBasedAnimationUI::on_actionXray_triggered( bool checked ) {
181 ui->m_xray->setChecked( checked );
182 on_m_xray_clicked( checked );
183}
184
185void SkeletonBasedAnimationUI::on_m_speed_valueChanged( double arg1 ) {
186 CORE_ASSERT( m_currentSkeleton, "Null SkeletonComponent." );
187 m_currentSkeleton->setSpeed( Scalar( arg1 ) );
188 askForUpdate();
189}
190
191void SkeletonBasedAnimationUI::on_m_pingPong_toggled( bool checked ) {
192 CORE_ASSERT( m_currentSkeleton, "Null SkeletonComponent." );
193 m_currentSkeleton->pingPong( checked );
194 askForUpdate();
195}
196
197void SkeletonBasedAnimationUI::on_m_autoRepeat_toggled( bool checked ) {
198 CORE_ASSERT( m_currentSkeleton, "Null SkeletonComponent." );
199 m_currentSkeleton->autoRepeat( checked );
200 askForUpdate();
201}
202
203void 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
233void 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
242void 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
253void 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
285void 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
308void SkeletonBasedAnimationUI::on_m_xray_clicked( bool checked ) {
309 m_system->setXray( checked );
310 askForUpdate();
311}
312
313void SkeletonBasedAnimationUI::on_m_showSkeleton_toggled( bool checked ) {
314 m_system->toggleSkeleton( checked );
315 askForUpdate();
316}
317
318void SkeletonBasedAnimationUI::on_m_manipulation_currentIndexChanged( int index ) {
319 m_currentSkeleton->setManipulationScheme( Skeleton::Manipulation( index ) );
320 askForUpdate();
321}
322
323void SkeletonBasedAnimationUI::on_m_skinningMethod_currentIndexChanged( int newType ) {
324 CORE_ASSERT( newType >= 0 && newType < 5, "Invalid Skinning Type" );
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
349void SkeletonBasedAnimationUI::on_m_showWeights_toggled( bool checked ) {
350 for ( auto skin : m_currentSkinnings ) {
351 skin->showWeights( checked );
352 }
353 askForUpdate();
354}
355
356void 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
363void 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
370void 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
379void 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
388void 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
An entity is an scene element. It ties together components with a transform.
Definition Entity.hpp:23
const Animation & getAnimation(const size_t i) const
Return the i -th animation.
The SkinningComponent class is responsible for applying Geometric Skinning Methods on an animated obj...
SkinningType
The Geometric Skinning Method.
NormalSkinning
How to skin the normal, tangent and binormal vectors.
The Timeline class provides display and management of time, as well as keyframes.
Definition Timeline.hpp:38
T find_if(T... args)
T max(T... args)
T min(T... args)
T system(T... args)