Loading [MathJax]/extensions/TeX/AMSmath.js
Radium Engine  1.5.24
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
DualQuaternionSkinning.cpp
1#include <Core/Animation/DualQuaternionSkinning.hpp>
2#include <Core/Animation/HandleArray.hpp>
3#include <Core/Animation/Skeleton.hpp>
4#include <Core/Animation/SkinningData.hpp>
5#include <Core/CoreMacros.hpp>
6#include <Core/Geometry/IndexedGeometry.hpp>
7#include <Core/Math/DualQuaternion.hpp>
8#include <Core/Math/Math.hpp>
9#include <Core/Types.hpp>
10#include <Eigen/Core>
11#include <Eigen/Geometry>
12#include <Eigen/SparseCore>
13#include <algorithm>
14#include <limits>
15#include <memory>
16#include <vector>
17
18namespace Ra {
19namespace Core {
20namespace Animation {
21
22DQList computeDQ( const Pose& pose, const Sparse& weight ) {
23 CORE_ASSERT( ( pose.size() == size_t( weight.cols() ) ), "pose/weight size mismatch." );
24 DQList DQ( weight.rows(),
25 DualQuaternion( Quaternion( 0, 0, 0, 0 ), Quaternion( 0, 0, 0, 0 ) ) );
26
27 // Stores the first non-zero quaternion for each vertex.
28 std::vector<uint> firstNonZero( weight.rows(), std::numeric_limits<uint>::max() );
29 // Contains the converted dual quaternions from the pose
30 std::vector<DualQuaternion> poseDQ( pose.size() );
31
32 // Loop through all transforms Tj
33 for ( int j = 0; j < weight.outerSize(); ++j ) {
34 poseDQ[j] = DualQuaternion( pose[j] );
35 // Count how many vertices are influenced by the given transform
36 const int nonZero = weight.col( j ).nonZeros();
37
38 Sparse::InnerIterator it0( weight, j );
39#pragma omp parallel for
40 // This for loop is here just because OpenMP wants classic for loops.
41 // Since we cannot iterate directly through the non-zero elements using the InnerIterator,
42 // we initialize an InnerIterator to the first element and then we increase it nz times.
43 /*
44 * This crappy piece of code was done in order to avoid the critical section
45 * DQ[i] += wq;
46 *
47 * that was occurring when parallelizing the main for loop.
48 *
49 * NOTE: this could be definitely improved by using std::thread
50 */
51 // Loop through all vertices vi who depend on Tj
52
53 for ( int nz = 0; nz < nonZero; ++nz ) {
54 Sparse::InnerIterator itn = it0 + Eigen::Index( nz );
55 const uint i = itn.row();
56 const Scalar w = itn.value();
57
58 firstNonZero[i] = std::min( firstNonZero[i], uint( j ) );
59 const Scalar sign =
60 Ra::Core::Math::signNZ( poseDQ[j].getQ0().dot( poseDQ[firstNonZero[i]].getQ0() ) );
61
62 const auto wq = poseDQ[j] * w * sign;
63 DQ[i] += wq;
64 }
65 }
66
67 // Normalize all dual quats.
68#pragma omp parallel for
69 for ( int i = 0; i < int( DQ.size() ); ++i ) {
70 DQ[i].normalize();
71 }
72
73 return DQ;
74}
75
76// alternate naive version, for reference purposes.
77// See Kavan , Collins, Zara and O'Sullivan, 2008
78DQList computeDQ_naive( const Pose& pose, const Sparse& weight ) {
79 CORE_ASSERT( ( pose.size() == size_t( weight.cols() ) ), "pose/weight size mismatch." );
80 DQList DQ( weight.rows(),
81 DualQuaternion( Quaternion( 0, 0, 0, 0 ), Quaternion( 0, 0, 0, 0 ) ) );
82 std::vector<DualQuaternion> poseDQ( pose.size() );
83
84 // 1. Convert all transforms to DQ
85#pragma omp parallel for
86 for ( int j = 0; j < weight.cols(); ++j ) {
87 poseDQ[j] = DualQuaternion( pose[j] );
88 }
89
90 // 2. for all vertices, blend the dual quats.
91 for ( int i = 0; i < weight.rows(); ++i ) {
92 int firstNonZero = -1;
93 for ( uint j = 0; j < weight.cols(); ++j ) {
94 const Scalar& w = weight.coeff( i, j );
95 if ( w == 0 ) { continue; }
96
97 if ( firstNonZero < 0 ) { firstNonZero = j; }
98
99 // Flip the dual quaternion sign according to the dot product with the first non-null
100 // dual quat see Algorithm 2 in section 4.1 of the paper.
101 Scalar sign =
102 Ra::Core::Math::signNZ( poseDQ[j].getQ0().dot( poseDQ[firstNonZero].getQ0() ) );
103
104 DQ[i] += sign * w * poseDQ[j];
105 }
106 }
107
108 // 3. renormalize all dual quats.
109#pragma omp parallel for
110 for ( int i = 0; i < int( DQ.size() ); ++i ) {
111 DQ[i].normalize();
112 }
113
114 return DQ;
115}
116
117Vector3Array applyDualQuaternions( const DQList& DQ, const Vector3Array& vertices ) {
118 Vector3Array out( vertices.size(), Vector3::Zero() );
119#pragma omp parallel for
120 for ( int i = 0; i < int( vertices.size() ); ++i ) {
121 out[i] = DQ[i].transform( vertices[i] );
122 }
123 return out;
124}
125
126void dualQuaternionSkinning( const SkinningRefData& refData,
127 const Vector3Array& tangents,
128 const Vector3Array& bitangents,
129 SkinningFrameData& frameData ) {
130 // prepare the pose w.r.t. the bind matrices and the mesh tranform
131 auto pose = frameData.m_skeleton.getPose( HandleArray::SpaceType::MODEL );
132#pragma omp parallel for
133 for ( int i = 0; i < int( frameData.m_skeleton.size() ); ++i ) {
134 pose[i] = refData.m_meshTransformInverse * pose[i] * refData.m_bindMatrices[i];
135 }
136 // compute the dual quaternion for each vertex
137 const auto DQ = computeDQ( pose, refData.m_weights );
138 // apply DQS
139 const auto& vertices = refData.m_referenceMesh.vertices();
140 const auto& normals = refData.m_referenceMesh.normals();
141#pragma omp parallel for
142 for ( int i = 0; i < int( frameData.m_currentPosition.size() ); ++i ) {
143 const auto& DQi = DQ[i];
144 frameData.m_currentPosition[i] = DQi.transform( vertices[i] );
145 frameData.m_currentNormal[i] = DQi.rotate( normals[i] );
146 frameData.m_currentTangent[i] = DQi.rotate( tangents[i] );
147 frameData.m_currentBitangent[i] = DQi.rotate( bitangents[i] );
148 }
149}
150} // namespace Animation
151} // namespace Core
152} // namespace Ra
T min(T... args)
constexpr T signNZ(const T &val)
Definition Math.hpp:114
constexpr int sign(const T &val)
Returns the sign of any numeric type as { -1, 0, 1}.
Definition Math.hpp:106
hepler function to manage enum as underlying types in VariableSet
Definition Cage.cpp:4