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