Loading [MathJax]/extensions/TeX/AMSmath.js
Radium Engine  1.5.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
RotationCenterSkinning.cpp
1 #include <Core/Animation/RotationCenterSkinning.hpp>
2 
3 #include <array>
4 #include <unordered_map>
5 
6 #include <Core/Animation/DualQuaternionSkinning.hpp>
7 #include <Core/Animation/HandleWeight.hpp>
8 #include <Core/Animation/Pose.hpp>
9 #include <Core/Animation/SkinningData.hpp>
10 #include <Core/Geometry/TopologicalMesh.hpp>
11 #include <Core/Utils/Log.hpp>
12 
13 namespace Ra {
14 namespace Core {
15 namespace Animation {
16 
17 using namespace Utils; // log
18 
19 Scalar weightSimilarity( const Eigen::SparseVector<Scalar>& v1w,
20  const Eigen::SparseVector<Scalar>& v2w,
21  Scalar sigma ) {
22  const Scalar sigmaSq = sigma * sigma;
23 
24  Scalar result = 0;
25  // Iterating over non zero coefficients
26  for ( Eigen::SparseVector<Scalar>::InnerIterator it1( v1w ); it1; ++it1 ) {
27  const uint j = it1.index();
28  const Scalar& W1j = it1.value(); // This one is necessarily non zero
29  const Scalar& W2j = v2w.coeff( it1.index() ); // This one may be 0.
30 
31  if ( W2j > 0 ) {
32  for ( Eigen::SparseVector<Scalar>::InnerIterator it2( v2w ); it2; ++it2 ) {
33  const uint k = it2.index();
34  const Scalar& W1k = v1w.coeff( it2.index() );
35  const Scalar& W2k = it2.value();
36  if ( j != k && W1k > 0 ) {
37  const Scalar diff =
38  std::exp( -Math::ipow<2>( ( W1j * W2k ) - ( W1k * W2j ) ) / ( sigmaSq ) );
39  result += W1j * W1k * W2j * W2k * diff;
40  }
41  }
42  }
43  }
44  return result;
45 }
46 
47 void computeCoR( SkinningRefData& dataInOut, Scalar sigma, Scalar weightEpsilon ) {
48  LOG( logDEBUG ) << "Precomputing CoRs";
49 
50  //
51  // First step : subdivide the original mesh until weights are sufficiently close enough.
52  //
53 
54  // convert the mesh to TopologicalMesh for easy processing.
55  Geometry::TriangleMesh triMesh;
56  triMesh.copy( dataInOut.m_referenceMesh );
57  Geometry::TopologicalMesh topoMesh( triMesh );
58 
59  // hashing function for Vector3
60  struct hash_vec {
61  size_t operator()( const Ra::Core::Vector3& lvalue ) const {
62  size_t hx = std::hash<Scalar>()( lvalue[0] );
63  size_t hy = std::hash<Scalar>()( lvalue[1] );
64  size_t hz = std::hash<Scalar>()( lvalue[2] );
65  return ( hx ^ ( hy << 1 ) ) ^ hz;
66  }
67  };
68 
69  // fill map from vertex position to handle index, used to access the weight
70  // matrix from initial mesh vertices
71  std::unordered_map<Ra::Core::Vector3, int, hash_vec> mapV2I;
72  for ( auto vit = topoMesh.vertices_begin(); vit != topoMesh.vertices_end(); ++vit ) {
73  mapV2I[topoMesh.point( *vit )] = vit->idx();
74  }
75 
76  // Squash weight matrix to fit TopologicalMesh (access through handle indices)
77  // Store the weights as row major here because we are going to query the per-vertex weights.
78  Eigen::SparseMatrix<Scalar, Eigen::RowMajor> subdivW;
79  const int numCols = dataInOut.m_weights.cols();
80  subdivW.resize( topoMesh.n_vertices(), numCols );
81  const auto& V = triMesh.vertices();
82  for ( std::size_t i = 0; i < V.size(); ++i ) {
83  subdivW.row( mapV2I[V[i]] ) = dataInOut.m_weights.row( int( i ) );
84  }
85 
86  // The mesh will be subdivided by repeated edge-split, so that adjacent vertices
87  // weights are distant of at most `weightEpsilon`.
88  // New vertices created by the edge splitting and their new weights are computed
89  // and appended to the existing vertices.
90  const Scalar wEps2 = weightEpsilon * weightEpsilon;
91  Scalar maxWeightDistance = 0;
92  do {
93  maxWeightDistance = 0;
94 
95  // Stores the edges to split
96  std::vector<Geometry::TopologicalMesh::EdgeHandle> edgesToSplit;
97 
98  // Compute all weights distances for all edges.
99  for ( auto e_it = topoMesh.edges_begin(); e_it != topoMesh.edges_end(); ++e_it ) {
100  const auto& he0 = topoMesh.halfedge_handle( *e_it, 0 );
101  const auto& he1 = topoMesh.halfedge_handle( *e_it, 1 );
102  int v0 = topoMesh.to_vertex_handle( he0 ).idx();
103  int v1 = topoMesh.to_vertex_handle( he1 ).idx();
104 
105  Scalar weightDistance = ( subdivW.row( v0 ) - subdivW.row( v1 ) ).squaredNorm();
106 
107  maxWeightDistance = std::max( maxWeightDistance, weightDistance );
108  if ( weightDistance > wEps2 ) { edgesToSplit.push_back( *e_it ); }
109  }
110  LOG( logDEBUG ) << "Max weight distance is " << sqrt( maxWeightDistance );
111 
112  // sort edges to split according to growing weightDistance to avoid
113  // creating edges larger than weightDistance
114  std::sort(
115  edgesToSplit.begin(),
116  edgesToSplit.end(),
117  [&topoMesh, &subdivW]( const auto& a, const auto& b ) {
118  const auto& a0 = topoMesh.to_vertex_handle( topoMesh.halfedge_handle( a, 0 ) );
119  const auto& a1 = topoMesh.to_vertex_handle( topoMesh.halfedge_handle( a, 1 ) );
120  Scalar la = ( subdivW.row( a0.idx() ) - subdivW.row( a1.idx() ) ).squaredNorm();
121  const auto& b0 = topoMesh.to_vertex_handle( topoMesh.halfedge_handle( b, 0 ) );
122  const auto& b1 = topoMesh.to_vertex_handle( topoMesh.halfedge_handle( b, 1 ) );
123  Scalar lb = ( subdivW.row( b0.idx() ) - subdivW.row( b1.idx() ) ).squaredNorm();
124  return ( la > lb );
125  } );
126 
127  // We found some edges over the limit, so we split them.
128  if ( !edgesToSplit.empty() ) {
129  LOG( logDEBUG ) << "Splitting " << edgesToSplit.size() << " edges";
130  int startIndex = subdivW.rows();
131 
132  Eigen::SparseMatrix<Scalar, Eigen::RowMajor> newWeights(
133  startIndex + edgesToSplit.size(), numCols );
134 
135  newWeights.topRows( startIndex ) = subdivW;
136  subdivW = newWeights;
137 
138  int i = 0;
139  // Split ALL the edges !
140  for ( const auto& edge : edgesToSplit ) {
141  int v0 = topoMesh.to_vertex_handle( topoMesh.halfedge_handle( edge, 0 ) ).idx();
142  int v1 = topoMesh.to_vertex_handle( topoMesh.halfedge_handle( edge, 1 ) ).idx();
143 
144  topoMesh.splitEdge( edge, 0.5f );
145 
146  subdivW.row( startIndex + i ) = 0.5f * ( subdivW.row( v0 ) + subdivW.row( v1 ) );
147  ++i;
148  }
149  }
150  } while ( maxWeightDistance > wEps2 );
151 
152  CORE_ASSERT( topoMesh.n_vertices() == size_t( subdivW.rows() ),
153  "Weights and vertices don't match" );
154 
155  //
156  // Second step : evaluate the integrals over all triangles for all vertices.
157  //
158 
159  // naive implementation : iterate over all the triangles (of the subdivided mesh)
160  // for all vertices (of the original mesh).
161  const uint nVerts = V.size();
162  dataInOut.m_CoR.clear();
163  dataInOut.m_CoR.resize( nVerts, Vector3::Zero() );
164 
165  // first precompute triangle data
166  std::map<Geometry::TopologicalMesh::FaceHandle,
167  std::tuple<Vector3, Scalar, Eigen::SparseVector<Scalar>>>
168  triangleData;
169  for ( auto f_it = topoMesh.faces_begin(); f_it != topoMesh.faces_end(); ++f_it ) {
170  // get needed data
171  const auto& he0 = topoMesh.halfedge_handle( *f_it );
172  const auto& he1 = topoMesh.next_halfedge_handle( he0 );
173  const auto& he2 = topoMesh.next_halfedge_handle( he1 );
174  const auto& v0 = topoMesh.to_vertex_handle( he0 );
175  const auto& v1 = topoMesh.to_vertex_handle( he1 );
176  const auto& v2 = topoMesh.to_vertex_handle( he2 );
177  const auto& p0 = topoMesh.point( v0 );
178  const auto& p1 = topoMesh.point( v1 );
179  const auto& p2 = topoMesh.point( v2 );
180  const Vector3 centroid = ( p0 + p1 + p2 ) / 3.f;
181  const Scalar area = ( ( ( p1 - p0 ).cross( p2 - p0 ) ).norm() * 0.5 );
182  const Eigen::SparseVector<Scalar> triWeight =
183  ( 1 / 3.f ) *
184  ( subdivW.row( v0.idx() ) + subdivW.row( v1.idx() ) + subdivW.row( v2.idx() ) );
185  triangleData[*f_it] = std::make_tuple( centroid, area, triWeight );
186  }
187 
188 #pragma omp parallel for
189  for ( int i = 0; i < int( nVerts ); ++i ) {
190  Vector3 cor( 0, 0, 0 );
191  Scalar sumweight = 0;
192  const Eigen::SparseVector<Scalar> Wi = subdivW.row( mapV2I[V[i]] );
193 
194  // Sum the cor and weights over all triangles of the subdivided mesh.
195  for ( auto f_it = topoMesh.faces_begin(); f_it != topoMesh.faces_end(); ++f_it ) {
196  const auto& triData = triangleData[*f_it];
197  const Vector3& centroid = std::get<0>( triData );
198  Scalar area = std::get<1>( triData );
199  const auto& triWeight = std::get<2>( triData );
200  const Scalar s = weightSimilarity( Wi, triWeight, sigma );
201  cor += s * area * centroid;
202  sumweight += s * area;
203  }
204 
205  // Avoid division by 0
206  if ( sumweight > 0 ) { dataInOut.m_CoR[i] = cor / sumweight; }
207 
208 #if defined CORE_DEBUG
209  if ( i % 100 == 0 ) { LOG( logDEBUG ) << "CoR: " << i << " / " << nVerts; }
210 #endif // CORE_DEBUG
211  }
212 }
213 
214 void centerOfRotationSkinning( const SkinningRefData& refData,
215  const Vector3Array& tangents,
216  const Vector3Array& bitangents,
217  SkinningFrameData& frameData ) {
218  CORE_ASSERT( refData.m_CoR.size() == frameData.m_currentPosition.size(),
219  "Invalid center of rotations" );
220 
221  const auto& W = refData.m_weights;
222  const auto& vertices = refData.m_referenceMesh.vertices();
223  const auto& normals = refData.m_referenceMesh.normals();
224  const auto& CoR = refData.m_CoR;
225  auto pose = frameData.m_skeleton.getPose( HandleArray::SpaceType::MODEL );
226 
227  // prepare the pose w.r.t. the bind matrices
228 #pragma omp parallel for
229  for ( int i = 0; i < int( frameData.m_skeleton.size() ); ++i ) {
230  pose[i] = refData.m_meshTransformInverse * pose[i] * refData.m_bindMatrices[i];
231  }
232  // Compute the dual quaternions
233  const auto DQ = computeDQ( pose, W );
234 
235  // Do LBS on the COR with weights of their associated vertices
236 #pragma omp parallel for
237  for ( int i = 0; i < int( frameData.m_currentPosition.size() ); ++i ) {
238  frameData.m_currentPosition[i] = Vector3::Zero();
239  }
240  for ( int k = 0; k < W.outerSize(); ++k ) {
241  const int nonZero = W.col( k ).nonZeros();
242  WeightMatrix::InnerIterator it0( W, k );
243 #pragma omp parallel for
244  for ( int nz = 0; nz < nonZero; ++nz ) {
245  WeightMatrix::InnerIterator it = it0 + Eigen::Index( nz );
246  const uint i = it.row();
247  const uint j = it.col();
248  const Scalar w = it.value();
249  frameData.m_currentPosition[i] += w * ( pose[j] * CoR[i] );
250  }
251  }
252 
253  // Compute final transformation
254 #pragma omp parallel for
255  for ( int i = 0; i < int( frameData.m_currentPosition.size() ); ++i ) {
256  frameData.m_currentPosition[i] += DQ[i].rotate( vertices[i] - CoR[i] );
257  frameData.m_currentNormal[i] = DQ[i].rotate( normals[i] );
258  frameData.m_currentTangent[i] = DQ[i].rotate( tangents[i] );
259  frameData.m_currentBitangent[i] = DQ[i].rotate( bitangents[i] );
260  }
261 }
262 
263 } // namespace Animation
264 } // namespace Core
265 } // namespace Ra
Definition: Cage.cpp:3