/src/assimp/code/PostProcessing/GenFaceNormalsProcess.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 | | /** |
43 | | * @file Implementation of the post-processing step to generate face |
44 | | * normals for all imported faces. |
45 | | */ |
46 | | |
47 | | #include "GenFaceNormalsProcess.h" |
48 | | #include <assimp/Exceptional.h> |
49 | | #include <assimp/postprocess.h> |
50 | | #include <assimp/qnan.h> |
51 | | #include <assimp/scene.h> |
52 | | #include <assimp/DefaultLogger.hpp> |
53 | | |
54 | | #include <numeric> |
55 | | #include <memory> |
56 | | |
57 | | using namespace Assimp; |
58 | | |
59 | | // ------------------------------------------------------------------------------------------------ |
60 | | // Returns whether the processing step is in the given flag field. |
61 | 462 | bool GenFaceNormalsProcess::IsActive(unsigned int pFlags) const { |
62 | 462 | force_ = (pFlags & aiProcess_ForceGenNormals) != 0; |
63 | 462 | flippedWindingOrder_ = (pFlags & aiProcess_FlipWindingOrder) != 0; |
64 | 462 | leftHanded_ = (pFlags & aiProcess_MakeLeftHanded) != 0; |
65 | 462 | return (pFlags & aiProcess_GenNormals) != 0; |
66 | 462 | } |
67 | | |
68 | | // ------------------------------------------------------------------------------------------------ |
69 | | // Executes the post-processing step on the given imported data. |
70 | 0 | void GenFaceNormalsProcess::Execute(aiScene *pScene) { |
71 | 0 | ASSIMP_LOG_DEBUG("GenFaceNormalsProcess begin"); |
72 | |
|
73 | 0 | if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) { |
74 | 0 | throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); |
75 | 0 | } |
76 | | |
77 | 0 | bool bHas = false; |
78 | 0 | for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { |
79 | 0 | if (this->GenMeshFaceNormals(pScene->mMeshes[a])) { |
80 | 0 | bHas = true; |
81 | 0 | } |
82 | 0 | } |
83 | 0 | if (bHas) { |
84 | 0 | ASSIMP_LOG_INFO("GenFaceNormalsProcess finished. " |
85 | 0 | "Face normals have been calculated"); |
86 | 0 | } else { |
87 | 0 | ASSIMP_LOG_DEBUG("GenFaceNormalsProcess finished. " |
88 | 0 | "Normals are already there"); |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | | namespace { |
93 | | |
94 | | template<class XMesh> |
95 | 0 | void updateXMeshVertices(XMesh *pMesh, std::vector<int> &uniqueVertices) { |
96 | | // replace vertex data with the unique data sets |
97 | 0 | pMesh->mNumVertices = static_cast<unsigned int>(uniqueVertices.size()); |
98 | | |
99 | | // ---------------------------------------------------------------------------- |
100 | | // NOTE - we're *not* calling Vertex::SortBack() because it would check for |
101 | | // presence of every single vertex component once PER VERTEX. And our CPU |
102 | | // dislikes branches, even if they're easily predictable. |
103 | | // ---------------------------------------------------------------------------- |
104 | | |
105 | | // Position, if present (check made for aiAnimMesh) |
106 | 0 | if (pMesh->mVertices) { |
107 | 0 | std::unique_ptr<aiVector3D[]> oldVertices(pMesh->mVertices); |
108 | 0 | pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; |
109 | 0 | for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { |
110 | 0 | pMesh->mVertices[a] = oldVertices[uniqueVertices[a]]; |
111 | 0 | } |
112 | 0 | } |
113 | | |
114 | | // Tangents, if present |
115 | 0 | if (pMesh->mTangents) { |
116 | 0 | std::unique_ptr<aiVector3D[]> oldTangents(pMesh->mTangents); |
117 | 0 | pMesh->mTangents = new aiVector3D[pMesh->mNumVertices]; |
118 | 0 | for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { |
119 | 0 | pMesh->mTangents[a] = oldTangents[uniqueVertices[a]]; |
120 | 0 | } |
121 | 0 | } |
122 | | // Bitangents as well |
123 | 0 | if (pMesh->mBitangents) { |
124 | 0 | std::unique_ptr<aiVector3D[]> oldBitangents(pMesh->mBitangents); |
125 | 0 | pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices]; |
126 | 0 | for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { |
127 | 0 | pMesh->mBitangents[a] = oldBitangents[uniqueVertices[a]]; |
128 | 0 | } |
129 | 0 | } |
130 | | // Vertex colors |
131 | 0 | for (unsigned int a = 0; pMesh->HasVertexColors(a); a++) { |
132 | 0 | std::unique_ptr<aiColor4D[]> oldColors(pMesh->mColors[a]); |
133 | 0 | pMesh->mColors[a] = new aiColor4D[pMesh->mNumVertices]; |
134 | 0 | for (unsigned int b = 0; b < pMesh->mNumVertices; b++) { |
135 | 0 | pMesh->mColors[a][b] = oldColors[uniqueVertices[b]]; |
136 | 0 | } |
137 | 0 | } |
138 | | // Texture coords |
139 | 0 | for (unsigned int a = 0; pMesh->HasTextureCoords(a); a++) { |
140 | 0 | std::unique_ptr<aiVector3D[]> oldTextureCoords(pMesh->mTextureCoords[a]); |
141 | 0 | pMesh->mTextureCoords[a] = new aiVector3D[pMesh->mNumVertices]; |
142 | 0 | for (unsigned int b = 0; b < pMesh->mNumVertices; b++) { |
143 | 0 | pMesh->mTextureCoords[a][b] = oldTextureCoords[uniqueVertices[b]]; |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | } // namespace |
149 | | |
150 | | // ------------------------------------------------------------------------------------------------ |
151 | | // Executes the post-processing step on the given imported data. |
152 | 0 | bool GenFaceNormalsProcess::GenMeshFaceNormals(aiMesh *pMesh) { |
153 | 0 | if (nullptr != pMesh->mNormals) { |
154 | 0 | if (force_) { |
155 | 0 | delete[] pMesh->mNormals; |
156 | 0 | } else { |
157 | 0 | return false; |
158 | 0 | } |
159 | 0 | } |
160 | | |
161 | | // If the mesh consists of lines and/or points but not of |
162 | | // triangles or higher-order polygons the normal vectors |
163 | | // are undefined. |
164 | 0 | if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON))) { |
165 | 0 | ASSIMP_LOG_INFO("Normal vectors are undefined for line and point meshes"); |
166 | 0 | return false; |
167 | 0 | } |
168 | | |
169 | | // allocate an array to hold the output normals |
170 | 0 | std::vector<aiVector3D> normals; |
171 | 0 | normals.resize(pMesh->mNumVertices); |
172 | | |
173 | | // mask to indicate if a vertex was already referenced and needs to be duplicated |
174 | 0 | std::vector<bool> alreadyReferenced; |
175 | 0 | alreadyReferenced.resize(pMesh->mNumVertices, false); |
176 | |
|
177 | 0 | std::vector<int> duplicatedVertices; |
178 | 0 | duplicatedVertices.resize(pMesh->mNumVertices); |
179 | 0 | std::iota(std::begin(duplicatedVertices), std::end(duplicatedVertices), 0); |
180 | |
|
181 | 0 | auto storeNormalSplitVertex = [&](unsigned int index, const aiVector3D& normal) { |
182 | 0 | if (!alreadyReferenced[index]) { |
183 | 0 | normals[index] = normal; |
184 | 0 | alreadyReferenced[index] = true; |
185 | 0 | } else { |
186 | 0 | normals.push_back(normal); |
187 | 0 | duplicatedVertices.push_back(index); |
188 | 0 | index = static_cast<unsigned int>(duplicatedVertices.size() - 1); |
189 | 0 | } |
190 | |
|
191 | 0 | return index; |
192 | 0 | }; |
193 | |
|
194 | 0 | const aiVector3D undefinedNormal = aiVector3D(get_qnan()); |
195 | | |
196 | | // iterate through all faces and compute per-face normals but store them per-vertex. |
197 | 0 | for (unsigned int a = 0; a < pMesh->mNumFaces; a++) { |
198 | 0 | const aiFace &face = pMesh->mFaces[a]; |
199 | 0 | if (face.mNumIndices < 3) { |
200 | | // either a point or a line -> no well-defined normal vector |
201 | 0 | for (unsigned int i = 0; i < face.mNumIndices; ++i) { |
202 | 0 | face.mIndices[i] = storeNormalSplitVertex(face.mIndices[i], undefinedNormal); |
203 | 0 | } |
204 | 0 | continue; |
205 | 0 | } |
206 | | |
207 | 0 | const aiVector3D *pV1 = &pMesh->mVertices[face.mIndices[0]]; |
208 | 0 | const aiVector3D *pV2 = &pMesh->mVertices[face.mIndices[1]]; |
209 | 0 | const aiVector3D *pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices - 1]]; |
210 | | // Boolean XOR - if either but not both of these flags are set, then the winding order has |
211 | | // changed and the cross-product to calculate the normal needs to be reversed |
212 | 0 | if (flippedWindingOrder_ != leftHanded_) |
213 | 0 | std::swap(pV2, pV3); |
214 | 0 | const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe(); |
215 | |
|
216 | 0 | for (unsigned int i = 0; i < face.mNumIndices; ++i) { |
217 | 0 | face.mIndices[i] = storeNormalSplitVertex(face.mIndices[i], vNor); |
218 | 0 | } |
219 | 0 | } |
220 | | |
221 | | // store normals (and additional vertices) back into the mesh |
222 | 0 | if (pMesh->mNumVertices != std::size(duplicatedVertices)) { |
223 | 0 | updateXMeshVertices(pMesh, duplicatedVertices); |
224 | 0 | } |
225 | 0 | pMesh->mNormals = new aiVector3D[normals.size()]; |
226 | 0 | memcpy(pMesh->mNormals, normals.data(), normals.size() * sizeof(aiVector3D)); |
227 | |
|
228 | 0 | return true; |
229 | 0 | } |