Radium Engine  1.5.20
Loading...
Searching...
No Matches
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
13namespace Ra {
14namespace Core {
15namespace Animation {
16
17using namespace Utils; // log
18
19Scalar 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
47void 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
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
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,
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
214void 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
T begin(T... args)
T empty(T... args)
T end(T... args)
T exp(T... args)
T make_tuple(T... args)
T max(T... args)
T ipow(const T &x, uint exp)
Run-time exponent version.
Definition Math.hpp:64
hepler function to manage enum as underlying types in VariableSet
Definition Cage.cpp:3
T push_back(T... args)
T size(T... args)
T sort(T... args)
T sqrt(T... args)