/src/assimp/code/PostProcessing/FindDegenerates.cpp
Line | Count | Source |
1 | | /* |
2 | | --------------------------------------------------------------------------- |
3 | | Open Asset Import Library (assimp) |
4 | | --------------------------------------------------------------------------- |
5 | | |
6 | | Copyright (c) 2006-2025, assimp team |
7 | | |
8 | | All rights reserved. |
9 | | |
10 | | Redistribution and use of this software in source and binary forms, |
11 | | with or without modification, are permitted provided that the following |
12 | | conditions are met: |
13 | | |
14 | | * Redistributions of source code must retain the above |
15 | | copyright notice, this list of conditions and the |
16 | | following disclaimer. |
17 | | |
18 | | * Redistributions in binary form must reproduce the above |
19 | | copyright notice, this list of conditions and the |
20 | | following disclaimer in the documentation and/or other |
21 | | materials provided with the distribution. |
22 | | |
23 | | * Neither the name of the assimp team, nor the names of its |
24 | | contributors may be used to endorse or promote products |
25 | | derived from this software without specific prior |
26 | | written permission of the assimp team. |
27 | | |
28 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
29 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
30 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
31 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
32 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
33 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
34 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
35 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
36 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
37 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
38 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
39 | | --------------------------------------------------------------------------- |
40 | | */ |
41 | | |
42 | | /** @file FindDegenerates.cpp |
43 | | * @brief Implementation of the FindDegenerates post-process step. |
44 | | */ |
45 | | |
46 | | #include "FindDegenerates.h" |
47 | | #include "Geometry/GeometryUtils.h" |
48 | | #include "ProcessHelper.h" |
49 | | |
50 | | #include <assimp/Exceptional.h> |
51 | | |
52 | | #include <unordered_map> |
53 | | |
54 | | using namespace Assimp; |
55 | | |
56 | | // Correct node indices to meshes and remove references to deleted mesh |
57 | | static void updateSceneGraph(aiNode *pNode, const std::unordered_map<unsigned int, unsigned int> &meshMap); |
58 | | |
59 | | // ------------------------------------------------------------------------------------------------ |
60 | | // Constructor to be privately used by Importer |
61 | | FindDegeneratesProcess::FindDegeneratesProcess() : |
62 | 383 | mConfigRemoveDegenerates(false), |
63 | 383 | mConfigCheckAreaOfTriangle(false) { |
64 | | // empty |
65 | 383 | } |
66 | | |
67 | | // ------------------------------------------------------------------------------------------------ |
68 | | // Returns whether the processing step is present in the given flag field. |
69 | 106 | bool FindDegeneratesProcess::IsActive(unsigned int pFlags) const { |
70 | 106 | return 0 != (pFlags & aiProcess_FindDegenerates); |
71 | 106 | } |
72 | | |
73 | | // ------------------------------------------------------------------------------------------------ |
74 | | // Setup import configuration |
75 | 69 | void FindDegeneratesProcess::SetupProperties(const Importer *pImp) { |
76 | | // Get the current value of AI_CONFIG_PP_FD_REMOVE |
77 | 69 | mConfigRemoveDegenerates = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_REMOVE, 0)); |
78 | 69 | mConfigCheckAreaOfTriangle = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_CHECKAREA)); |
79 | 69 | } |
80 | | |
81 | | // ------------------------------------------------------------------------------------------------ |
82 | | // Executes the post processing step on the given imported data. |
83 | 69 | void FindDegeneratesProcess::Execute(aiScene *pScene) { |
84 | 69 | ASSIMP_LOG_DEBUG("FindDegeneratesProcess begin"); |
85 | 69 | if (nullptr == pScene) { |
86 | 0 | return; |
87 | 0 | } |
88 | | |
89 | 69 | std::unordered_map<unsigned int, unsigned int> meshMap; |
90 | 69 | meshMap.reserve(pScene->mNumMeshes); |
91 | | |
92 | 69 | const unsigned int originalNumMeshes = pScene->mNumMeshes; |
93 | 69 | unsigned int targetIndex = 0; |
94 | 1.52k | for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { |
95 | | // Do not process point cloud, ExecuteOnMesh works only with faces data |
96 | 1.45k | if ((pScene->mMeshes[i]->mPrimitiveTypes != aiPrimitiveType::aiPrimitiveType_POINT) && ExecuteOnMesh(pScene->mMeshes[i])) { |
97 | 0 | delete pScene->mMeshes[i]; |
98 | | // Not strictly required, but clean: |
99 | 0 | pScene->mMeshes[i] = nullptr; |
100 | 1.45k | } else { |
101 | 1.45k | meshMap[i] = targetIndex; |
102 | 1.45k | pScene->mMeshes[targetIndex] = pScene->mMeshes[i]; |
103 | 1.45k | ++targetIndex; |
104 | 1.45k | } |
105 | 1.45k | } |
106 | 69 | pScene->mNumMeshes = targetIndex; |
107 | | |
108 | 69 | if (meshMap.size() < originalNumMeshes) { |
109 | 0 | updateSceneGraph(pScene->mRootNode, meshMap); |
110 | 0 | } |
111 | | |
112 | 69 | ASSIMP_LOG_DEBUG("FindDegeneratesProcess finished"); |
113 | 69 | } |
114 | | |
115 | 0 | static void updateSceneGraph(aiNode *pNode, const std::unordered_map<unsigned int, unsigned int> &meshMap) { |
116 | 0 | unsigned int targetIndex = 0; |
117 | 0 | for (unsigned i = 0; i < pNode->mNumMeshes; ++i) { |
118 | 0 | const unsigned int sourceMeshIndex = pNode->mMeshes[i]; |
119 | 0 | auto it = meshMap.find(sourceMeshIndex); |
120 | 0 | if (it != meshMap.end()) { |
121 | 0 | pNode->mMeshes[targetIndex] = it->second; |
122 | 0 | ++targetIndex; |
123 | 0 | } |
124 | 0 | } |
125 | 0 | pNode->mNumMeshes = targetIndex; |
126 | | // recurse to all children |
127 | 0 | for (unsigned i = 0; i < pNode->mNumChildren; ++i) { |
128 | 0 | updateSceneGraph(pNode->mChildren[i], meshMap); |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | | // ------------------------------------------------------------------------------------------------ |
133 | | // Executes the post processing step on the given imported mesh |
134 | 1.35k | bool FindDegeneratesProcess::ExecuteOnMesh(aiMesh *mesh) { |
135 | 1.35k | mesh->mPrimitiveTypes = 0; |
136 | | |
137 | 1.35k | std::vector<bool> remove_me; |
138 | 1.35k | if (mConfigRemoveDegenerates) { |
139 | 0 | remove_me.resize(mesh->mNumFaces, false); |
140 | 0 | } |
141 | | |
142 | 1.35k | unsigned int deg = 0, limit; |
143 | 54.1k | for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { |
144 | 52.7k | aiFace &face = mesh->mFaces[a]; |
145 | 52.7k | bool first = true; |
146 | 323k | auto vertex_in_range = [numVertices = mesh->mNumVertices](unsigned int vertex_idx) { return vertex_idx < numVertices; }; |
147 | | |
148 | | // check whether the face contains degenerated entries |
149 | 162k | for (unsigned int i = 0; i < face.mNumIndices; ++i) { |
150 | 109k | if (!std::all_of(face.mIndices, face.mIndices + face.mNumIndices, vertex_in_range)) |
151 | 0 | continue; |
152 | | |
153 | | // Polygons with more than 4 points are allowed to have double points, that is |
154 | | // simulating polygons with holes just with concave polygons. However, |
155 | | // double points may not come directly after another. |
156 | 109k | limit = face.mNumIndices; |
157 | 109k | if (face.mNumIndices > 4) { |
158 | 0 | limit = std::min(limit, i + 2); |
159 | 0 | } |
160 | | |
161 | 240k | for (unsigned int t = i + 1; t < limit; ++t) { |
162 | 131k | if (mesh->mVertices[face.mIndices[i]] == mesh->mVertices[face.mIndices[t]]) { |
163 | | // we have found a matching vertex position |
164 | | // remove the corresponding index from the array |
165 | 47.4k | --face.mNumIndices; |
166 | 47.4k | --limit; |
167 | 71.9k | for (unsigned int m = t; m < face.mNumIndices; ++m) { |
168 | 24.5k | face.mIndices[m] = face.mIndices[m + 1]; |
169 | 24.5k | } |
170 | 47.4k | --t; |
171 | | |
172 | | // NOTE: we set the removed vertex index to an unique value |
173 | | // to make sure the developer gets notified when his |
174 | | // application attempts to access this data. |
175 | 47.4k | face.mIndices[face.mNumIndices] = 0xdeadbeef; |
176 | | |
177 | 47.4k | if (first) { |
178 | 25.5k | ++deg; |
179 | 25.5k | first = false; |
180 | 25.5k | } |
181 | | |
182 | 47.4k | if (mConfigRemoveDegenerates) { |
183 | 0 | remove_me[a] = true; |
184 | 0 | goto evil_jump_outside; // hrhrhrh ... yeah, this rocks baby! |
185 | 0 | } |
186 | 47.4k | } |
187 | 131k | } |
188 | | |
189 | 109k | if (mConfigCheckAreaOfTriangle) { |
190 | 109k | if (face.mNumIndices == 3) { |
191 | 79.8k | ai_real area = GeometryUtils::calculateAreaOfTriangle(face, mesh); |
192 | 79.8k | if (area < ai_epsilon) { |
193 | 43.6k | if (mConfigRemoveDegenerates) { |
194 | 0 | remove_me[a] = true; |
195 | 0 | ++deg; |
196 | 0 | goto evil_jump_outside; |
197 | 0 | } |
198 | | |
199 | | // todo: check for index which is corrupt. |
200 | 43.6k | } |
201 | 79.8k | } |
202 | 109k | } |
203 | 109k | } |
204 | | |
205 | | // We need to update the primitive flags array of the mesh. |
206 | 52.7k | switch (face.mNumIndices) { |
207 | 22.4k | case 1u: |
208 | 22.4k | mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; |
209 | 22.4k | break; |
210 | 3.84k | case 2u: |
211 | 3.84k | mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; |
212 | 3.84k | break; |
213 | 26.4k | case 3u: |
214 | 26.4k | mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; |
215 | 26.4k | break; |
216 | 0 | default: |
217 | 0 | mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; |
218 | 0 | break; |
219 | 52.7k | }; |
220 | 52.7k | evil_jump_outside: |
221 | 52.7k | continue; |
222 | 52.7k | } |
223 | | |
224 | | // If AI_CONFIG_PP_FD_REMOVE is true, remove degenerated faces from the import |
225 | 1.35k | if (mConfigRemoveDegenerates && deg) { |
226 | 0 | unsigned int n = 0; |
227 | 0 | for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { |
228 | 0 | aiFace &face_src = mesh->mFaces[a]; |
229 | 0 | if (!remove_me[a]) { |
230 | 0 | aiFace &face_dest = mesh->mFaces[n++]; |
231 | | |
232 | | // Do a manual copy, keep the index array |
233 | 0 | face_dest.mNumIndices = face_src.mNumIndices; |
234 | 0 | face_dest.mIndices = face_src.mIndices; |
235 | |
|
236 | 0 | if (&face_src != &face_dest) { |
237 | | // clear source |
238 | 0 | face_src.mNumIndices = 0; |
239 | 0 | face_src.mIndices = nullptr; |
240 | 0 | } |
241 | 0 | } else { |
242 | | // Otherwise delete it if we don't need this face |
243 | 0 | delete[] face_src.mIndices; |
244 | 0 | face_src.mIndices = nullptr; |
245 | 0 | face_src.mNumIndices = 0; |
246 | 0 | } |
247 | 0 | } |
248 | | // Just leave the rest of the array unreferenced, we don't care for now |
249 | 0 | mesh->mNumFaces = n; |
250 | 0 | if (!mesh->mNumFaces) { |
251 | | // The whole mesh consists of degenerated faces |
252 | | // signal upward, that this mesh should be deleted. |
253 | 0 | ASSIMP_LOG_VERBOSE_DEBUG("FindDegeneratesProcess removed a mesh full of degenerated primitives"); |
254 | 0 | return true; |
255 | 0 | } |
256 | 0 | } |
257 | | |
258 | 1.35k | if (deg && !DefaultLogger::isNullLogger()) { |
259 | | ASSIMP_LOG_WARN("Found ", deg, " degenerated primitives"); |
260 | 0 | } |
261 | 1.35k | return false; |
262 | 1.35k | } |