Coverage Report

Created: 2025-08-26 06:41

/src/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp
Line
Count
Source (jump to first uncovered line)
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 Implementation of the post processing step to generate face
43
* normals for all imported faces.
44
*/
45
46
// internal headers
47
#include "GenVertexNormalsProcess.h"
48
#include "ProcessHelper.h"
49
#include <assimp/Exceptional.h>
50
#include <assimp/qnan.h>
51
52
using namespace Assimp;
53
54
// ------------------------------------------------------------------------------------------------
55
// Constructor to be privately used by Importer
56
GenVertexNormalsProcess::GenVertexNormalsProcess() :
57
276
        configMaxAngle(AI_DEG_TO_RAD(175.f)) {
58
    // empty
59
276
}
60
61
// ------------------------------------------------------------------------------------------------
62
// Returns whether the processing step is present in the given flag field.
63
96
bool GenVertexNormalsProcess::IsActive(unsigned int pFlags) const {
64
96
    force_ = (pFlags & aiProcess_ForceGenNormals) != 0;
65
96
    flippedWindingOrder_ = (pFlags & aiProcess_FlipWindingOrder) != 0;
66
96
    leftHanded_ = (pFlags & aiProcess_MakeLeftHanded) != 0;
67
96
    return (pFlags & aiProcess_GenSmoothNormals) != 0;
68
96
}
69
70
// ------------------------------------------------------------------------------------------------
71
// Executes the post processing step on the given imported data.
72
56
void GenVertexNormalsProcess::SetupProperties(const Importer *pImp) {
73
    // Get the current value of the AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE property
74
56
    configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, (ai_real)175.0);
75
56
    configMaxAngle = AI_DEG_TO_RAD(std::max(std::min(configMaxAngle, (ai_real)175.0), (ai_real)0.0));
76
56
}
77
78
// ------------------------------------------------------------------------------------------------
79
// Executes the post processing step on the given imported data.
80
56
void GenVertexNormalsProcess::Execute(aiScene *pScene) {
81
56
    ASSIMP_LOG_DEBUG("GenVertexNormalsProcess begin");
82
83
56
    if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) {
84
0
        throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here");
85
0
    }
86
87
56
    bool bHas = false;
88
1.90k
    for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
89
1.85k
        if (GenMeshVertexNormals(pScene->mMeshes[a], a))
90
543
            bHas = true;
91
1.85k
    }
92
93
56
    if (bHas) {
94
26
        ASSIMP_LOG_INFO("GenVertexNormalsProcess finished. "
95
26
                        "Vertex normals have been calculated");
96
30
    } else {
97
30
        ASSIMP_LOG_DEBUG("GenVertexNormalsProcess finished. "
98
30
                         "Normals are already there");
99
30
    }
100
56
}
101
102
// ------------------------------------------------------------------------------------------------
103
// Executes the post processing step on the given imported data.
104
1.85k
bool GenVertexNormalsProcess::GenMeshVertexNormals(aiMesh *pMesh, unsigned int meshIndex) {
105
1.85k
    if (nullptr != pMesh->mNormals) {
106
18
        if (!force_) {
107
18
            return false;
108
18
        }
109
0
        delete[] pMesh->mNormals;
110
0
        pMesh->mNormals = nullptr;
111
0
    }
112
113
    // If the mesh consists of lines and/or points but not of
114
    // triangles or higher-order polygons the normal vectors
115
    // are undefined.
116
1.83k
    if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON))) {
117
1.29k
        ASSIMP_LOG_INFO("Normal vectors are undefined for line and point meshes");
118
1.29k
        return false;
119
1.29k
    }
120
121
    // Allocate the array to hold the output normals
122
543
    const float qnan = std::numeric_limits<ai_real>::quiet_NaN();
123
543
    pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
124
125
    // Compute per-face normals but store them per-vertex
126
44.4k
    for (unsigned int a = 0; a < pMesh->mNumFaces; a++) {
127
43.8k
        const aiFace &face = pMesh->mFaces[a];
128
43.8k
        if (face.mNumIndices < 3) {
129
            // either a point or a line -> no normal vector
130
0
            for (unsigned int i = 0; i < face.mNumIndices; ++i) {
131
0
                pMesh->mNormals[face.mIndices[i]] = aiVector3D(qnan);
132
0
            }
133
134
0
            continue;
135
0
        }
136
137
43.8k
        const aiVector3D *pV1 = &pMesh->mVertices[face.mIndices[0]];
138
43.8k
        const aiVector3D *pV2 = &pMesh->mVertices[face.mIndices[1]];
139
43.8k
        const aiVector3D *pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices - 1]];
140
        // Boolean XOR - if either but not both of these flags is set, then the winding order has
141
        // changed and the cross product to calculate the normal needs to be reversed
142
43.8k
        if (flippedWindingOrder_ != leftHanded_) {
143
0
            std::swap(pV2, pV3);
144
0
        }
145
43.8k
        const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe();
146
147
175k
        for (unsigned int i = 0; i < face.mNumIndices; ++i) {
148
131k
            pMesh->mNormals[face.mIndices[i]] = vNor;
149
131k
        }
150
43.8k
    }
151
152
    // Set up a SpatialSort to quickly find all vertices close to a given position
153
    // check whether we can reuse the SpatialSort of a previous step.
154
543
    SpatialSort *vertexFinder = nullptr;
155
543
    SpatialSort _vertexFinder;
156
543
    ai_real posEpsilon = ai_real(1e-5);
157
543
    if (shared) {
158
543
        std::vector<std::pair<SpatialSort, ai_real>> *avf;
159
543
        shared->GetProperty(AI_SPP_SPATIAL_SORT, avf);
160
543
        if (avf) {
161
543
            std::pair<SpatialSort, ai_real> &blubb = avf->operator[](meshIndex);
162
543
            vertexFinder = &blubb.first;
163
543
            posEpsilon = blubb.second;
164
543
        }
165
543
    }
166
543
    if (!vertexFinder) {
167
0
        _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof(aiVector3D));
168
0
        vertexFinder = &_vertexFinder;
169
0
        posEpsilon = ComputePositionEpsilon(pMesh);
170
0
    }
171
543
    std::vector<unsigned int> verticesFound;
172
543
    aiVector3D *pcNew = new aiVector3D[pMesh->mNumVertices];
173
174
543
    if (configMaxAngle >= AI_DEG_TO_RAD(175.f)) {
175
        // There is no angle limit. Thus all vertices with positions close
176
        // to each other will receive the same vertex normal. This allows us
177
        // to optimize the whole algorithm a little bit ...
178
543
        std::vector<bool> abHad(pMesh->mNumVertices, false);
179
139k
        for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) {
180
139k
            if (abHad[i]) {
181
137k
                continue;
182
137k
            }
183
184
            // Get all vertices that share this one ...
185
1.76k
            vertexFinder->FindPositions(pMesh->mVertices[i], posEpsilon, verticesFound);
186
187
1.76k
            aiVector3D pcNor;
188
141k
            for (unsigned int a = 0; a < verticesFound.size(); ++a) {
189
139k
                const aiVector3D &v = pMesh->mNormals[verticesFound[a]];
190
139k
                if (is_not_qnan(v.x)) pcNor += v;
191
139k
            }
192
1.76k
            pcNor.NormalizeSafe();
193
194
            // Write the smoothed normal back to all affected normals
195
141k
            for (unsigned int a = 0; a < verticesFound.size(); ++a) {
196
139k
                unsigned int vidx = verticesFound[a];
197
139k
                pcNew[vidx] = pcNor;
198
139k
                abHad[vidx] = true;
199
139k
            }
200
1.76k
        }
201
543
    }
202
    // Slower code path if a smooth angle is set. There are many ways to achieve
203
    // the effect, this one is the most straightforward one.
204
0
    else {
205
0
        const ai_real fLimit = std::cos(configMaxAngle);
206
0
        for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) {
207
            // Get all vertices that share this one ...
208
0
            vertexFinder->FindPositions(pMesh->mVertices[i], posEpsilon, verticesFound);
209
210
0
            aiVector3D vr = pMesh->mNormals[i];
211
212
0
            aiVector3D pcNor;
213
0
            for (unsigned int a = 0; a < verticesFound.size(); ++a) {
214
0
                aiVector3D v = pMesh->mNormals[verticesFound[a]];
215
216
                // Check whether the angle between the two normals is not too large.
217
                // Skip the angle check on our own normal to avoid false negatives
218
                // (v*v is not guaranteed to be 1.0 for all unit vectors v)
219
0
                if (is_not_qnan(v.x) && (verticesFound[a] == i || (v * vr >= fLimit)))
220
0
                    pcNor += v;
221
0
            }
222
0
            pcNew[i] = pcNor.NormalizeSafe();
223
0
        }
224
0
    }
225
226
543
    delete[] pMesh->mNormals;
227
543
    pMesh->mNormals = pcNew;
228
229
543
    return true;
230
1.83k
}