Radium Engine  1.5.0
SkinningComponent.cpp
1 #include <Engine/Scene/SkinningComponent.hpp>
2 
3 #include <Core/Animation/PoseOperation.hpp>
4 
5 #include <Core/Animation/DualQuaternionSkinning.hpp>
6 #include <Core/Animation/HandleWeightOperation.hpp>
7 #include <Core/Animation/LinearBlendSkinning.hpp>
8 #include <Core/Animation/RotationCenterSkinning.hpp>
9 #include <Core/Geometry/DistanceQueries.hpp>
10 #include <Core/Utils/Color.hpp>
11 #include <Core/Utils/Log.hpp>
12 
13 #include <Engine/Data/BlinnPhongMaterial.hpp>
14 #include <Engine/Data/Mesh.hpp>
15 #include <Engine/Data/ShaderConfigFactory.hpp>
16 #include <Engine/Data/Texture.hpp>
17 #include <Engine/RadiumEngine.hpp>
18 #include <Engine/Rendering/RenderObject.hpp>
19 #include <Engine/Rendering/RenderObjectManager.hpp>
20 #include <Engine/Rendering/RenderTechnique.hpp>
21 
22 using namespace Ra::Core;
23 
25 using Geometry::PolyMesh;
26 using Geometry::QuadMesh;
27 using Geometry::TriangleMesh;
28 
29 using namespace Animation;
31 
32 using namespace Utils;
33 
34 namespace Ra {
35 namespace Engine {
36 namespace Scene {
37 
38 static const std::string tangentName =
39  Ra::Core::Geometry::getAttribName( Ra::Core::Geometry::VERTEX_TANGENT );
40 static const std::string bitangentName =
41  Ra::Core::Geometry::getAttribName( Ra::Core::Geometry::VERTEX_BITANGENT );
42 
43 TriangleMesh triangulate( const PolyMesh& polyMesh ) {
44  TriangleMesh res;
45  res.setVertices( polyMesh.vertices() );
46  res.setNormals( polyMesh.normals() );
47  res.copyAllAttributes( polyMesh );
48  VectorArray<Vector3ui> indices;
49  // using the same triangulation as in Ra::Engine::GeneralMesh::triangulate
50  for ( const auto& face : polyMesh.getIndices() ) {
51  if ( face.size() == 3 ) { indices.push_back( face ); }
52  else {
53  int minus { int( face.size() ) - 1 };
54  int plus { 0 };
55  while ( plus + 1 < minus ) {
56  if ( ( plus - minus ) % 2 ) {
57  indices.emplace_back( face[plus], face[plus + 1], face[minus] );
58  ++plus;
59  }
60  else {
61  indices.emplace_back( face[minus], face[plus], face[minus - 1] );
62  --minus;
63  }
64  }
65  }
66  }
67  res.setIndices( std::move( indices ) );
68  return res;
69 }
70 
71 TriangleMesh triangulate( const QuadMesh& quadMesh ) {
72  TriangleMesh res;
73  res.setVertices( quadMesh.vertices() );
74  res.setNormals( quadMesh.normals() );
75  res.copyAllAttributes( quadMesh );
76  VectorArray<Vector3ui> indices;
77  // using the same triangulation as in Ra::Engine::GeneralMesh::triangulate
78  for ( const auto& face : quadMesh.getIndices() ) {
79  indices.emplace_back( face[0], face[1], face[2] );
80  indices.emplace_back( face[0], face[2], face[3] );
81  }
82  res.setIndices( std::move( indices ) );
83  return res;
84 }
85 
86 void SkinningComponent::initialize() {
87  auto compMsg = ComponentMessenger::getInstance();
88  // get the current animation data.
89  bool hasSkel = compMsg->canGet<Skeleton>( getEntity(), m_skelName );
90  bool hasRefPose = compMsg->canGet<RefPose>( getEntity(), m_skelName );
91  bool hasTriMesh = compMsg->canGet<TriangleMesh>( getEntity(), m_meshName );
92  m_meshIsPoly = compMsg->canGet<PolyMesh>( getEntity(), m_meshName );
93  m_meshIsQuad = compMsg->canGet<QuadMesh>( getEntity(), m_meshName );
94 
95  if ( hasSkel && hasRefPose && ( hasTriMesh || m_meshIsPoly || m_meshIsQuad ) ) {
96  m_renderObjectReader = compMsg->getterCallback<Index>( getEntity(), m_meshName );
97  m_skeletonGetter = compMsg->getterCallback<Skeleton>( getEntity(), m_skelName );
98  if ( hasTriMesh ) {
99  m_triMeshWriter = compMsg->rwCallback<TriangleMesh>( getEntity(), m_meshName );
100  }
101  else if ( m_meshIsQuad ) {
102  m_quadMeshWriter = compMsg->rwCallback<QuadMesh>( getEntity(), m_meshName );
103  }
104  else { m_polyMeshWriter = compMsg->rwCallback<PolyMesh>( getEntity(), m_meshName ); }
105 
106  // copy mesh triangles and find duplicates for normal computation.
107  if ( hasTriMesh ) { m_refData.m_referenceMesh = *m_triMeshWriter(); }
108  else if ( m_meshIsQuad ) { m_refData.m_referenceMesh = triangulate( *m_quadMeshWriter() ); }
109  else { m_refData.m_referenceMesh = triangulate( *m_polyMeshWriter() ); }
111  if ( !m_refData.m_referenceMesh.hasAttrib( tangentName ) &&
112  !m_refData.m_referenceMesh.hasAttrib( bitangentName ) ) {
113  const auto& normals = m_refData.m_referenceMesh.normals();
114  Vector3Array tangents( normals.size() );
115  Vector3Array bitangents( normals.size() );
116 #pragma omp parallel for
117  for ( int i = 0; i < int( normals.size() ); ++i ) {
118  Core::Math::getOrthogonalVectors( normals[i], tangents[i], bitangents[i] );
119  }
120  m_refData.m_referenceMesh.addAttrib( tangentName, std::move( tangents ) );
121  m_refData.m_referenceMesh.addAttrib( bitangentName, std::move( bitangents ) );
122  }
123  else if ( !m_refData.m_referenceMesh.hasAttrib( tangentName ) ) {
124  const auto& normals = m_refData.m_referenceMesh.normals();
125  const auto& bH = m_refData.m_referenceMesh.getAttribHandle<Vector3>( bitangentName );
126  const auto& bitangents = m_refData.m_referenceMesh.getAttrib( bH ).data();
127  Vector3Array tangents( normals.size() );
128 #pragma omp parallel for
129  for ( int i = 0; i < int( normals.size() ); ++i ) {
130  tangents[i] = bitangents[i].cross( normals[i] );
131  }
132  m_refData.m_referenceMesh.addAttrib( tangentName, std::move( tangents ) );
133  }
134  else if ( !m_refData.m_referenceMesh.hasAttrib( bitangentName ) ) {
135  const auto& normals = m_refData.m_referenceMesh.normals();
136  const auto& tH = m_refData.m_referenceMesh.getAttribHandle<Vector3>( tangentName );
137  const auto& tangents = m_refData.m_referenceMesh.getAttrib( tH ).data();
138  Vector3Array bitangents( normals.size() );
139 #pragma omp parallel for
140  for ( int i = 0; i < int( normals.size() ); ++i ) {
141  bitangents[i] = normals[i].cross( tangents[i] );
142  }
143  m_refData.m_referenceMesh.addAttrib( bitangentName, std::move( bitangents ) );
144  }
145 
146  m_topoMesh = Ra::Core::Geometry::TopologicalMesh { m_refData.m_referenceMesh };
147 
148  auto ro = getRoMgr()->getRenderObject( *m_renderObjectReader() );
149  // get other data
150  m_refData.m_meshTransformInverse = ro->getLocalTransform().inverse();
151  m_refData.m_skeleton = *m_skeletonGetter();
152  createWeightMatrix();
153 
154  // initialize frame data
155  m_frameData.m_skeleton = m_refData.m_skeleton;
156  m_frameData.m_currentPosition = m_refData.m_referenceMesh.vertices();
157  m_frameData.m_currentNormal = m_refData.m_referenceMesh.normals();
158  const auto& tH = m_refData.m_referenceMesh.getAttribHandle<Vector3>( tangentName );
159  m_frameData.m_currentTangent = m_refData.m_referenceMesh.getAttrib( tH ).data();
160  const auto& bH = m_refData.m_referenceMesh.getAttribHandle<Vector3>( bitangentName );
161  m_frameData.m_currentBitangent = m_refData.m_referenceMesh.getAttrib( bH ).data();
162  m_frameData.m_frameCounter = 0;
163  m_frameData.m_doSkinning = true;
164  m_frameData.m_doReset = false;
165 
166  // setup comp data
167  m_isReady = true;
168  m_forceUpdate = true;
169 
170  // prepare RO for skinning weights display
171  m_baseMaterial = ro->getMaterial();
172 
173  // prepare UV
174  auto attrUV = Ra::Core::Geometry::getAttribName( Ra::Core::Geometry::VERTEX_TEXCOORD );
175  AttribArrayGeometry* geom;
176  if ( hasTriMesh ) { geom = const_cast<TriangleMesh*>( m_triMeshWriter() ); }
177  else {
178  if ( m_meshIsPoly ) { geom = const_cast<PolyMesh*>( m_polyMeshWriter() ); }
179  else { geom = const_cast<QuadMesh*>( m_quadMeshWriter() ); }
180  }
181  if ( geom->hasAttrib( attrUV ) ) {
182  auto handle = geom->getAttribHandle<Vector3>( attrUV );
183  m_baseUV = geom->getAttrib( handle ).data();
184  }
185 
186  // prepare weights material
187  auto mat = new Data::BlinnPhongMaterial( "SkinningWeights_Mat" );
188  mat->m_kd = Color::Skin();
189  mat->m_ks = Color::White();
190 
191  // assign texture
192  Data::TextureParameters texParam;
193  texParam.name = "Engine:Skinning:weights";
194  mat->addTexture( Data::BlinnPhongMaterial::TextureSemantic::TEX_DIFFUSE, texParam );
195  m_weightMaterial.reset( mat );
196 
197  // compute default weights uv
198  showWeightsType( STANDARD );
199  }
200 }
201 
202 void SkinningComponent::skin() {
203  CORE_ASSERT( m_isReady, "Skinning is not setup" );
204 
205  const Skeleton* skel = m_skeletonGetter();
206 
207  bool reset = ComponentMessenger::getInstance()->get<bool>( getEntity(), m_skelName );
208 
209  // Reset the skin if it wasn't done before
210  if ( reset && !m_frameData.m_doReset ) {
211  m_frameData.m_doReset = true;
212  m_frameData.m_frameCounter = 0;
213  m_forceUpdate = true;
214  }
215  const auto prevPose = m_frameData.m_skeleton.getPose( SpaceType::MODEL );
216  m_frameData.m_skeleton = *skel;
217  auto currentPose = m_frameData.m_skeleton.getPose( SpaceType::MODEL );
218  if ( !areEqual( currentPose, prevPose ) || m_forceUpdate ) {
219  m_forceUpdate = false;
220  m_frameData.m_doSkinning = true;
221  m_frameData.m_frameCounter++;
222 
223  const auto tH = m_refData.m_referenceMesh.getAttribHandle<Vector3>( tangentName );
224  const Vector3Array& tangents = m_refData.m_referenceMesh.getAttrib( tH ).data();
225  const auto bH = m_refData.m_referenceMesh.getAttribHandle<Vector3>( bitangentName );
226  const Vector3Array& bitangents = m_refData.m_referenceMesh.getAttrib( bH ).data();
227 
228  switch ( m_skinningType ) {
229  case DQS: {
230  dualQuaternionSkinning( m_refData, tangents, bitangents, m_frameData );
231  break;
232  }
233  case COR: {
234  centerOfRotationSkinning( m_refData, tangents, bitangents, m_frameData );
235  break;
236  }
237  case LBS:
238  default: {
239  linearBlendSkinning( m_refData, tangents, bitangents, m_frameData );
240  break;
241  }
242  }
243 
244  if ( m_normalSkinning == GEOMETRIC ) {
245  m_topoMesh.updatePositions( m_frameData.m_currentPosition );
246  m_topoMesh.updateWedgeNormals();
247  m_topoMesh.updateTriangleMeshNormals( m_frameData.m_currentNormal );
248 #pragma omp parallel for
249  for ( int i = 0; i < int( m_frameData.m_currentNormal.size() ); ++i ) {
250  Core::Math::getOrthogonalVectors( m_frameData.m_currentNormal[i],
251  m_frameData.m_currentTangent[i],
252  m_frameData.m_currentBitangent[i] );
253  }
254  }
255  }
256 }
257 
258 void SkinningComponent::endSkinning() {
259  if ( m_frameData.m_doSkinning ) {
260  AttribArrayGeometry* geom;
261  if ( !m_meshIsPoly ) {
262  if ( !m_meshIsQuad ) { geom = const_cast<TriangleMesh*>( m_triMeshWriter() ); }
263  else { geom = const_cast<QuadMesh*>( m_quadMeshWriter() ); }
264  }
265  else { geom = const_cast<PolyMesh*>( m_polyMeshWriter() ); }
266 
267  geom->setVertices( m_frameData.m_currentPosition );
268  geom->setNormals( m_frameData.m_currentNormal );
269  auto handle = geom->getAttribHandle<Vector3>( tangentName );
270  if ( handle.idx().isValid() ) {
271  geom->getAttrib( handle ).setData( m_frameData.m_currentTangent );
272  }
273  handle = geom->getAttribHandle<Vector3>( bitangentName );
274  if ( handle.idx().isValid() ) {
275  geom->getAttrib( handle ).setData( m_frameData.m_currentBitangent );
276  }
277 
278  m_frameData.m_doReset = false;
279  m_frameData.m_doSkinning = false;
280  }
281 }
282 
283 void SkinningComponent::handleSkinDataLoading( const Asset::HandleData* data,
284  const std::string& meshName ) {
285  m_skelName = data->getName();
286  m_meshName = meshName;
287  setupIO( meshName );
288  for ( const auto& bone : data->getComponentData() ) {
289  auto it_w = bone.m_weights.find( meshName );
290  if ( it_w != bone.m_weights.end() ) {
291  m_loadedWeights[bone.m_name] = it_w->second;
292  auto it_b = bone.m_bindMatrices.find( meshName );
293  if ( it_b != bone.m_bindMatrices.end() ) {
294  m_loadedBindMatrices[bone.m_name] = it_b->second;
295  }
296  else {
297  LOG( logWARNING ) << "Bone " << bone.m_name
298  << " has skinning weights but no bind matrix. Using Identity.";
299  m_loadedBindMatrices[bone.m_name] = Transform::Identity();
300  }
301  }
302  }
303 }
304 
305 void SkinningComponent::createWeightMatrix() {
306  m_refData.m_bindMatrices.resize( m_refData.m_skeleton.size(), Transform::Identity() );
307  m_refData.m_weights.resize( int( m_refData.m_referenceMesh.vertices().size() ),
308  m_refData.m_skeleton.size() );
309  std::vector<Eigen::Triplet<Scalar>> triplets;
310  for ( uint col = 0; col < m_refData.m_skeleton.size(); ++col ) {
311  std::string boneName = m_refData.m_skeleton.getLabel( col );
312  auto it = m_loadedWeights.find( boneName );
313  if ( it != m_loadedWeights.end() ) {
314  const auto& W = it->second;
315  for ( uint i = 0; i < W.size(); ++i ) {
316  const auto& w = W[i];
317  int row { int( w.first ) };
318  CORE_ASSERT( row < m_refData.m_weights.rows(),
319  "Weights are incompatible with mesh." );
320  triplets.push_back( { row, int( col ), w.second } );
321  }
322  m_refData.m_bindMatrices[col] = m_loadedBindMatrices[boneName];
323  }
324  }
325  m_refData.m_weights.setFromTriplets( triplets.begin(), triplets.end() );
326 
327  checkWeightMatrix( m_refData.m_weights, false, true );
328 
329  if ( normalizeWeights( m_refData.m_weights, true ) ) {
330  LOG( logINFO ) << "Skinning weights have been normalized";
331  }
332 }
333 
334 void SkinningComponent::setupIO( const std::string& id ) {
335  auto compMsg = ComponentMessenger::getInstance();
336 
337  auto refData = std::bind( &SkinningComponent::getSkinningRefData, this );
338  compMsg->registerOutput<SkinningRefData>( getEntity(), this, id, refData );
339 
340  auto frameData = std::bind( &SkinningComponent::getSkinningFrameData, this );
341  compMsg->registerOutput<SkinningFrameData>( getEntity(), this, id, frameData );
342 }
343 
344 void SkinningComponent::setSkinningType( SkinningType type ) {
345  m_skinningType = type;
346  if ( m_isReady ) {
347  // compute the per-vertex center of rotation only if required.
348  // FIXME: takes time, would be nice to store them in a file and reload.
349  if ( m_skinningType == COR && m_refData.m_CoR.empty() ) { computeCoR( m_refData ); }
350  m_forceUpdate = true;
351  }
352 }
353 
354 void SkinningComponent::setNormalSkinning( NormalSkinning normalSkinning ) {
355  m_normalSkinning = normalSkinning;
356  if ( m_isReady ) { m_forceUpdate = true; }
357 }
358 
359 const std::string& SkinningComponent::getMeshName() const {
360  return m_meshName;
361 }
362 
363 const std::string& SkinningComponent::getSkeletonName() const {
364  return m_skelName;
365 }
366 
367 void SkinningComponent::showWeights( bool on ) {
368  m_showingWeights = on;
369  auto ro = getRoMgr()->getRenderObject( *m_renderObjectReader() );
370  auto attrUV = Ra::Core::Geometry::getAttribName( Ra::Core::Geometry::VERTEX_TEXCOORD );
371  AttribHandle<Vector3> handle;
372 
373  AttribArrayGeometry* geom;
374  if ( !m_meshIsPoly ) {
375  if ( !m_meshIsQuad ) { geom = const_cast<TriangleMesh*>( m_triMeshWriter() ); }
376  else { geom = const_cast<QuadMesh*>( m_quadMeshWriter() ); }
377  }
378  else { geom = const_cast<PolyMesh*>( m_polyMeshWriter() ); }
379 
380  if ( m_showingWeights ) {
381  // update the displayed weights
382  const auto size = m_frameData.m_currentPosition.size();
383  m_weightsUV.resize( size, Vector3::Zero() );
384  switch ( m_weightType ) {
385  case STANDARD:
386  default: {
387 #pragma omp parallel for
388  for ( int i = 0; i < int( size ); ++i ) {
389  m_weightsUV[i][0] = m_refData.m_weights.coeff( i, m_weightBone );
390  }
391  } break;
392  } // end of switch.
393  // change the material
394  ro->setMaterial( m_weightMaterial );
395  // TODO : as this considers renderer is ForwardRenderer, find how to generalize.
396  ro->getRenderTechnique()->setParametersProvider( m_weightMaterial );
397  // get the UV attrib handle, will create it if not there.
398  handle = geom->addAttrib<Vector3>( attrUV );
399  geom->getAttrib( handle ).setData( m_weightsUV );
400  }
401  else {
402  // change the material
403  ro->setMaterial( m_baseMaterial );
404  ro->getRenderTechnique()->setParametersProvider( m_baseMaterial );
405  // if the UV attrib existed before, reset it, otherwise remove it.
406  handle = geom->getAttribHandle<Vector3>( attrUV );
407  if ( m_baseUV.size() > 0 ) { geom->getAttrib( handle ).setData( m_baseUV ); }
408  else { geom->removeAttrib( handle ); }
409  }
410  m_forceUpdate = true;
411 }
412 
413 bool SkinningComponent::isShowingWeights() {
414  return m_showingWeights;
415 }
416 
417 void SkinningComponent::showWeightsType( WeightType type ) {
418  m_weightType = type;
419  if ( m_showingWeights ) { showWeights( true ); }
420 }
421 
422 SkinningComponent::WeightType SkinningComponent::getWeightsType() {
423  return m_weightType;
424 }
425 
426 void SkinningComponent::setWeightBone( uint bone ) {
427  m_weightBone = bone;
428  if ( m_showingWeights ) { showWeights( true ); }
429 }
430 
431 } // namespace Scene
432 } // namespace Engine
433 } // namespace Ra
const Pose & getPose(const SpaceType MODE) const override
Definition: Skeleton.cpp:22
virtual const std::string & getName() const
Acces to the name of the asset.
Definition: AssetData.hpp:29
const Core::AlignedStdVector< HandleComponentData > & getComponentData() const
Definition: HandleData.hpp:300
This class represents vertex + attributes per vertex. Toplogy is handled in MultiIndexedGeometry subc...
Utils::AttribHandle< T > getAttribHandle(const std::string &name) const
void setNormals(PointAttribHandle::Container &&normals)
Set normals.
bool hasAttrib(const std::string &name) const
void setVertices(PointAttribHandle::Container &&vertices)
Set vertices.
void removeAttrib(Utils::AttribHandle< T > &h)
Utils::AttribHandle< T > addAttrib(const std::string &name)
Utils::Attrib< T > & getAttrib(const Utils::AttribHandle< T > &h)
This class implements ContainerIntrospectionInterface for AlignedStdVector.
Definition: VectorArray.hpp:35
SkinningType
The Geometric Skinning Method.
NormalSkinning
How to skin the normal, tangent and binormal vectors.
void getOrthogonalVectors(const Vector3 &fx, Eigen::Ref< Vector3 > fy, Eigen::Ref< Vector3 > fz)
Definition: Cage.cpp:3
Skinning data that get set at startup including the "reference state".
std::string name
Name of the texture.
Definition: Texture.hpp:44