/src/assimp/code/PostProcessing/DeboneProcess.cpp
Line | Count | Source |
1 | | /* |
2 | | Open Asset Import Library (assimp) |
3 | | ---------------------------------------------------------------------- |
4 | | |
5 | | Copyright (c) 2006-2026, assimp team |
6 | | |
7 | | All rights reserved. |
8 | | |
9 | | Redistribution and use of this software in source and binary forms, |
10 | | with or without modification, are permitted provided that the |
11 | | following conditions are met: |
12 | | |
13 | | * Redistributions of source code must retain the above |
14 | | copyright notice, this list of conditions and the |
15 | | following disclaimer. |
16 | | |
17 | | * Redistributions in binary form must reproduce the above |
18 | | copyright notice, this list of conditions and the |
19 | | following disclaimer in the documentation and/or other |
20 | | materials provided with the distribution. |
21 | | |
22 | | * Neither the name of the assimp team, nor the names of its |
23 | | contributors may be used to endorse or promote products |
24 | | derived from this software without specific prior |
25 | | written permission of the assimp team. |
26 | | |
27 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | | |
39 | | ---------------------------------------------------------------------- |
40 | | */ |
41 | | |
42 | | /// @file DeboneProcess.cpp |
43 | | /** Implementation of the DeboneProcess post processing step */ |
44 | | |
45 | | // internal headers of the post-processing framework |
46 | | #include "ProcessHelper.h" |
47 | | #include "DeboneProcess.h" |
48 | | #include <stdio.h> |
49 | | |
50 | | using namespace Assimp; |
51 | | |
52 | | // ------------------------------------------------------------------------------------------------ |
53 | | // Constructor to be privately used by Importer |
54 | 31.9k | DeboneProcess::DeboneProcess() : mNumBones(0), mNumBonesCanDoWithout(0), mThreshold(AI_DEBONE_THRESHOLD), mAllOrNone(false) {} |
55 | | |
56 | | // ------------------------------------------------------------------------------------------------ |
57 | | // Returns whether the processing step is present in the given flag field. |
58 | 9.32k | bool DeboneProcess::IsActive( unsigned int pFlags) const { |
59 | 9.32k | return (pFlags & aiProcess_Debone) != 0; |
60 | 9.32k | } |
61 | | |
62 | | // ------------------------------------------------------------------------------------------------ |
63 | | // Executes the post processing step on the given imported data. |
64 | 0 | void DeboneProcess::SetupProperties(const Importer* pImp) { |
65 | | // get the current value of the property |
66 | 0 | mAllOrNone = pImp->GetPropertyInteger(AI_CONFIG_PP_DB_ALL_OR_NONE,0)?true:false; |
67 | 0 | mThreshold = pImp->GetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD,AI_DEBONE_THRESHOLD); |
68 | 0 | } |
69 | | |
70 | | // ------------------------------------------------------------------------------------------------ |
71 | | // Executes the post processing step on the given imported data. |
72 | 0 | void DeboneProcess::Execute( aiScene* pScene) { |
73 | 0 | ASSIMP_LOG_DEBUG("DeboneProcess begin"); |
74 | |
|
75 | 0 | if(!pScene->mNumMeshes) { |
76 | 0 | return; |
77 | 0 | } |
78 | | |
79 | 0 | std::vector<bool> splitList(pScene->mNumMeshes); |
80 | 0 | for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { |
81 | 0 | splitList[a] = ConsiderMesh( pScene->mMeshes[a] ); |
82 | 0 | } |
83 | |
|
84 | 0 | int numSplits = 0; |
85 | |
|
86 | 0 | if(!!mNumBonesCanDoWithout && (!mAllOrNone||mNumBonesCanDoWithout==mNumBones)) { |
87 | 0 | for(unsigned int a = 0; a < pScene->mNumMeshes; a++) { |
88 | 0 | if(splitList[a]) { |
89 | 0 | ++numSplits; |
90 | 0 | } |
91 | 0 | } |
92 | 0 | } |
93 | |
|
94 | 0 | if(numSplits) { |
95 | | // we need to do something. Let's go. |
96 | | //mSubMeshIndices.clear(); // really needed? |
97 | 0 | mSubMeshIndices.resize(pScene->mNumMeshes); // because we're doing it here anyway |
98 | | |
99 | | // build a new array of meshes for the scene |
100 | 0 | std::vector<aiMesh*> meshes; |
101 | |
|
102 | 0 | for (unsigned int a=0;a<pScene->mNumMeshes; ++a) { |
103 | 0 | aiMesh* srcMesh = pScene->mMeshes[a]; |
104 | 0 | std::vector<std::pair<aiMesh*,const aiBone*> > newMeshes; |
105 | |
|
106 | 0 | if(splitList[a]) { |
107 | 0 | SplitMesh(srcMesh,newMeshes); |
108 | 0 | } |
109 | | |
110 | | // mesh was split |
111 | 0 | if(!newMeshes.empty()) { |
112 | 0 | unsigned int out = 0, in = srcMesh->mNumBones; |
113 | | |
114 | | // store new meshes and indices of the new meshes |
115 | 0 | for(unsigned int b=0;b<newMeshes.size();b++) { |
116 | 0 | const aiString *find = newMeshes[b].second ? &newMeshes[b].second->mName : nullptr; |
117 | |
|
118 | 0 | aiNode *theNode = find ? pScene->mRootNode->FindNode(*find) : nullptr; |
119 | 0 | std::pair<unsigned int,aiNode*> push_pair(static_cast<unsigned int>(meshes.size()),theNode); |
120 | |
|
121 | 0 | mSubMeshIndices[a].emplace_back(push_pair); |
122 | 0 | meshes.emplace_back(newMeshes[b].first); |
123 | |
|
124 | 0 | out+=newMeshes[b].first->mNumBones; |
125 | 0 | } |
126 | |
|
127 | 0 | if(!DefaultLogger::isNullLogger()) { |
128 | 0 | ASSIMP_LOG_INFO("Removed %u bones. Input bones:", in - out, ". Output bones: ", out); |
129 | 0 | } |
130 | | |
131 | | // and destroy the source mesh. It should be completely contained inside the new submeshes |
132 | 0 | delete srcMesh; |
133 | 0 | } else { |
134 | | // Mesh is kept unchanged - store it's new place in the mesh array |
135 | 0 | mSubMeshIndices[a].emplace_back(static_cast<unsigned int>(meshes.size()), (aiNode *)nullptr); |
136 | 0 | meshes.push_back(srcMesh); |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | | // rebuild the scene's mesh array |
141 | 0 | pScene->mNumMeshes = static_cast<unsigned int>(meshes.size()); |
142 | 0 | delete [] pScene->mMeshes; |
143 | 0 | pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; |
144 | 0 | std::copy( meshes.begin(), meshes.end(), pScene->mMeshes); |
145 | | |
146 | | // recurse through all nodes and translate the node's mesh indices to fit the new mesh array |
147 | 0 | UpdateNode( pScene->mRootNode); |
148 | 0 | } |
149 | |
|
150 | 0 | ASSIMP_LOG_DEBUG("DeboneProcess end"); |
151 | 0 | } |
152 | | |
153 | | // ------------------------------------------------------------------------------------------------ |
154 | | // Counts bones total/removable in a given mesh. |
155 | 0 | bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) { |
156 | 0 | if(!pMesh->HasBones()) { |
157 | 0 | return false; |
158 | 0 | } |
159 | | |
160 | 0 | bool split = false; |
161 | | |
162 | | //interstitial faces not permitted |
163 | 0 | bool isInterstitialRequired = false; |
164 | |
|
165 | 0 | std::vector<bool> isBoneNecessary(pMesh->mNumBones,false); |
166 | 0 | std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX); |
167 | |
|
168 | 0 | const unsigned int cUnowned = UINT_MAX; |
169 | 0 | const unsigned int cCoowned = UINT_MAX-1; |
170 | |
|
171 | 0 | for(unsigned int i=0;i<pMesh->mNumBones;i++) { |
172 | 0 | for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++) { |
173 | 0 | float w = pMesh->mBones[i]->mWeights[j].mWeight; |
174 | 0 | if (w == 0.0f) { |
175 | 0 | continue; |
176 | 0 | } |
177 | | |
178 | 0 | unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId; |
179 | 0 | if (w >= mThreshold) { |
180 | 0 | if (vertexBones[vid] != cUnowned) { |
181 | | //double entry |
182 | 0 | if(vertexBones[vid]==i) { |
183 | 0 | ASSIMP_LOG_WARN("Encountered double entry in bone weights"); |
184 | 0 | } else { |
185 | | //TODO: track attraction in order to break tie |
186 | 0 | vertexBones[vid] = cCoowned; |
187 | 0 | } |
188 | 0 | } else { |
189 | 0 | vertexBones[vid] = i; |
190 | 0 | } |
191 | 0 | } |
192 | |
|
193 | 0 | if(!isBoneNecessary[i]) { |
194 | 0 | isBoneNecessary[i] = w<mThreshold; |
195 | 0 | } |
196 | 0 | } |
197 | |
|
198 | 0 | if(!isBoneNecessary[i]) { |
199 | 0 | isInterstitialRequired = true; |
200 | 0 | } |
201 | 0 | } |
202 | |
|
203 | 0 | if(isInterstitialRequired) { |
204 | 0 | for(unsigned int i=0;i<pMesh->mNumFaces;i++) { |
205 | 0 | unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]]; |
206 | 0 | for (unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) { |
207 | 0 | unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]]; |
208 | |
|
209 | 0 | if (v != w) { |
210 | 0 | if(v<pMesh->mNumBones) { |
211 | 0 | isBoneNecessary[v] = true; |
212 | 0 | } |
213 | 0 | if (w<pMesh->mNumBones) { |
214 | 0 | isBoneNecessary[w] = true; |
215 | 0 | } |
216 | 0 | } |
217 | 0 | } |
218 | 0 | } |
219 | 0 | } |
220 | |
|
221 | 0 | for(unsigned int i=0;i<pMesh->mNumBones;i++) { |
222 | 0 | if(!isBoneNecessary[i]) { |
223 | 0 | mNumBonesCanDoWithout++; |
224 | 0 | split = true; |
225 | 0 | } |
226 | |
|
227 | 0 | mNumBones++; |
228 | 0 | } |
229 | 0 | return split; |
230 | 0 | } |
231 | | |
232 | | // ------------------------------------------------------------------------------------------------ |
233 | | // Splits the given mesh by bone count. |
234 | 0 | void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const { |
235 | | // same deal here as ConsiderMesh basically |
236 | |
|
237 | 0 | std::vector<bool> isBoneNecessary(pMesh->mNumBones,false); |
238 | 0 | std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX); |
239 | |
|
240 | 0 | const unsigned int cUnowned = UINT_MAX; |
241 | 0 | const unsigned int cCoowned = UINT_MAX-1; |
242 | |
|
243 | 0 | for(unsigned int i=0;i<pMesh->mNumBones;i++) { |
244 | 0 | for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++) { |
245 | 0 | float w = pMesh->mBones[i]->mWeights[j].mWeight; |
246 | |
|
247 | 0 | if(w==0.0f) { |
248 | 0 | continue; |
249 | 0 | } |
250 | | |
251 | 0 | unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId; |
252 | |
|
253 | 0 | if(w>=mThreshold) { |
254 | 0 | if(vertexBones[vid]!=cUnowned) { |
255 | 0 | if(vertexBones[vid]==i) //double entry |
256 | 0 | { |
257 | 0 | ASSIMP_LOG_WARN("Encountered double entry in bone weights"); |
258 | 0 | } |
259 | 0 | else //TODO: track attraction in order to break tie |
260 | 0 | { |
261 | 0 | vertexBones[vid] = cCoowned; |
262 | 0 | } |
263 | 0 | } |
264 | 0 | else vertexBones[vid] = i; |
265 | 0 | } |
266 | |
|
267 | 0 | if(!isBoneNecessary[i]) { |
268 | 0 | isBoneNecessary[i] = w<mThreshold; |
269 | 0 | } |
270 | 0 | } |
271 | 0 | } |
272 | |
|
273 | 0 | unsigned int nFacesUnowned = 0; |
274 | |
|
275 | 0 | std::vector<unsigned int> faceBones(pMesh->mNumFaces,UINT_MAX); |
276 | 0 | std::vector<unsigned int> facesPerBone(pMesh->mNumBones,0); |
277 | |
|
278 | 0 | for(unsigned int i=0;i<pMesh->mNumFaces;i++) { |
279 | 0 | unsigned int nInterstitial = 1; |
280 | |
|
281 | 0 | unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]]; |
282 | |
|
283 | 0 | for(unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) { |
284 | 0 | unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]]; |
285 | |
|
286 | 0 | if(v!=w) { |
287 | 0 | if(v<pMesh->mNumBones) isBoneNecessary[v] = true; |
288 | 0 | if(w<pMesh->mNumBones) isBoneNecessary[w] = true; |
289 | 0 | } |
290 | 0 | else nInterstitial++; |
291 | 0 | } |
292 | |
|
293 | 0 | if(v<pMesh->mNumBones &&nInterstitial==pMesh->mFaces[i].mNumIndices) { |
294 | 0 | faceBones[i] = v; //primitive belongs to bone #v |
295 | 0 | facesPerBone[v]++; |
296 | 0 | } |
297 | 0 | else nFacesUnowned++; |
298 | 0 | } |
299 | | |
300 | | // invalidate any "cojoined" faces |
301 | 0 | for(unsigned int i=0;i<pMesh->mNumFaces;i++) { |
302 | 0 | if(faceBones[i]<pMesh->mNumBones&&isBoneNecessary[faceBones[i]]) |
303 | 0 | { |
304 | 0 | ai_assert(facesPerBone[faceBones[i]]>0); |
305 | 0 | facesPerBone[faceBones[i]]--; |
306 | |
|
307 | 0 | nFacesUnowned++; |
308 | 0 | faceBones[i] = cUnowned; |
309 | 0 | } |
310 | 0 | } |
311 | |
|
312 | 0 | if(nFacesUnowned) { |
313 | 0 | std::vector<unsigned int> subFaces; |
314 | |
|
315 | 0 | for(unsigned int i=0;i<pMesh->mNumFaces;i++) { |
316 | 0 | if(faceBones[i]==cUnowned) { |
317 | 0 | subFaces.push_back(i); |
318 | 0 | } |
319 | 0 | } |
320 | |
|
321 | 0 | aiMesh *baseMesh = MakeSubmesh(pMesh,subFaces,0); |
322 | 0 | std::pair<aiMesh *, const aiBone *> push_pair(baseMesh, (const aiBone *)nullptr); |
323 | |
|
324 | 0 | poNewMeshes.push_back(push_pair); |
325 | 0 | } |
326 | |
|
327 | 0 | for(unsigned int i=0;i<pMesh->mNumBones;i++) { |
328 | |
|
329 | 0 | if(!isBoneNecessary[i]&&facesPerBone[i]>0) { |
330 | 0 | std::vector<unsigned int> subFaces; |
331 | |
|
332 | 0 | for(unsigned int j=0;j<pMesh->mNumFaces;j++) { |
333 | 0 | if(faceBones[j]==i) { |
334 | 0 | subFaces.push_back(j); |
335 | 0 | } |
336 | 0 | } |
337 | |
|
338 | 0 | unsigned int f = AI_SUBMESH_FLAGS_SANS_BONES; |
339 | 0 | aiMesh *subMesh =MakeSubmesh(pMesh,subFaces,f); |
340 | | |
341 | | //Lifted from PretransformVertices.cpp |
342 | 0 | ApplyTransform(subMesh,pMesh->mBones[i]->mOffsetMatrix); |
343 | 0 | std::pair<aiMesh*,const aiBone*> push_pair(subMesh,pMesh->mBones[i]); |
344 | |
|
345 | 0 | poNewMeshes.push_back(push_pair); |
346 | 0 | } |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | // ------------------------------------------------------------------------------------------------ |
351 | | // Recursively updates the node's mesh list to account for the changed mesh list |
352 | 0 | void DeboneProcess::UpdateNode(aiNode* pNode) const { |
353 | | // rebuild the node's mesh index list |
354 | |
|
355 | 0 | std::vector<unsigned int> newMeshList; |
356 | | |
357 | | // this will require two passes |
358 | |
|
359 | 0 | unsigned int m = static_cast<unsigned int>(pNode->mNumMeshes), n = static_cast<unsigned int>(mSubMeshIndices.size()); |
360 | | |
361 | | // first pass, look for meshes which have not moved |
362 | 0 | for(unsigned int a=0;a<m;a++) { |
363 | 0 | unsigned int srcIndex = pNode->mMeshes[a]; |
364 | 0 | const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[srcIndex]; |
365 | 0 | unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size()); |
366 | |
|
367 | 0 | for(unsigned int b=0;b<nSubmeshes;b++) { |
368 | 0 | if(!subMeshes[b].second) { |
369 | 0 | newMeshList.push_back(subMeshes[b].first); |
370 | 0 | } |
371 | 0 | } |
372 | 0 | } |
373 | | |
374 | | // second pass, collect deboned meshes |
375 | |
|
376 | 0 | for(unsigned int a=0;a<n;a++) { |
377 | 0 | const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[a]; |
378 | 0 | unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size()); |
379 | |
|
380 | 0 | for(unsigned int b=0;b<nSubmeshes;b++) { |
381 | 0 | if(subMeshes[b].second == pNode) { |
382 | 0 | newMeshList.push_back(subMeshes[b].first); |
383 | 0 | } |
384 | 0 | } |
385 | 0 | } |
386 | |
|
387 | 0 | if( pNode->mNumMeshes > 0 ) { |
388 | 0 | delete[] pNode->mMeshes; |
389 | 0 | pNode->mMeshes = nullptr; |
390 | 0 | } |
391 | |
|
392 | 0 | pNode->mNumMeshes = static_cast<unsigned int>(newMeshList.size()); |
393 | |
|
394 | 0 | if(pNode->mNumMeshes) { |
395 | 0 | pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; |
396 | 0 | std::copy( newMeshList.begin(), newMeshList.end(), pNode->mMeshes); |
397 | 0 | } |
398 | | |
399 | | // do that also recursively for all children |
400 | 0 | for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) { |
401 | 0 | UpdateNode( pNode->mChildren[a]); |
402 | 0 | } |
403 | 0 | } |
404 | | |
405 | | // ------------------------------------------------------------------------------------------------ |
406 | | // Apply the node transformation to a mesh |
407 | 0 | void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const { |
408 | | // Check whether we need to transform the coordinates at all |
409 | 0 | if (!mat.IsIdentity()) { |
410 | |
|
411 | 0 | if (mesh->HasPositions()) { |
412 | 0 | for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { |
413 | 0 | mesh->mVertices[i] = mat * mesh->mVertices[i]; |
414 | 0 | } |
415 | 0 | } |
416 | 0 | if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) { |
417 | 0 | aiMatrix4x4 mWorldIT = mat; |
418 | 0 | mWorldIT.Inverse().Transpose(); |
419 | | |
420 | | // TODO: implement Inverse() for aiMatrix3x3 |
421 | 0 | aiMatrix3x3 m = aiMatrix3x3(mWorldIT); |
422 | |
|
423 | 0 | if (mesh->HasNormals()) { |
424 | 0 | for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { |
425 | 0 | mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize(); |
426 | 0 | } |
427 | 0 | } |
428 | 0 | if (mesh->HasTangentsAndBitangents()) { |
429 | 0 | for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { |
430 | 0 | mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize(); |
431 | 0 | mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize(); |
432 | 0 | } |
433 | 0 | } |
434 | 0 | } |
435 | 0 | } |
436 | 0 | } |