/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 | } |