Radium Engine  1.5.20
Loading...
Searching...
No Matches
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
22using namespace Ra::Core;
23
25using Geometry::PolyMesh;
26using Geometry::QuadMesh;
27using Geometry::TriangleMesh;
28
29using namespace Animation;
31
32using namespace Utils;
33
34namespace Ra {
35namespace Engine {
36namespace Scene {
37
38static const std::string tangentName =
39 Ra::Core::Geometry::getAttribName( Ra::Core::Geometry::VERTEX_TANGENT );
40static const std::string bitangentName =
41 Ra::Core::Geometry::getAttribName( Ra::Core::Geometry::VERTEX_BITANGENT );
42
43TriangleMesh triangulate( const PolyMesh& polyMesh ) {
44 TriangleMesh res;
45 res.setVertices( polyMesh.vertices() );
46 res.setNormals( polyMesh.normals() );
47 res.copyAllAttributes( polyMesh );
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
71TriangleMesh triangulate( const QuadMesh& quadMesh ) {
72 TriangleMesh res;
73 res.setVertices( quadMesh.vertices() );
74 res.setNormals( quadMesh.normals() );
75 res.copyAllAttributes( quadMesh );
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
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 );
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->setDiffuseColor( Color::Skin() );
189 mat->setSpecularColor( Color::White() );
190
191 // assign texture
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
199 }
200}
201
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 ) {
251 m_frameData.m_currentTangent[i],
252 m_frameData.m_currentBitangent[i] );
253 }
254 }
255 }
256}
257
259 if ( m_frameData.m_doSkinning ) {
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
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
305void 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() );
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
334void 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
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
355 m_normalSkinning = normalSkinning;
356 if ( m_isReady ) { m_forceUpdate = true; }
357}
358
360 return m_meshName;
361}
362
364 return m_skelName;
365}
366
368 m_showingWeights = on;
369 auto ro = getRoMgr()->getRenderObject( *m_renderObjectReader() );
370 auto attrUV = Ra::Core::Geometry::getAttribName( Ra::Core::Geometry::VERTEX_TEXCOORD );
372
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
414 return m_showingWeights;
415}
416
418 m_weightType = type;
419 if ( m_showingWeights ) { showWeights( true ); }
420}
421
425
427 m_weightBone = bone;
428 if ( m_showingWeights ) { showWeights( true ); }
429}
430
431} // namespace Scene
432} // namespace Engine
433} // namespace Ra
T begin(T... args)
T bind(T... args)
Label getLabel(const uint i) const
uint size() const override
Definition Skeleton.hpp:40
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
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.
const NormalAttribHandle::Container & normals() const
Access the vertices normals.
void removeAttrib(Utils::AttribHandle< T > &h)
const PointAttribHandle::Container & vertices() const
Access the vertices positions.
Utils::AttribHandle< T > addAttrib(const std::string &name)
Utils::Attrib< T > & getAttrib(const Utils::AttribHandle< T > &h)
An attrib handle basically store an Index and a name.
Definition Attribs.hpp:146
This class implements ContainerIntrospectionInterface for AlignedStdVector.
virtual Entity * getEntity() const
Return the entity the component belongs to.
Definition Component.hpp:53
static Rendering::RenderObjectManager * getRoMgr()
Shortcut to access the render object manager.
Definition Component.cpp:37
void endSkinning()
Update internal data and update the skinned mesh.
void setWeightBone(uint bone)
Set the bone to show the weights of.
void handleSkinDataLoading(const Core::Asset::HandleData *data, const std::string &meshName)
SkinningType
The Geometric Skinning Method.
@ COR
Center of Rotation skinning.
WeightType getWeightsType()
Returns the type of skinning weights displayed.
const Core::Animation::SkinningRefData * getSkinningRefData() const
Returns the reference skinning data.
const Core::Animation::SkinningFrameData * getSkinningFrameData() const
Returns the current Pose data.
const std::string & getSkeletonName() const
Returns the name of the skeleton skinning the mesh.
void skin()
Apply the Skinning Method and update the SkinningFrameData.
@ STANDARD
Standard geometric skinning weights.
void setSkinningType(SkinningType type)
Sets the Skinning method to use.
void setNormalSkinning(NormalSkinning normalSkinning)
Sets the method to use to skin the normal, tangent and binormal vectors.
const std::string & getMeshName() const
Returns the name of the skinned mesh.
void showWeights(bool on)
Toggles display of skinning weights.
void showWeightsType(WeightType type)
Set the type of skinning weight to display.
virtual void initialize() override
Pure virtual method to be overridden by any component. When this method is called you are guaranteed ...
bool isShowingWeights()
Returns whether the skinning weights are displayed or not.
NormalSkinning
How to skin the normal, tangent and binormal vectors.
@ GEOMETRIC
Recompute from scratch from the skinned positions.
T emplace_back(T... args)
T end(T... args)
T find(T... args)
T move(T... args)
void getOrthogonalVectors(const Vector3 &fx, Eigen::Ref< Vector3 > fy, Eigen::Ref< Vector3 > fz)
This namespace contains everything "low level", related to data, datastuctures, and computation.
Definition Cage.cpp:4
hepler function to manage enum as underlying types in VariableSet
Definition Cage.cpp:3
T push_back(T... args)
bool m_doSkinning
Whether skinning must be processed for the current frame.
Vector3Array m_currentTangent
The current mesh vertex tangent vectors.
uint m_frameCounter
The number of the current frame.
Vector3Array m_currentBitangent
The current mesh vertex bitangent vectors.
Skeleton m_skeleton
The animation skeleton in the current pose.
Vector3Array m_currentNormal
The current mesh vertex normals.
Vector3Array m_currentPosition
The current mesh vertex position.
bool m_doReset
Whether the skin must be reset to its initial reference configuration.
Skinning data that get set at startup including the "reference state".
Transform m_meshTransformInverse
The inverse of the mesh's transform.
Vector3Array m_CoR
The optionnal centers of rotations for CoR skinning.
Skeleton m_skeleton
The animation skeleton saved in rest pose.
WeightMatrix m_weights
The matrix of skinning weights.
AlignedStdVector< Transform > m_bindMatrices
The per-bone bind matrices.
Geometry::TriangleMesh m_referenceMesh
The mesh in reference position.
Describes the sampler and image of a texture.
Definition Texture.hpp:99