Coverage Report

Created: 2026-01-07 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/PostProcessing/FindInstancesProcess.cpp
Line
Count
Source
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2025, assimp team
7
8
9
10
All rights reserved.
11
12
Redistribution and use of this software in source and binary forms,
13
with or without modification, are permitted provided that the following
14
conditions are met:
15
16
* Redistributions of source code must retain the above
17
  copyright notice, this list of conditions and the
18
  following disclaimer.
19
20
* Redistributions in binary form must reproduce the above
21
  copyright notice, this list of conditions and the
22
  following disclaimer in the documentation and/or other
23
  materials provided with the distribution.
24
25
* Neither the name of the assimp team, nor the names of its
26
  contributors may be used to endorse or promote products
27
  derived from this software without specific prior
28
  written permission of the assimp team.
29
30
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41
---------------------------------------------------------------------------
42
*/
43
44
/** @file  FindInstancesProcess.cpp
45
 *  @brief Implementation of the aiProcess_FindInstances postprocessing step
46
*/
47
48
49
#include "FindInstancesProcess.h"
50
#include <memory>
51
#include <stdio.h>
52
53
using namespace Assimp;
54
55
// ------------------------------------------------------------------------------------------------
56
// Constructor to be privately used by Importer
57
FindInstancesProcess::FindInstancesProcess()
58
383
:   configSpeedFlag (false)
59
383
{}
60
61
// ------------------------------------------------------------------------------------------------
62
// Returns whether the processing step is present in the given flag field.
63
bool FindInstancesProcess::IsActive( unsigned int pFlags) const
64
106
{
65
    // FindInstances makes absolutely no sense together with PreTransformVertices
66
    // fixme: spawn error message somewhere else?
67
106
    return 0 != (pFlags & aiProcess_FindInstances) && 0 == (pFlags & aiProcess_PreTransformVertices);
68
106
}
69
70
// ------------------------------------------------------------------------------------------------
71
// Setup properties for the step
72
void FindInstancesProcess::SetupProperties(const Importer* pImp)
73
0
{
74
    // AI_CONFIG_FAVOUR_SPEED
75
0
    configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
76
0
}
77
78
// ------------------------------------------------------------------------------------------------
79
// Compare the bones of two meshes
80
bool CompareBones(const aiMesh* orig, const aiMesh* inst)
81
0
{
82
0
    for (unsigned int i = 0; i < orig->mNumBones;++i) {
83
0
        aiBone* aha = orig->mBones[i];
84
0
        aiBone* oha = inst->mBones[i];
85
86
0
        if (aha->mNumWeights   != oha->mNumWeights   ||
87
0
            aha->mOffsetMatrix != oha->mOffsetMatrix) {
88
0
            return false;
89
0
        }
90
91
        // compare weight per weight ---
92
0
        for (unsigned int n = 0; n < aha->mNumWeights;++n) {
93
0
            if  (aha->mWeights[n].mVertexId != oha->mWeights[n].mVertexId ||
94
0
                (aha->mWeights[n].mWeight - oha->mWeights[n].mWeight) < 10e-3f) {
95
0
                return false;
96
0
            }
97
0
        }
98
0
    }
99
0
    return true;
100
0
}
101
102
// ------------------------------------------------------------------------------------------------
103
// Update mesh indices in the node graph
104
void UpdateMeshIndices(aiNode* node, unsigned int* lookup)
105
0
{
106
0
    for (unsigned int n = 0; n < node->mNumMeshes;++n)
107
0
        node->mMeshes[n] = lookup[node->mMeshes[n]];
108
109
0
    for (unsigned int n = 0; n < node->mNumChildren;++n)
110
0
        UpdateMeshIndices(node->mChildren[n],lookup);
111
0
}
112
113
// ------------------------------------------------------------------------------------------------
114
// Executes the post processing step on the given imported data.
115
void FindInstancesProcess::Execute( aiScene* pScene)
116
0
{
117
0
    ASSIMP_LOG_DEBUG("FindInstancesProcess begin");
118
0
    if (pScene->mNumMeshes) {
119
120
        // use a pseudo hash for all meshes in the scene to quickly find
121
        // the ones which are possibly equal. This step is executed early
122
        // in the pipeline, so we could, depending on the file format,
123
        // have several thousand small meshes. That's too much for a brute
124
        // everyone-against-everyone check involving up to 10 comparisons
125
        // each.
126
0
        std::unique_ptr<uint64_t[]> hashes (new uint64_t[pScene->mNumMeshes]);
127
0
        std::unique_ptr<unsigned int[]> remapping (new unsigned int[pScene->mNumMeshes]);
128
129
0
        unsigned int numMeshesOut = 0;
130
0
        for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
131
132
0
            aiMesh* inst = pScene->mMeshes[i];
133
0
            hashes[i] = GetMeshHash(inst);
134
135
            // Find an appropriate epsilon
136
            // to compare position differences against
137
0
            float epsilon = ComputePositionEpsilon(inst);
138
0
            epsilon *= epsilon;
139
140
0
            for (int a = i-1; a >= 0; --a) {
141
0
                if (hashes[i] == hashes[a])
142
0
                {
143
0
                    aiMesh* orig = pScene->mMeshes[a];
144
0
                    if (!orig)
145
0
                        continue;
146
147
                    // check for hash collision .. we needn't check
148
                    // the vertex format, it *must* match due to the
149
                    // (brilliant) construction of the hash
150
0
                    if (orig->mNumBones       != inst->mNumBones      ||
151
0
                        orig->mNumFaces       != inst->mNumFaces      ||
152
0
                        orig->mNumVertices    != inst->mNumVertices   ||
153
0
                        orig->mMaterialIndex  != inst->mMaterialIndex ||
154
0
                        orig->mPrimitiveTypes != inst->mPrimitiveTypes)
155
0
                        continue;
156
157
                    // up to now the meshes are equal. Now compare vertex positions, normals,
158
                    // tangents and bitangents using this epsilon.
159
0
                    if (orig->HasPositions()) {
160
0
                        if(!CompareArrays(orig->mVertices,inst->mVertices,orig->mNumVertices,epsilon))
161
0
                            continue;
162
0
                    }
163
0
                    if (orig->HasNormals()) {
164
0
                        if(!CompareArrays(orig->mNormals,inst->mNormals,orig->mNumVertices,epsilon))
165
0
                            continue;
166
0
                    }
167
0
                    if (orig->HasTangentsAndBitangents()) {
168
0
                        if (!CompareArrays(orig->mTangents,inst->mTangents,orig->mNumVertices,epsilon) ||
169
0
                            !CompareArrays(orig->mBitangents,inst->mBitangents,orig->mNumVertices,epsilon))
170
0
                            continue;
171
0
                    }
172
173
                    // use a constant epsilon for colors and UV coordinates
174
0
                    static const float uvEpsilon = 10e-4f;
175
0
                    {
176
0
                        unsigned int j, end = orig->GetNumUVChannels();
177
0
                        for(j = 0; j < end; ++j) {
178
0
                            if (!orig->mTextureCoords[j]) {
179
0
                                continue;
180
0
                            }
181
0
                            if(!CompareArrays(orig->mTextureCoords[j],inst->mTextureCoords[j],orig->mNumVertices,uvEpsilon)) {
182
0
                                break;
183
0
                            }
184
0
                        }
185
0
                        if (j != end) {
186
0
                            continue;
187
0
                        }
188
0
                    }
189
0
                    {
190
0
                        unsigned int j, end = orig->GetNumColorChannels();
191
0
                        for(j = 0; j < end; ++j) {
192
0
                            if (!orig->mColors[j]) {
193
0
                                continue;
194
0
                            }
195
0
                            if(!CompareArrays(orig->mColors[j],inst->mColors[j],orig->mNumVertices,uvEpsilon)) {
196
0
                                break;
197
0
                            }
198
0
                        }
199
0
                        if (j != end) {
200
0
                            continue;
201
0
                        }
202
0
                    }
203
204
                    // These two checks are actually quite expensive and almost *never* required.
205
                    // Almost. That's why they're still here. But there's no reason to do them
206
                    // in speed-targeted imports.
207
0
                    if (!configSpeedFlag) {
208
209
                        // It seems to be strange, but we really need to check whether the
210
                        // bones are identical too. Although it's extremely unprobable
211
                        // that they're not if control reaches here, we need to deal
212
                        // with unprobable cases, too. It could still be that there are
213
                        // equal shapes which are deformed differently.
214
0
                        if (!CompareBones(orig,inst))
215
0
                            continue;
216
217
                        // For completeness ... compare even the index buffers for equality
218
                        // face order & winding order doesn't care. Input data is in verbose format.
219
0
                        std::unique_ptr<unsigned int[]> ftbl_orig(new unsigned int[orig->mNumVertices]);
220
0
                        std::unique_ptr<unsigned int[]> ftbl_inst(new unsigned int[orig->mNumVertices]);
221
222
0
                        for (unsigned int tt = 0; tt < orig->mNumFaces;++tt) {
223
0
                            aiFace& f = orig->mFaces[tt];
224
0
                            for (unsigned int nn = 0; nn < f.mNumIndices;++nn)
225
0
                                ftbl_orig[f.mIndices[nn]] = tt;
226
227
0
                            aiFace& f2 = inst->mFaces[tt];
228
0
                            for (unsigned int nn = 0; nn < f2.mNumIndices;++nn)
229
0
                                ftbl_inst[f2.mIndices[nn]] = tt;
230
0
                        }
231
0
                        if (0 != ::memcmp(ftbl_inst.get(),ftbl_orig.get(),orig->mNumVertices*sizeof(unsigned int)))
232
0
                            continue;
233
0
                    }
234
235
                    // We're still here. Or in other words: 'inst' is an instance of 'orig'.
236
                    // Place a marker in our list that we can easily update mesh indices.
237
0
                    remapping[i] = remapping[a];
238
239
                    // Delete the instanced mesh, we don't need it anymore
240
0
                    delete inst;
241
0
                    pScene->mMeshes[i] = nullptr;
242
0
                    break;
243
0
                }
244
0
            }
245
246
            // If we didn't find a match for the current mesh: keep it
247
0
            if (pScene->mMeshes[i]) {
248
0
                remapping[i] = numMeshesOut++;
249
0
            }
250
0
        }
251
0
        ai_assert(0 != numMeshesOut);
252
0
        if (numMeshesOut != pScene->mNumMeshes) {
253
254
            // Collapse the meshes array by removing all nullptr entries
255
0
            for (unsigned int real = 0, i = 0; real < numMeshesOut; ++i) {
256
0
                if (pScene->mMeshes[i])
257
0
                    pScene->mMeshes[real++] = pScene->mMeshes[i];
258
0
            }
259
260
            // And update the node graph with our nice lookup table
261
0
            UpdateMeshIndices(pScene->mRootNode,remapping.get());
262
263
            // write to log
264
0
            if (!DefaultLogger::isNullLogger()) {
265
0
                ASSIMP_LOG_INFO( "FindInstancesProcess finished. Found ", (pScene->mNumMeshes - numMeshesOut), " instances" );
266
0
            }
267
0
            pScene->mNumMeshes = numMeshesOut;
268
0
        } else {
269
            ASSIMP_LOG_DEBUG("FindInstancesProcess finished. No instanced meshes found");
270
0
        }
271
0
    }
272
0
}