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>
19#include <Core/Animation/DualQuaternionSkinning.hpp>
22#include <Core/Animation/PoseOperation.hpp>
23#include <Core/Animation/Skeleton.hpp>
25#include <Eigen/Geometry>
26#include <Eigen/SparseCore>
28#include <catch2/catch_test_macros.hpp>
34using namespace Ra::Core::Animation;
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;
41 WeightMatrix matrix1 { w, h };
42 matrix1.setIdentity();
44 SECTION(
"Test normalization" ) {
46 REQUIRE( !normalizeWeights( matrix1 ) );
48 matrix1.coeffRef( w / 3, h / 2 ) = 0.8_ra;
51 REQUIRE( normalizeWeights( matrix1 ) );
53 WeightMatrix matrix2 = matrix1;
56 WeightMatrix matrix3 = partitionOfUnity( matrix2 );
59 REQUIRE( normalizeWeights( matrix2 ) );
61 REQUIRE( !normalizeWeights( matrix3 ) );
63 REQUIRE( matrix1.isApprox( matrix2 ) );
65 REQUIRE( matrix1.isApprox( matrix3 ) );
67 matrix2.coeffRef( w / 3, h / 2 ) =
std::nanf(
"" );
69 REQUIRE( checkWeightMatrix( matrix1,
false ) );
71 REQUIRE( !checkWeightMatrix( matrix2,
false ) );
75TEST_CASE(
"Core/Animation/KeyFramedValue",
"[unittests][Core][Core/Animation][KeyFramedValue]" ) {
79 auto checkValues = [](
auto& p, Scalar
time, Scalar value ) {
84 auto checkSorting = [](
auto& lkf ) {
85 for (
size_t i = 1; i < lkf.size(); ++i ) {
86 REQUIRE( lkf[i - 1].first < lkf[i].first );
90 SECTION(
"Test keyframe manipulation" ) {
92 REQUIRE( kf.size() == 1 );
94 REQUIRE( !kf.removeKeyFrame( 0 ) );
96 checkValues( kf[0], 2_ra, 2_ra );
99 kf.insertKeyFrame( 0_ra, 0_ra );
101 REQUIRE( kf.size() == 2 );
105 checkValues( kf[0], 0_ra, 0_ra );
107 checkValues( kf[1], 2_ra, 2_ra );
110 kf.insertKeyFrame( 1_ra, 1_ra );
112 REQUIRE( kf.size() == 3 );
116 checkValues( kf[0], 0_ra, 0_ra );
118 checkValues( kf[1], 1_ra, 1_ra );
120 checkValues( kf[2], 2_ra, 2_ra );
123 kf.insertKeyFrame( 3_ra, 3_ra );
125 REQUIRE( kf.size() == 4 );
129 checkValues( kf[0], 0_ra, 0_ra );
131 checkValues( kf[1], 1_ra, 1_ra );
133 checkValues( kf[2], 2_ra, 2_ra );
135 checkValues( kf[3], 3_ra, 3_ra );
140 REQUIRE( kf2.size() == 4 );
143 REQUIRE( kf2.removeKeyFrame( 2 ) );
145 REQUIRE( kf2.size() == 3 );
149 checkValues( kf2[0], 0_ra, 0_ra );
151 checkValues( kf2[1], 1_ra, 1_ra );
153 checkValues( kf2[2], 3_ra, 3_ra );
156 REQUIRE( kf2.removeKeyFrame( 2 ) );
158 REQUIRE( kf2.size() == 2 );
162 checkValues( kf2[0], 0_ra, 0_ra );
164 checkValues( kf2[1], 1_ra, 1_ra );
167 REQUIRE( kf2.removeKeyFrame( 0 ) );
169 REQUIRE( kf2.size() == 1 );
171 checkValues( kf2[0], 1_ra, 1_ra );
174 REQUIRE( !kf2.removeKeyFrame( 0 ) );
178 kf.insertKeyFrame( 3_ra, 2_ra );
180 REQUIRE( kf.size() == 4 );
184 checkValues( kf[0], 0_ra, 0_ra );
186 checkValues( kf[1], 1_ra, 1_ra );
188 checkValues( kf[2], 2_ra, 2_ra );
190 checkValues( kf[3], 3_ra, 2_ra );
193 kf.moveKeyFrame( 1, -1_ra );
195 REQUIRE( kf.size() == 4 );
199 checkValues( kf[0], -1_ra, 1_ra );
201 checkValues( kf[1], 0_ra, 0_ra );
203 checkValues( kf[2], 2_ra, 2_ra );
205 checkValues( kf[3], 3_ra, 2_ra );
208 kf.moveKeyFrame( 1, 2.5_ra );
210 REQUIRE( kf.size() == 4 );
214 checkValues( kf[0], -1_ra, 1_ra );
216 checkValues( kf[1], 2_ra, 2_ra );
218 checkValues( kf[2], 2.5_ra, 0_ra );
220 checkValues( kf[3], 3_ra, 2_ra );
223 kf.moveKeyFrame( 1, 4_ra );
225 REQUIRE( kf.size() == 4 );
229 checkValues( kf[0], -1_ra, 1_ra );
231 checkValues( kf[1], 2.5_ra, 0_ra );
233 checkValues( kf[2], 3_ra, 2_ra );
235 checkValues( kf[3], 4_ra, 2_ra );
241 for (
size_t i = 0; i < kf.size(); ++i ) {
248 kf[kf.size() - 1].second ) );
255 kf.insertInterpolatedKeyFrame( -2_ra, linearInterpolate<Scalar> );
257 REQUIRE( kf.size() == 5 );
261 checkValues( kf[0], -2_ra, 1_ra );
263 checkValues( kf[1], -1_ra, 1_ra );
265 checkValues( kf[2], 2.5_ra, 0_ra );
267 checkValues( kf[3], 3_ra, 2_ra );
269 checkValues( kf[4], 4_ra, 2_ra );
272 kf.insertInterpolatedKeyFrame( 2.75_ra, linearInterpolate<Scalar> );
274 REQUIRE( kf.size() == 6 );
278 checkValues( kf[0], -2_ra, 1_ra );
280 checkValues( kf[1], -1_ra, 1_ra );
282 checkValues( kf[2], 2.5_ra, 0_ra );
284 checkValues( kf[3], 2.75_ra, 1_ra );
286 checkValues( kf[4], 3_ra, 2_ra );
288 checkValues( kf[5], 4_ra, 2_ra );
291 kf.insertInterpolatedKeyFrame( 5_ra, linearInterpolate<Scalar> );
293 REQUIRE( kf.size() == 7 );
297 checkValues( kf[0], -2_ra, 1_ra );
299 checkValues( kf[1], -1_ra, 1_ra );
301 checkValues( kf[2], 2.5_ra, 0_ra );
303 checkValues( kf[3], 2.75_ra, 1_ra );
305 checkValues( kf[4], 3_ra, 2_ra );
307 checkValues( kf[5], 4_ra, 2_ra );
309 checkValues( kf[6], 5_ra, 2_ra );
313TEST_CASE(
"Core/Animation/KeyFramedStruct",
"[unittests]" ) {
317 m_nonAnimatedData { 2_ra },
318 m_animatedData { 1_ra, 0_ra }
320 m_animatedData.insertKeyFrame( 3_ra, 2_ra );
323 Scalar fetch( Scalar time ) {
325 Scalar v_t = m_animatedData.at( time, Ra::Core::Animation::linearInterpolate<Scalar> );
327 return m_nonAnimatedData * v_t;
330 Scalar m_nonAnimatedData;
336 struct MyStructAnimator {
337 MyStructAnimator( MyStruct& s ) {
342 m_controller.m_value = frames;
343 m_controller.m_updater = [frames, &s](
const Scalar& t ) {
345 auto v_t = frames->at( t, Ra::Core::Animation::linearInterpolate<Scalar> );
347 s.m_nonAnimatedData = v_t;
351 void update( Scalar time ) {
352 m_controller.updateKeyFrame( time );
374 MyStructAnimator b( a );
410TEST_CASE(
"Core/Animation/Skeleton",
"[unittests][Core][Core/Animation][Skeleton]" ) {
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" );
421 SECTION(
"Test initialization" ) {
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 );
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 );
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 ) );
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 ) );
467 REQUIRE( a.isApprox( Vector3::Zero() ) );
468 REQUIRE( b.isApprox( Vector3::UnitX() ) );
470 REQUIRE( a.isApprox( Vector3::UnitX() ) );
471 REQUIRE( b.isApprox( 2 * Vector3::UnitX() ) );
473 REQUIRE( a.isApprox( 2 * Vector3::UnitX() ) );
474 REQUIRE( b.isApprox( 3 * Vector3::UnitX() ) );
476 REQUIRE( a.isApprox( 3 * Vector3::UnitX() ) );
477 REQUIRE( b.isApprox( 3 * Vector3::UnitX() ) );
480 SECTION(
"Test Forward Manipulation" ) {
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;
502 SECTION(
"using only Model Space" ) {
506 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
507 T.translation() = Vector3::UnitY();
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 } ) );
521 T1 = Transform::Identity();
522 T1.translation() = 2 * Vector3::UnitY();
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 } ) );
538 T2 = Transform::Identity();
539 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
540 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
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 } ) );
557 T3 = Transform::Identity();
558 T3.rotate( AngleAxis( Math::Pi, Vector3::UnitZ() ) );
559 T3.translation() = 2 * Vector3::UnitX();
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 } ) );
569 SECTION(
"using only Local Space" ) {
579 T = Transform::Identity();
580 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
581 T.translation() = Vector3::UnitY();
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 } ) );
605 localT1 = Transform::Identity();
606 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
607 localT1.translation() = Vector3::UnitX();
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 } ) );
624 localT2 = Transform::Identity();
625 localT2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
626 localT2.translation() = 2 * Vector3::UnitX();
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 } ) );
643 localT3 = Transform::Identity();
644 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
645 localT3.translation() = 2 * Vector3::UnitX();
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 } ) );
655 SECTION(
"using Both Spaces" ) {
659 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
660 T.translation() = Vector3::UnitY();
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 } ) );
674 localT1 = Transform::Identity();
675 localT1.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
676 localT1.translation() = Vector3::UnitX();
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 } ) );
694 T2 = Transform::Identity();
695 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
696 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
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 } ) );
712 localT3 = Transform::Identity();
713 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
714 localT3.translation() = 2 * Vector3::UnitX();
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 } ) );
725 SECTION(
"Test Pseudo-IK Manipulation" ) {
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;
748 SECTION(
"with Model Space" ) {
758 T = Transform::Identity();
759 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
760 T.translation() = Vector3::UnitY();
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 } ) );
785 T2 = Transform::Identity();
786 T2.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
787 T2.translation() = 2 * Vector3::UnitX() + 2 * Vector3::UnitY();
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 } ) );
809 T3 = Transform::Identity();
810 T3.rotate( AngleAxis( Math::Pi, Vector3::UnitZ() ) );
811 T3.translation() = 2 * Vector3::UnitX();
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 } ) );
821 SECTION(
"with Local Space" ) {
831 T = Transform::Identity();
832 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
833 T.translation() = Vector3::UnitY();
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 } ) );
859 localT2 = Transform::Identity();
860 localT2.rotate( AngleAxis( -Math::Pi, Vector3::UnitZ() ) );
861 localT2.translation() = -2 * Vector3::UnitY();
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 } ) );
888 localT3 = Transform::Identity();
889 localT3.rotate( AngleAxis( -Math::Pi / 2, Vector3::UnitZ() ) );
890 localT3.translation() = 2 * Vector3::UnitX();
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 } ) );
900 SECTION(
"with Both Spaces" ) {
910 T = Transform::Identity();
911 T.rotate( AngleAxis( Math::Pi / 2, Vector3::UnitZ() ) );
912 T.translation() = Vector3::UnitY();
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 } ) );
938 localT2 = Transform::Identity();
939 localT2.rotate( AngleAxis( -Math::Pi, Vector3::UnitZ() ) );
940 localT2.translation() = -2 * Vector3::UnitY();
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 } ) );
967 T3 = Transform::Identity();
968 T3.rotate( AngleAxis( Math::Pi, Vector3::UnitZ() ) );
969 T3.translation() = 2 * Vector3::UnitX();
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 } ) );
981TEST_CASE(
"Core/Animation/DualQuaternionSkinning",
982 "[unitests][Core][Core/Animation][DualQuaternionSkinning]" ) {
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 } ) );
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 } };
998 Ra::Core::Animation::WeightMatrix weights( 6, 2 );
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;
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;
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 );
1017 auto dq = Ra::Core::Animation::computeDQ( pose, weights );
1018 REQUIRE( q3.toRotationMatrix().isApprox( dq[2].getTransform().linear() ) );
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] ) );
1030 auto dq_n = Ra::Core::Animation::computeDQ( pose, weights );
1031 REQUIRE( q3.toRotationMatrix().isApprox( dq_n[2].getTransform().linear() ) );
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="")
@ FORWARD
Standard editing scheme: rotation and / or translation of one bone.
@ PSEUDO_IK
Advanced editing scheme: translation of a bone means parent's rotation.
void setTransform(const uint i, const Transform &T, const SpaceType MODE) override
AdjacencyList m_graph
The Joint hierarchy.
void getBonePoints(uint i, Vector3 &startOut, Vector3 &endOut) const
uint addBone(const uint parent, const Transform &T=Transform::Identity(), const SpaceType MODE=SpaceType::LOCAL, const Label label="")
Manipulation m_manipulation
The manipulation scheme.
const Pose & getPose(const SpaceType MODE) const override
const Transform & getTransform(const uint i, const SpaceType MODE) const override
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.
This namespace contains everything "low level", related to data, datastuctures, and computation.