Loading [MathJax]/extensions/TeX/AMSmath.js
Radium Engine  1.5.28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
animation.cpp
1#include <Core/Animation/HandleWeightOperation.hpp>
3#include <Core/Animation/KeyFramedValue.hpp>
4#include <Core/Animation/KeyFramedValueInterpolators.hpp>
7#include <Core/Animation/HandleWeight.hpp>
8#include <Core/Animation/KeyFramedValueController.hpp>
9#include <Core/Animation/Pose.hpp>
10#include <Core/Containers/AdjacencyList.hpp>
11#include <Core/Containers/AlignedStdVector.hpp>
12#include <Core/Containers/VectorArray.hpp>
13#include <Core/CoreMacros.hpp>
14#include <Core/Math/Math.hpp>
15#include <Core/Types.hpp>
17
19#include <Core/Animation/DualQuaternionSkinning.hpp>
21
22#include <Core/Animation/PoseOperation.hpp>
23#include <Core/Animation/Skeleton.hpp>
24#include <Eigen/Core>
25#include <Eigen/Geometry>
26#include <Eigen/SparseCore>
27#include <algorithm>
28#include <catch2/catch_test_macros.hpp>
29#include <cmath>
30#include <iostream>
31#include <vector>
32
33using namespace Ra::Core;
34using namespace Ra::Core::Animation;
35
36TEST_CASE( "Core/Animation/HandleWeightOperation",
37 "[unittests][Core][Core/Animation][HandleWeightOperation]" ) {
38 static const constexpr int w = 50;
39 static const constexpr int h = w;
40
41 WeightMatrix matrix1 { w, h };
42 matrix1.setIdentity();
43
44 SECTION( "Test normalization" ) {
45 // Matrix1 is already normalized
46 REQUIRE( !normalizeWeights( matrix1 ) );
47
48 matrix1.coeffRef( w / 3, h / 2 ) = 0.8_ra;
49
50 // Matrix1 normalization ok
51 REQUIRE( normalizeWeights( matrix1 ) );
52
53 WeightMatrix matrix2 = matrix1;
54 matrix2 *= 0.5_ra;
55
56 WeightMatrix matrix3 = partitionOfUnity( matrix2 );
57
58 // Matrix2 needs to be normalized
59 REQUIRE( normalizeWeights( matrix2 ) );
60 // Matrix3 is already normalized
61 REQUIRE( !normalizeWeights( matrix3 ) );
62 // Two matrices are equivalent after normalization
63 REQUIRE( matrix1.isApprox( matrix2 ) );
64 // Two matrices are equivalent after partition of unity
65 REQUIRE( matrix1.isApprox( matrix3 ) );
66
67 matrix2.coeffRef( w / 3, h / 2 ) = std::nanf( "" );
68 // Should not find NaN in this matrix
69 REQUIRE( checkWeightMatrix( matrix1, false ) );
70 // Should find NaN in this matrix
71 REQUIRE( !checkWeightMatrix( matrix2, false ) );
72 }
73}
74
75TEST_CASE( "Core/Animation/KeyFramedValue", "[unittests][Core][Core/Animation][KeyFramedValue]" ) {
76
77 KeyFramedValue<Scalar> kf { 2_ra, 2_ra };
78
79 auto checkValues = []( auto& p, Scalar time, Scalar value ) {
80 REQUIRE( Math::areApproxEqual( p.first, time ) );
81 REQUIRE( Math::areApproxEqual( p.second, value ) );
82 };
83
84 auto checkSorting = []( auto& lkf ) {
85 for ( size_t i = 1; i < lkf.size(); ++i ) {
86 REQUIRE( lkf[i - 1].first < lkf[i].first );
87 }
88 };
89
90 SECTION( "Test keyframe manipulation" ) {
91 // There is one keyframe
92 REQUIRE( kf.size() == 1 );
93 // We cannot remove it
94 REQUIRE( !kf.removeKeyFrame( 0 ) );
95 // It should be (2,2)
96 checkValues( kf[0], 2_ra, 2_ra );
97
98 // adding before first
99 kf.insertKeyFrame( 0_ra, 0_ra );
100 // There should be 2 keyframes
101 REQUIRE( kf.size() == 2 );
102 // These should be sorted
103 checkSorting( kf );
104 // The first one should be (0,0)
105 checkValues( kf[0], 0_ra, 0_ra );
106 // The second one should be (2,2)
107 checkValues( kf[1], 2_ra, 2_ra );
108
109 // adding in between
110 kf.insertKeyFrame( 1_ra, 1_ra );
111 // There should be 3 keyframes
112 REQUIRE( kf.size() == 3 );
113 // These should be sorted
114 checkSorting( kf );
115 // The first one should be (0,0)
116 checkValues( kf[0], 0_ra, 0_ra );
117 // The second one should be (1,1)
118 checkValues( kf[1], 1_ra, 1_ra );
119 // The third one should be (2,2)
120 checkValues( kf[2], 2_ra, 2_ra );
121
122 // adding after last
123 kf.insertKeyFrame( 3_ra, 3_ra );
124 // There should still be 4 keyframes
125 REQUIRE( kf.size() == 4 );
126 // These should be sorted
127 checkSorting( kf );
128 // The first one should be (0,0)
129 checkValues( kf[0], 0_ra, 0_ra );
130 // The second one should be (1,1)
131 checkValues( kf[1], 1_ra, 1_ra );
132 // The third one should be (2,2)
133 checkValues( kf[2], 2_ra, 2_ra );
134 // The fourth one should be (3,3)
135 checkValues( kf[3], 3_ra, 3_ra );
136
137 {
138 auto kf2 = kf;
139 // There should be 4 keyframes
140 REQUIRE( kf2.size() == 4 );
141
142 // Should be able to remove inside
143 REQUIRE( kf2.removeKeyFrame( 2 ) );
144 // There should be 3 keyframes
145 REQUIRE( kf2.size() == 3 );
146 // These should be sorted
147 checkSorting( kf2 );
148 // The first one should be (0,0)
149 checkValues( kf2[0], 0_ra, 0_ra );
150 // The second one should be (1,1)
151 checkValues( kf2[1], 1_ra, 1_ra );
152 // The third one should be (3,3)
153 checkValues( kf2[2], 3_ra, 3_ra );
154
155 // Should be able to remove last
156 REQUIRE( kf2.removeKeyFrame( 2 ) );
157 // There should be 2 keyframes
158 REQUIRE( kf2.size() == 2 );
159 // These should be sorted
160 checkSorting( kf2 );
161 // The first one should be (0,0)
162 checkValues( kf2[0], 0_ra, 0_ra );
163 // The second one should be (1,1)
164 checkValues( kf2[1], 1_ra, 1_ra );
165
166 // Should be able to remove first
167 REQUIRE( kf2.removeKeyFrame( 0 ) );
168 // There should be 1 keyframe
169 REQUIRE( kf2.size() == 1 );
170 // It should be (1,1)
171 checkValues( kf2[0], 1_ra, 1_ra );
172
173 // We cannot remove it
174 REQUIRE( !kf2.removeKeyFrame( 0 ) );
175 }
176
177 // Replacing value
178 kf.insertKeyFrame( 3_ra, 2_ra );
179 // There should still be 4 keyframes
180 REQUIRE( kf.size() == 4 );
181 // These should be sorted
182 checkSorting( kf );
183 // The first one should be (0,0)
184 checkValues( kf[0], 0_ra, 0_ra );
185 // The second one should be (1,1)
186 checkValues( kf[1], 1_ra, 1_ra );
187 // The third one should be (2,2)
188 checkValues( kf[2], 2_ra, 2_ra );
189 // The fourth one should be (3,2)
190 checkValues( kf[3], 3_ra, 2_ra );
191
192 // Moving keyframe to the front
193 kf.moveKeyFrame( 1, -1_ra );
194 // There should still be 4 keyframes
195 REQUIRE( kf.size() == 4 );
196 // These should be sorted
197 checkSorting( kf );
198 // The first one should be (-1,1)
199 checkValues( kf[0], -1_ra, 1_ra );
200 // The second one should be (0,0)
201 checkValues( kf[1], 0_ra, 0_ra );
202 // The third one should be (2,2)
203 checkValues( kf[2], 2_ra, 2_ra );
204 // The fourth one should be (3,2)
205 checkValues( kf[3], 3_ra, 2_ra );
206
207 // Moving keyframe in between
208 kf.moveKeyFrame( 1, 2.5_ra );
209 // There should still be 4 keyframes
210 REQUIRE( kf.size() == 4 );
211 // These should be sorted
212 checkSorting( kf );
213 // The first one should be (-1,1)
214 checkValues( kf[0], -1_ra, 1_ra );
215 // The second one should be (2,2)
216 checkValues( kf[1], 2_ra, 2_ra );
217 // The third one should be (2.5,0)
218 checkValues( kf[2], 2.5_ra, 0_ra );
219 // The fourth one should be (3,2)
220 checkValues( kf[3], 3_ra, 2_ra );
221
222 // Moving keyframe to the end
223 kf.moveKeyFrame( 1, 4_ra );
224 // There should still be 4 keyframes
225 REQUIRE( kf.size() == 4 );
226 // These should be sorted
227 checkSorting( kf );
228 // The first one should be (-1,1)
229 checkValues( kf[0], -1_ra, 1_ra );
230 // The second one should be (2.5,0)
231 checkValues( kf[1], 2.5_ra, 0_ra );
232 // The third one should be (3,2)
233 checkValues( kf[2], 3_ra, 2_ra );
234 // The fourth one should be (4,2)
235 checkValues( kf[3], 4_ra, 2_ra );
236
237 // Evaluating before first time should give first value
238 REQUIRE( Math::areApproxEqual( kf.at( kf[0].first - 1, linearInterpolate<Scalar> ),
239 kf[0].second ) );
240 // Evaluating at keyframe time should give keyframe value
241 for ( size_t i = 0; i < kf.size(); ++i ) {
242 REQUIRE( Math::areApproxEqual( kf.at( kf[i].first, linearInterpolate<Scalar> ),
243 kf[i].second ) );
244 }
245 // Evaluating after last time should give last value
246 REQUIRE(
247 Math::areApproxEqual( kf.at( kf[kf.size() - 1].first + 1, linearInterpolate<Scalar> ),
248 kf[kf.size() - 1].second ) );
249 // Check some in-between interpolations
250 REQUIRE( Math::areApproxEqual( kf.at( 0_ra, linearInterpolate<Scalar> ), 5_ra / 7 ) );
251 REQUIRE( Math::areApproxEqual( kf.at( 2.75_ra, linearInterpolate<Scalar> ), 1_ra ) );
252 REQUIRE( Math::areApproxEqual( kf.at( 3.7_ra, linearInterpolate<Scalar> ), 2_ra ) );
253
254 // Insert before first using interpolation
255 kf.insertInterpolatedKeyFrame( -2_ra, linearInterpolate<Scalar> );
256 // There should still be 5 keyframes
257 REQUIRE( kf.size() == 5 );
258 // These should be sorted
259 checkSorting( kf );
260 // The first one should be (-2,1)
261 checkValues( kf[0], -2_ra, 1_ra );
262 // The second one should be (-1,1)
263 checkValues( kf[1], -1_ra, 1_ra );
264 // The third one should be (2.5,0)
265 checkValues( kf[2], 2.5_ra, 0_ra );
266 // The fourth one should be (3,2)
267 checkValues( kf[3], 3_ra, 2_ra );
268 // The fifth one should be (4,2)
269 checkValues( kf[4], 4_ra, 2_ra );
270
271 // Insert in-between using interpolation
272 kf.insertInterpolatedKeyFrame( 2.75_ra, linearInterpolate<Scalar> );
273 // There should still be 6 keyframes
274 REQUIRE( kf.size() == 6 );
275 // These should be sorted
276 checkSorting( kf );
277 // The first one should be (-2,1)
278 checkValues( kf[0], -2_ra, 1_ra );
279 // The second one should be (-1,1)
280 checkValues( kf[1], -1_ra, 1_ra );
281 // The third one should be (2.5,0)
282 checkValues( kf[2], 2.5_ra, 0_ra );
283 // The fourth one should be (2.75,1)
284 checkValues( kf[3], 2.75_ra, 1_ra );
285 // The fifth one should be (3,2)
286 checkValues( kf[4], 3_ra, 2_ra );
287 // The sixth one should be (4,2)
288 checkValues( kf[5], 4_ra, 2_ra );
289
290 // Insert after last using interpolation
291 kf.insertInterpolatedKeyFrame( 5_ra, linearInterpolate<Scalar> );
292 // There should still be 7 keyframes
293 REQUIRE( kf.size() == 7 );
294 // These should be sorted
295 checkSorting( kf );
296 // The first one should be (-2,1)
297 checkValues( kf[0], -2_ra, 1_ra );
298 // The second one should be (-1,1)
299 checkValues( kf[1], -1_ra, 1_ra );
300 // The third one should be (2.5,0)
301 checkValues( kf[2], 2.5_ra, 0_ra );
302 // The fourth one should be (2.75,1)
303 checkValues( kf[3], 2.75_ra, 1_ra );
304 // The fifth one should be (3,2)
305 checkValues( kf[4], 3_ra, 2_ra );
306 // The sixth one should be (4,2)
307 checkValues( kf[5], 4_ra, 2_ra );
308 // The seventh one should be (5,2)
309 checkValues( kf[6], 5_ra, 2_ra );
310 }
311}
312
313TEST_CASE( "Core/Animation/KeyFramedStruct", "[unittests]" ) {
315 struct MyStruct {
316 MyStruct() :
317 m_nonAnimatedData { 2_ra }, // initialize the non animated data as 2
318 m_animatedData { 1_ra, 0_ra } // creating the animated data with value 0 at time 1
319 {
320 m_animatedData.insertKeyFrame( 3_ra, 2_ra ); // adding a keyframe with value 2 at time 3
321 }
322
323 Scalar fetch( Scalar time ) {
324 // fetch the interpolated value for the given time
325 Scalar v_t = m_animatedData.at( time, Ra::Core::Animation::linearInterpolate<Scalar> );
326 // use it
327 return m_nonAnimatedData * v_t;
328 }
329
330 Scalar m_nonAnimatedData;
332 };
335
336 struct MyStructAnimator {
337 MyStructAnimator( MyStruct& s ) {
338 // create the keyframes for the data
339 auto frames = new Ra::Core::Animation::KeyFramedValue<Scalar> { 0_ra, 0_ra };
340 frames->insertKeyFrame( 4_ra, 4_ra );
341 // create the controller
342 m_controller.m_value = frames;
343 m_controller.m_updater = [frames, &s]( const Scalar& t ) {
344 // fetch the interpolated value for the given time
345 auto v_t = frames->at( t, Ra::Core::Animation::linearInterpolate<Scalar> );
346 // update the data
347 s.m_nonAnimatedData = v_t;
348 };
349 }
350
351 void update( Scalar time ) {
352 m_controller.updateKeyFrame( time ); // uses the keyframes to update the data.
353 }
354
356 };
358
359 SECTION( "" ) {
361 MyStruct a;
362 std::cout << a.fetch( 0_ra ) << std::endl; // prints: 0
363 std::cout << a.fetch( 2_ra ) << std::endl; // prints: 2
364 std::cout << a.fetch( 4_ra ) << std::endl; // prints: 4
366
367 REQUIRE( Math::areApproxEqual( a.fetch( 0_ra ), 0_ra ) );
368 REQUIRE( Math::areApproxEqual( a.fetch( 2_ra ), 2_ra ) );
369 REQUIRE( Math::areApproxEqual( a.fetch( 4_ra ), 4_ra ) );
370 }
371 SECTION( "" ) {
373 MyStruct a; // a.m_nonAnimatedData = 2 in ctor
374 MyStructAnimator b( a );
375 std::cout << a.fetch( 0_ra ) << std::endl; // prints: 0
376 std::cout << a.fetch( 2_ra ) << std::endl; // prints: 2
377 std::cout << a.fetch( 4_ra ) << std::endl; // prints: 4
378 b.update( 1_ra ); // now: a.m_nonAnimatedData = 1
379 std::cout << a.fetch( 0_ra ) << std::endl; // prints: 0
380 std::cout << a.fetch( 2_ra ) << std::endl; // prints: 1
381 std::cout << a.fetch( 4_ra ) << std::endl; // prints: 2
382 b.update( 2_ra ); // now: a.m_nonAnimatedData = 2
383 std::cout << a.fetch( 0_ra ) << std::endl; // prints: 0
384 std::cout << a.fetch( 2_ra ) << std::endl; // prints: 2
385 std::cout << a.fetch( 4_ra ) << std::endl; // prints: 4
386 b.update( 4_ra ); // now: a.m_nonAnimatedData = 4
387 std::cout << a.fetch( 0_ra ) << std::endl; // prints: 0
388 std::cout << a.fetch( 2_ra ) << std::endl; // prints: 4
389 std::cout << a.fetch( 4_ra ) << std::endl; // prints: 8
390
392 b.update( 1_ra );
393 REQUIRE( Math::areApproxEqual( a.m_nonAnimatedData, 1_ra ) );
394 REQUIRE( Math::areApproxEqual( a.fetch( 0_ra ), 0_ra ) );
395 REQUIRE( Math::areApproxEqual( a.fetch( 2_ra ), 1_ra ) );
396 REQUIRE( Math::areApproxEqual( a.fetch( 4_ra ), 2_ra ) );
397 b.update( 2_ra );
398 REQUIRE( Math::areApproxEqual( a.m_nonAnimatedData, 2_ra ) );
399 REQUIRE( Math::areApproxEqual( a.fetch( 0_ra ), 0_ra ) );
400 REQUIRE( Math::areApproxEqual( a.fetch( 2_ra ), 2_ra ) );
401 REQUIRE( Math::areApproxEqual( a.fetch( 4_ra ), 4_ra ) );
402 b.update( 4_ra );
403 REQUIRE( Math::areApproxEqual( a.m_nonAnimatedData, 4_ra ) );
404 REQUIRE( Math::areApproxEqual( a.fetch( 0_ra ), 0_ra ) );
405 REQUIRE( Math::areApproxEqual( a.fetch( 2_ra ), 4_ra ) );
406 REQUIRE( Math::areApproxEqual( a.fetch( 4_ra ), 8_ra ) );
407 }
408}
409
410TEST_CASE( "Core/Animation/Skeleton", "[unittests][Core][Core/Animation][Skeleton]" ) {
411 using Space = HandleArray::SpaceType;
412 // build the skeleton in the X direction: > - > - > - > starting at the origin
413 Skeleton skel;
414 int root = skel.addRoot( Transform::Identity(), "root" );
415 Transform localT = Transform::Identity();
416 localT.translation() = Vector3::UnitX();
417 int bone1 = skel.addBone( root, localT, Space::LOCAL, "bone1" );
418 int bone2 = skel.addBone( bone1, localT, Space::LOCAL, "bone2" );
419 int bone3 = skel.addBone( bone2, localT, Space::LOCAL, "bone3" );
420
421 SECTION( "Test initialization" ) {
422 // check root / leaf status
423 REQUIRE( skel.m_graph.isRoot( root ) );
424 REQUIRE( !skel.m_graph.isRoot( bone1 ) );
425 REQUIRE( !skel.m_graph.isRoot( bone2 ) );
426 REQUIRE( !skel.m_graph.isRoot( bone3 ) );
427 REQUIRE( !skel.m_graph.isLeaf( root ) );
428 REQUIRE( !skel.m_graph.isLeaf( bone1 ) );
429 REQUIRE( !skel.m_graph.isLeaf( bone2 ) );
430 REQUIRE( skel.m_graph.isLeaf( bone3 ) );
431 // check hierarchy
432 REQUIRE( skel.m_graph.parents().size() == 4 );
433 REQUIRE( skel.m_graph.parents()[root] == -1 );
434 REQUIRE( skel.m_graph.parents()[bone1] == root );
435 REQUIRE( skel.m_graph.parents()[bone2] == bone1 );
436 REQUIRE( skel.m_graph.parents()[bone3] == bone2 );
437 REQUIRE( skel.m_graph.children().size() == 4 );
438 REQUIRE( skel.m_graph.children()[root].size() == 1 );
439 REQUIRE( skel.m_graph.children()[root][0] == bone1 );
440 REQUIRE( skel.m_graph.children()[bone1].size() == 1 );
441 REQUIRE( skel.m_graph.children()[bone1][0] == bone2 );
442 REQUIRE( skel.m_graph.children()[bone2].size() == 1 );
443 REQUIRE( skel.m_graph.children()[bone2][0] == bone3 );
444 REQUIRE( skel.m_graph.children()[bone3].size() == 0 );
445 // check Local Pose
446 REQUIRE( areEqual( skel.getPose( Space::LOCAL ),
447 { Transform::Identity(), localT, localT, localT } ) );
448 REQUIRE( skel.getTransform( root, Space::LOCAL ).isApprox( Transform::Identity() ) );
449 REQUIRE( skel.getTransform( bone1, Space::LOCAL ).isApprox( localT ) );
450 REQUIRE( skel.getTransform( bone2, Space::LOCAL ).isApprox( localT ) );
451 REQUIRE( skel.getTransform( bone3, Space::LOCAL ).isApprox( localT ) );
452 // check Model Pose
453 Transform T1 = Transform::Identity();
454 T1.translation() = Vector3::UnitX();
455 Transform T2 = Transform::Identity();
456 T2.translation() = 2 * Vector3::UnitX();
457 Transform T3 = Transform::Identity();
458 T3.translation() = 3 * Vector3::UnitX();
459 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { Transform::Identity(), T1, T2, T3 } ) );
460 REQUIRE( skel.getTransform( root, Space::MODEL ).isApprox( Transform::Identity() ) );
461 REQUIRE( skel.getTransform( bone1, Space::MODEL ).isApprox( T1 ) );
462 REQUIRE( skel.getTransform( bone2, Space::MODEL ).isApprox( T2 ) );
463 REQUIRE( skel.getTransform( bone3, Space::MODEL ).isApprox( T3 ) );
464 // check endPoints
465 Vector3 a, b;
466 skel.getBonePoints( root, a, b );
467 REQUIRE( a.isApprox( Vector3::Zero() ) );
468 REQUIRE( b.isApprox( Vector3::UnitX() ) );
469 skel.getBonePoints( bone1, a, b );
470 REQUIRE( a.isApprox( Vector3::UnitX() ) );
471 REQUIRE( b.isApprox( 2 * Vector3::UnitX() ) );
472 skel.getBonePoints( bone2, a, b );
473 REQUIRE( a.isApprox( 2 * Vector3::UnitX() ) );
474 REQUIRE( b.isApprox( 3 * Vector3::UnitX() ) );
475 skel.getBonePoints( bone3, a, b );
476 REQUIRE( a.isApprox( 3 * Vector3::UnitX() ) );
477 REQUIRE( b.isApprox( 3 * Vector3::UnitX() ) );
478 }
479
480 SECTION( "Test Forward Manipulation" ) {
481 /* > - - v
482 * Here we will pose the skeleton from to | |
483 * ^ |
484 * > - > - > - > + <
485 * (in the X-Y plane) where + is the origin, using Forward manipulation
486 */
487
488 // reminder of the current transforms:
489 Transform T = Transform::Identity();
490 Transform T1 = Transform::Identity();
491 T1.translation() = Vector3::UnitX();
492 Transform T2 = Transform::Identity();
493 T2.translation() = 2 * Vector3::UnitX();
494 Transform T3 = Transform::Identity();
495 T3.translation() = 3 * Vector3::UnitX();
496 Transform localT1 = localT;
497 Transform localT2 = localT;
498 Transform localT3 = localT;
499
500 REQUIRE( skel.m_manipulation == Skeleton::FORWARD );
501
502 SECTION( "using only Model Space" ) {
503 /* first rotate and move root: => ^
504 * > - > - > - > + ` > - > - >
505 */
506 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
507 T.translation() = Vector3::UnitY();
508 skel.setTransform( root, T, Space::MODEL );
509 // check Pose
510 localT1 = Transform::Identity();
511 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
512 localT1.translation() = -Vector3::UnitX() - Vector3::UnitY();
513 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
514 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
515
516 /* >
517 * then move bone1: => | \
518 * ^ ^ \
519 * + ` > - > - > + > - >
520 */
521 T1 = Transform::Identity();
522 T1.translation() = 2 * Vector3::UnitY();
523 skel.setTransform( bone1, T1, Space::MODEL );
524 // check Pose
525 localT1 = Transform::Identity();
526 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
527 localT1.translation() = Vector3::UnitX();
528 localT2 = Transform::Identity();
529 localT2.translation() = 2 * Vector3::UnitX() - 2 * Vector3::UnitY();
530 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
531 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
532
533 /* > > - - v
534 * then rotate and move bone2: | \ => |
535 * ^ \ ^ \
536 * + > - > + >
537 */
538 T2 = Transform::Identity();
539 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
540 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
541 skel.setTransform( bone2, T2, Space::MODEL );
542 // check Pose
543 localT2 = Transform::Identity();
544 localT2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
545 localT2.translation() = 2 * Vector3::UnitX();
546 localT3 = Transform::Identity();
547 localT3.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
548 localT3.translation() = 2 * Vector3::UnitX() + Vector3::UnitY();
549 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
550 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
551
552 /* > - - v > - - v
553 * finally rotate bone3: | => | |
554 * ^ \ ^ |
555 * + > + <
556 */
557 T3 = Transform::Identity();
558 T3.rotate( AngleAxis( Math::Pi, Vector3::UnitZ() ) );
559 T3.translation() = 2 * Vector3::UnitX();
560 skel.setTransform( bone3, T3, Space::MODEL );
561 // check Pose
562 localT3 = Transform::Identity();
563 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
564 localT3.translation() = 2 * Vector3::UnitX();
565 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
566 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
567 }
568
569 SECTION( "using only Local Space" ) {
570 /* ^
571 * |
572 * ^
573 * |
574 * ^
575 * |
576 * first rotate and move root: => ^
577 * > - > - > - > +
578 */
579 T = Transform::Identity();
580 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
581 T.translation() = Vector3::UnitY();
582 skel.setTransform( root, T, Space::LOCAL );
583 // check Pose
584 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
585 T1 = Transform::Identity();
586 T1.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
587 T1.translation() = 2 * Vector3::UnitY();
588 T2 = Transform::Identity();
589 T2.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
590 T2.translation() = 3 * Vector3::UnitY();
591 T3 = Transform::Identity();
592 T3.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
593 T3.translation() = 4 * Vector3::UnitY();
594 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
595
596 /* ^
597 * |
598 * ^
599 * |
600 * ^ > - > - >
601 * | |
602 * then rotate bone1: ^ => ^
603 * + +
604 */
605 localT1 = Transform::Identity();
606 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
607 localT1.translation() = Vector3::UnitX();
608 skel.setTransform( bone1, localT1, Space::LOCAL );
609 // check Pose
610 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
611 T1 = Transform::Identity();
612 T1.translation() = 2 * Vector3::UnitY();
613 T2 = Transform::Identity();
614 T2.translation() = Vector3::UnitX() + 2 * Vector3::UnitY();
615 T3 = Transform::Identity();
616 T3.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
617 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
618
619 /* > - > - > > - - v
620 * | | |
621 * then rotate and move bone2: ^ => ^ v
622 * + +
623 */
624 localT2 = Transform::Identity();
625 localT2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
626 localT2.translation() = 2 * Vector3::UnitX();
627 skel.setTransform( bone2, localT2, Space::LOCAL );
628 // check Pose
629 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
630 T2 = Transform::Identity();
631 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
632 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
633 T3 = Transform::Identity();
634 T3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
635 T3.translation() = 2 * Vector3::UnitX() + Vector3::UnitY();
636 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
637
638 /* > - - v > - - v
639 * | | | |
640 * finally rotate and move bone3: ^ v => ^ |
641 * + + <
642 */
643 localT3 = Transform::Identity();
644 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
645 localT3.translation() = 2 * Vector3::UnitX();
646 skel.setTransform( bone3, localT3, Space::LOCAL );
647 // check Pose
648 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
649 T3 = Transform::Identity();
650 T3.rotate( AngleAxis( Math::Pi, Vector3::UnitZ() ) );
651 T3.translation() = 2 * Vector3::UnitX();
652 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
653 }
654
655 SECTION( "using Both Spaces" ) {
656 /* first rotate and move root in Model Space: => ^
657 * > - > - > - > + ` > - > - >
658 */
659 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
660 T.translation() = Vector3::UnitY();
661 skel.setTransform( root, T, Space::MODEL );
662 // check Pose
663 localT1 = Transform::Identity();
664 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
665 localT1.translation() = -Vector3::UnitX() - Vector3::UnitY();
666 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
667 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
668
669 /* > - > - >
670 * |
671 * then move bone1 in Local Space: ^ => ^
672 * + ` > - > - > +
673 */
674 localT1 = Transform::Identity();
675 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
676 localT1.translation() = Vector3::UnitX();
677 skel.setTransform( bone1, localT1, Space::LOCAL );
678 // check Pose
679 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
680 T1 = Transform::Identity();
681 T1.translation() = 2 * Vector3::UnitY();
682 T2 = Transform::Identity();
683 T2.translation() = Vector3::UnitX() + 2 * Vector3::UnitY();
684 T3 = Transform::Identity();
685 T3.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
686 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
687
688 /*
689 * > - > - > > - - v>
690 * | |
691 * then rotate and move bone2 in Model Space: ^ => ^
692 * + +
693 */
694 T2 = Transform::Identity();
695 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
696 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
697 skel.setTransform( bone2, T2, Space::MODEL );
698 // check Pose
699 localT2 = Transform::Identity();
700 localT2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
701 localT2.translation() = 2 * Vector3::UnitX();
702 localT3 = Transform::Identity();
703 localT3.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
704 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
705 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
706
707 /* > - - v> > - - v
708 * | | |
709 * finally rotate and move bone3 in Local Space: ^ => ^ |
710 * + + <
711 */
712 localT3 = Transform::Identity();
713 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
714 localT3.translation() = 2 * Vector3::UnitX();
715 skel.setTransform( bone3, localT3, Space::LOCAL );
716 // check Pose
717 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
718 T3 = Transform::Identity();
719 T3.rotate( AngleAxis( Math::Pi, Vector3::UnitZ() ) );
720 T3.translation() = 2 * Vector3::UnitX();
721 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
722 }
723 }
724
725 SECTION( "Test Pseudo-IK Manipulation" ) {
726 /* > - - v
727 * Here we will pose the skeleton from to | |
728 * ^ |
729 * > - > - > - > + <
730 * (in the X-Y plane) where + is the origin, using Pseudo-IK manipulation
731 */
732
733 // set Pseudo-IK mode
735
736 // reminder of the current transforms:
737 Transform T = Transform::Identity();
738 Transform T1 = Transform::Identity();
739 T1.translation() = Vector3::UnitX();
740 Transform T2 = Transform::Identity();
741 T2.translation() = 2 * Vector3::UnitX();
742 Transform T3 = Transform::Identity();
743 T3.translation() = 3 * Vector3::UnitX();
744 Transform localT1 = localT;
745 Transform localT2 = localT;
746 Transform localT3 = localT;
747
748 SECTION( "with Model Space" ) {
749 /* ^
750 * |
751 * ^
752 * |
753 * ^
754 * |
755 * first rotate and move root: => ^
756 * > - > - > - > +
757 */
758 T = Transform::Identity();
759 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
760 T.translation() = Vector3::UnitY();
761 skel.setTransform( root, T, Space::MODEL );
762 // check Pose
763 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
764 T1 = Transform::Identity();
765 T1.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
766 T1.translation() = 2 * Vector3::UnitY();
767 T2 = Transform::Identity();
768 T2.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
769 T2.translation() = 3 * Vector3::UnitY();
770 T3 = Transform::Identity();
771 T3.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
772 T3.translation() = 4 * Vector3::UnitY();
773 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
774
775 /* ^
776 * |
777 * ^
778 * |
779 * ^ > - - v
780 * | | |
781 * then rotate and move bone2: ^ => ^ v
782 * + +
783 * hence rotating bone1
784 */
785 T2 = Transform::Identity();
786 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
787 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
788 skel.setTransform( bone2, T2, Space::MODEL );
789 // check Pose
790 localT1 = Transform::Identity();
791 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
792 localT1.translation() = Vector3::UnitX();
793 localT2 = Transform::Identity();
794 localT2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
795 localT2.translation() = 2 * Vector3::UnitX();
796 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
797 T1 = Transform::Identity();
798 T1.translation() = 2 * Vector3::UnitY();
799 T3 = Transform::Identity();
800 T3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
801 T3.translation() = 2 * Vector3::UnitX() + Vector3::UnitY();
802 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
803
804 /* > - - v > - - v
805 * | | | |
806 * finally rotate and move bone3: ^ v => ^ |
807 * + + <
808 */
809 T3 = Transform::Identity();
810 T3.rotate( AngleAxis( Math::Pi, Vector3::UnitZ() ) );
811 T3.translation() = 2 * Vector3::UnitX();
812 skel.setTransform( bone3, T3, Space::MODEL );
813 // check Pose
814 localT3 = Transform::Identity();
815 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
816 localT3.translation() = 2 * Vector3::UnitX();
817 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
818 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
819 }
820
821 SECTION( "with Local Space" ) {
822 /* ^
823 * |
824 * ^
825 * |
826 * ^
827 * |
828 * first rotate and move root: => ^
829 * > - > - > - > +
830 */
831 T = Transform::Identity();
832 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
833 T.translation() = Vector3::UnitY();
834 skel.setTransform( root, T, Space::LOCAL );
835 // check Pose
836 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
837 T1 = Transform::Identity();
838 T1.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
839 T1.translation() = 2 * Vector3::UnitY();
840 T2 = Transform::Identity();
841 T2.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
842 T2.translation() = 3 * Vector3::UnitY();
843 T3 = Transform::Identity();
844 T3.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
845 T3.translation() = 4 * Vector3::UnitY();
846 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
847
848 /* ^
849 * |
850 * ^
851 * |
852 * ^ > - - v
853 * | | |
854 * then rotate and move bone2: ^ => ^ v
855 * + +
856 * hence rotating bone1 in the process
857 */
858 // note: we express localT2 as we expect it to be if bone1 is not rotated!
859 localT2 = Transform::Identity();
860 localT2.rotate( AngleAxis( -Math::Pi, Vector3::UnitZ() ) );
861 localT2.translation() = -2 * Vector3::UnitY();
862 skel.setTransform( bone2, localT2, Space::LOCAL );
863 // note: the local transform of bone 2 is not localT2 anymore since we
864 // rotated bone1 thanks to the Pseudo-IK!
865 // check Pose
866 localT1 = Transform::Identity();
867 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
868 localT1.translation() = Vector3::UnitX();
869 localT2 = Transform::Identity();
870 localT2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
871 localT2.translation() = 2 * Vector3::UnitX();
872 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
873 T1 = Transform::Identity();
874 T1.translation() = 2 * Vector3::UnitY();
875 T2 = Transform::Identity();
876 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
877 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
878 T3 = Transform::Identity();
879 T3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
880 T3.translation() = 2 * Vector3::UnitX() + Vector3::UnitY();
881 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
882
883 /* > - - v > - - v
884 * | | | |
885 * finally rotate and move bone3: ^ v => ^ |
886 * + + <
887 */
888 localT3 = Transform::Identity();
889 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
890 localT3.translation() = 2 * Vector3::UnitX();
891 skel.setTransform( bone3, localT3, Space::LOCAL );
892 // check Pose
893 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
894 T3 = Transform::Identity();
895 T3.rotate( AngleAxis( -Math::Pi, Vector3::UnitZ() ) );
896 T3.translation() = 2 * Vector3::UnitX();
897 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
898 }
899
900 SECTION( "with Both Spaces" ) {
901 /* ^
902 * |
903 * ^
904 * |
905 * ^
906 * |
907 * first rotate and move root: => ^
908 * > - > - > - > +
909 */
910 T = Transform::Identity();
911 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
912 T.translation() = Vector3::UnitY();
913 skel.setTransform( root, T, Space::MODEL );
914 // check Pose
915 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
916 T1 = Transform::Identity();
917 T1.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
918 T1.translation() = 2 * Vector3::UnitY();
919 T2 = Transform::Identity();
920 T2.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
921 T2.translation() = 3 * Vector3::UnitY();
922 T3 = Transform::Identity();
923 T3.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
924 T3.translation() = 4 * Vector3::UnitY();
925 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
926
927 /* ^
928 * |
929 * ^
930 * |
931 * ^ > - - v
932 * | | |
933 * then rotate and move bone2: ^ => ^ v
934 * + +
935 * hence rotating bone1 in the process
936 */
937 // note: we express localT2 as we expect it to be if bone1 is not rotated!
938 localT2 = Transform::Identity();
939 localT2.rotate( AngleAxis( -Math::Pi, Vector3::UnitZ() ) );
940 localT2.translation() = -2 * Vector3::UnitY();
941 skel.setTransform( bone2, localT2, Space::LOCAL );
942 // note: the local transform of bone 2 is not localT2 anymore since we
943 // rotated bone1 thanks to the Pseudo-IK!
944 // check Pose
945 localT1 = Transform::Identity();
946 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
947 localT1.translation() = Vector3::UnitX();
948 localT2 = Transform::Identity();
949 localT2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
950 localT2.translation() = 2 * Vector3::UnitX();
951 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
952 T1 = Transform::Identity();
953 T1.translation() = 2 * Vector3::UnitY();
954 T2 = Transform::Identity();
955 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
956 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
957 T3 = Transform::Identity();
958 T3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
959 T3.translation() = 2 * Vector3::UnitX() + Vector3::UnitY();
960 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
961
962 /* > - - v > - - v
963 * | | | |
964 * finally rotate and move bone3: ^ v => ^ |
965 * + + <
966 */
967 T3 = Transform::Identity();
968 T3.rotate( AngleAxis( Math::Pi, Vector3::UnitZ() ) );
969 T3.translation() = 2 * Vector3::UnitX();
970 skel.setTransform( bone3, T3, Space::MODEL );
971 // check Pose
972 localT3 = Transform::Identity();
973 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
974 localT3.translation() = 2 * Vector3::UnitX();
975 REQUIRE( areEqual( skel.getPose( Space::LOCAL ), { T, localT1, localT2, localT3 } ) );
976 REQUIRE( areEqual( skel.getPose( Space::MODEL ), { T, T1, T2, T3 } ) );
977 }
978 }
979}
980
981TEST_CASE( "Core/Animation/DualQuaternionSkinning",
982 "[unitests][Core][Core/Animation][DualQuaternionSkinning]" ) {
983 // initialize the pose
985 Ra::Core::Transform t0 { Ra::Core::Transform::Identity() };
986 Ra::Core::Transform t1(
987 Eigen::AngleAxis( Scalar( -M_PI / 4. ), Ra::Core::Vector3 { 0_ra, 1_ra, 0_ra } ) );
988 pose.push_back( t0 );
989 pose.push_back( t1 );
990 // define a simple mesh
991 Ra::Core::Vector3Array vertices { { 0_ra, 0_ra, 0_ra },
992 { 0_ra, 1_ra, 0_ra },
993 { 1_ra, 0_ra, 0_ra },
994 { 1_ra, 1_ra, 0_ra },
995 { 2_ra, 0_ra, 0_ra },
996 { 2_ra, 1_ra, 0_ra } };
997 // define the weight matrix
998 Ra::Core::Animation::WeightMatrix weights( 6, 2 );
999 // M( i, j ) = w , if vertex i is influenced by transform j ;
1000 // influence of bone 0
1001 weights.insert( 0, 0 ) = 1_ra;
1002 weights.insert( 1, 0 ) = 1_ra;
1003 weights.insert( 2, 0 ) = 0.5_ra;
1004 weights.insert( 3, 0 ) = 0.5_ra;
1005 // influence of bone 1
1006 weights.insert( 2, 1 ) = 0.5_ra;
1007 weights.insert( 3, 1 ) = 0.5_ra;
1008 weights.insert( 4, 1 ) = 1_ra;
1009 weights.insert( 5, 1 ) = 1_ra;
1010
1011 // Interpolate the transformations
1012 Ra::Core::Quaternion q0( t0.linear() );
1013 Ra::Core::Quaternion q1( t1.linear() );
1014 Ra::Core::Quaternion q3 = q0.slerp( 0.5_ra, q1 );
1015
1016 // test dual quaternions
1017 auto dq = Ra::Core::Animation::computeDQ( pose, weights );
1018 REQUIRE( q3.toRotationMatrix().isApprox( dq[2].getTransform().linear() ) );
1019
1020 // apply and verify dualquaternion deformation
1021 auto sk = Ra::Core::Animation::applyDualQuaternions( dq, vertices );
1022 REQUIRE( vertices[0].isApprox( sk[0] ) );
1023 Ra::Core::Transform t( q3 );
1024 auto vt = t * vertices[2];
1025 REQUIRE( vt.isApprox( sk[2] ) );
1026 auto vt1 = t1 * vertices[4];
1027 REQUIRE( vt1.isApprox( sk[4] ) );
1028
1029 // test naive dual quaternions
1030 auto dq_n = Ra::Core::Animation::computeDQ( pose, weights );
1031 REQUIRE( q3.toRotationMatrix().isApprox( dq_n[2].getTransform().linear() ) );
1032}
bool isLeaf(const uint i) const
Return true if the node is a leaf node.
bool isRoot(const uint i) const
Return true if a node is a root node.
void insertKeyFrame(const Scalar &t, const VALUE_TYPE &frame)
uint addRoot(const Transform &T=Transform::Identity(), const Label label="")
Definition Skeleton.cpp:142
@ FORWARD
Standard editing scheme: rotation and / or translation of one bone.
Definition Skeleton.hpp:36
@ PSEUDO_IK
Advanced editing scheme: translation of a bone means parent's rotation.
Definition Skeleton.hpp:37
void setTransform(const uint i, const Transform &T, const SpaceType MODE) override
Definition Skeleton.cpp:63
AdjacencyList m_graph
The Joint hierarchy.
Definition Skeleton.hpp:112
void getBonePoints(uint i, Vector3 &startOut, Vector3 &endOut) const
Definition Skeleton.cpp:167
uint addBone(const uint parent, const Transform &T=Transform::Identity(), const SpaceType MODE=SpaceType::LOCAL, const Label label="")
Definition Skeleton.cpp:149
Manipulation m_manipulation
The manipulation scheme.
Definition Skeleton.hpp:115
const Pose & getPose(const SpaceType MODE) const override
Definition Skeleton.cpp:22
const Transform & getTransform(const uint i, const SpaceType MODE) const override
Definition Skeleton.cpp:55
T endl(T... args)
std::enable_if<!std::numeric_limits< T >::is_integer, bool >::type areApproxEqual(T x, T y, T espilonBoostFactor=T(10))
Compare two numbers such that |x-y| < espilon*epsilonBoostFactor.
Definition Math.hpp:42
This namespace contains everything "low level", related to data, datastuctures, and computation.
Definition Cage.cpp:5
T nanf(T... args)
T push_back(T... args)
T size(T... args)
T time(T... args)