Coverage Report

Created: 2025-06-22 07:30

/src/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2025, 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
#include "LimitBoneWeightsProcess.h"
40
#include <assimp/SmallVector.h>
41
#include <assimp/StringUtils.h>
42
#include <assimp/postprocess.h>
43
#include <assimp/DefaultLogger.hpp>
44
#include <assimp/scene.h>
45
#include <stdio.h>
46
47
namespace Assimp {
48
49
// Make sure this value is set.
50
#ifndef AI_LMW_MAX_WEIGHTS
51
#   define AI_LMW_MAX_WEIGHTS 16
52
#endif
53
54
// ------------------------------------------------------------------------------------------------
55
// Constructor to be privately used by Importer
56
LimitBoneWeightsProcess::LimitBoneWeightsProcess() :
57
1.77k
        mMaxWeights(AI_LMW_MAX_WEIGHTS), mRemoveEmptyBones(true) {
58
    // empty
59
1.77k
}
60
61
// ------------------------------------------------------------------------------------------------
62
// Returns whether the processing step is present in the given flag field.
63
435
bool LimitBoneWeightsProcess::IsActive( unsigned int pFlags) const {
64
435
    return (pFlags & aiProcess_LimitBoneWeights) != 0;
65
435
}
66
67
// ------------------------------------------------------------------------------------------------
68
// Executes the post processing step on the given imported data.
69
271
void LimitBoneWeightsProcess::Execute( aiScene* pScene) {
70
271
    ai_assert(pScene != nullptr);
71
72
271
    ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess begin");
73
74
5.81k
    for (unsigned int m = 0; m < pScene->mNumMeshes; ++m) {
75
5.54k
        ProcessMesh(pScene->mMeshes[m]);
76
5.54k
    }
77
78
271
    ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess end");
79
271
}
80
81
// ------------------------------------------------------------------------------------------------
82
// Executes the post processing step on the given imported data.
83
271
void LimitBoneWeightsProcess::SetupProperties(const Importer* pImp) {
84
271
    this->mMaxWeights = pImp->GetPropertyInteger(AI_CONFIG_PP_LBW_MAX_WEIGHTS,AI_LMW_MAX_WEIGHTS);
85
271
    this->mRemoveEmptyBones = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, 1) != 0;
86
271
}
87
88
// ------------------------------------------------------------------------------------------------
89
0
static unsigned int removeEmptyBones(aiMesh *pMesh) {
90
0
    ai_assert(pMesh != nullptr);
91
92
0
    unsigned int writeBone = 0;
93
0
    for (unsigned int readBone = 0; readBone< pMesh->mNumBones; ++readBone) {
94
0
        aiBone* bone = pMesh->mBones[readBone];
95
0
        if (bone->mNumWeights > 0) {
96
0
            pMesh->mBones[writeBone++] = bone;
97
0
        } else {
98
0
            delete bone;
99
0
        }
100
0
    }
101
102
0
    return writeBone;
103
0
}
104
105
// ------------------------------------------------------------------------------------------------
106
// Unites identical vertices in the given mesh
107
5.54k
void LimitBoneWeightsProcess::ProcessMesh(aiMesh* pMesh) {
108
5.54k
    if (!pMesh->HasBones())
109
5.47k
        return;
110
111
    // collect all bone weights per vertex
112
66
    typedef SmallVector<Weight,8> VertexWeightArray;
113
66
    typedef std::vector<VertexWeightArray> WeightsPerVertex;
114
66
    WeightsPerVertex vertexWeights(pMesh->mNumVertices);
115
66
    size_t maxVertexWeights = 0;
116
117
10.2k
    for (unsigned int b = 0; b < pMesh->mNumBones; ++b) {
118
10.1k
        const aiBone* bone = pMesh->mBones[b];
119
90.8k
        for (unsigned int w = 0; w < bone->mNumWeights; ++w) {
120
80.6k
            const aiVertexWeight& vw = bone->mWeights[w];
121
122
80.6k
            if (vertexWeights.size() <= vw.mVertexId)
123
80.3k
                continue;
124
125
374
            vertexWeights[vw.mVertexId].push_back(Weight(b, vw.mWeight));
126
374
            maxVertexWeights = std::max(maxVertexWeights, vertexWeights[vw.mVertexId].size());
127
374
        }
128
10.1k
    }
129
130
66
    if (maxVertexWeights <= mMaxWeights)
131
66
        return;
132
133
0
    unsigned int removed = 0, old_bones = pMesh->mNumBones;
134
135
    // now cut the weight count if it exceeds the maximum
136
0
    for (WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit) {
137
0
        if (vit->size() <= mMaxWeights)
138
0
            continue;
139
140
        // more than the defined maximum -> first sort by weight in descending order. That's
141
        // why we defined the < operator in such a weird way.
142
0
        std::sort(vit->begin(), vit->end());
143
144
        // now kill everything beyond the maximum count
145
0
        unsigned int m = static_cast<unsigned int>(vit->size());
146
0
        vit->resize(mMaxWeights);
147
0
        removed += static_cast<unsigned int>(m - vit->size());
148
149
        // and renormalize the weights
150
0
        float sum = 0.0f;
151
0
        for(const Weight* it = vit->begin(); it != vit->end(); ++it) {
152
0
            sum += it->mWeight;
153
0
        }
154
0
        if (0.0f != sum) {
155
0
            const float invSum = 1.0f / sum;
156
0
            for(Weight* it = vit->begin(); it != vit->end(); ++it) {
157
0
                it->mWeight *= invSum;
158
0
            }
159
0
        }
160
0
    }
161
162
    // clear weight count for all bone
163
0
    for (unsigned int a = 0; a < pMesh->mNumBones; ++a) {
164
0
        pMesh->mBones[a]->mNumWeights = 0;
165
0
    }
166
167
    // rebuild the vertex weight array for all bones
168
0
    for (unsigned int a = 0; a < vertexWeights.size(); ++a) {
169
0
        const VertexWeightArray& vw = vertexWeights[a];
170
0
        for (const Weight* it = vw.begin(); it != vw.end(); ++it) {
171
0
            aiBone* bone = pMesh->mBones[it->mBone];
172
0
            bone->mWeights[bone->mNumWeights++] = aiVertexWeight(a, it->mWeight);
173
0
        }
174
0
    }
175
176
    // remove empty bones
177
0
    if (mRemoveEmptyBones) {
178
0
        pMesh->mNumBones = removeEmptyBones(pMesh);
179
0
    }
180
181
0
    if (!DefaultLogger::isNullLogger()) {
182
0
        ASSIMP_LOG_INFO("Removed ", removed, " weights. Input bones: ", old_bones, ". Output bones: ", pMesh->mNumBones);
183
0
    }
184
0
}
185
186
} // namespace Assimp