Coverage Report

Created: 2025-08-26 06:41

/src/assimp/code/PostProcessing/CalcTangentsProcess.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
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 Implementation of the post processing step to calculate
45
 *  tangents and bitangents for all imported meshes
46
 */
47
48
// internal headers
49
#include "CalcTangentsProcess.h"
50
#include "ProcessHelper.h"
51
#include <assimp/TinyFormatter.h>
52
#include <assimp/qnan.h>
53
54
using namespace Assimp;
55
56
// ------------------------------------------------------------------------------------------------
57
// Constructor to be privately used by Importer
58
CalcTangentsProcess::CalcTangentsProcess() :
59
276
        configMaxAngle(float(AI_DEG_TO_RAD(45.f))), configSourceUV(0) {
60
    // nothing to do here
61
276
}
62
63
// ------------------------------------------------------------------------------------------------
64
// Returns whether the processing step is present in the given flag field.
65
96
bool CalcTangentsProcess::IsActive(unsigned int pFlags) const {
66
96
    return (pFlags & aiProcess_CalcTangentSpace) != 0;
67
96
}
68
69
// ------------------------------------------------------------------------------------------------
70
// Executes the post processing step on the given imported data.
71
56
void CalcTangentsProcess::SetupProperties(const Importer *pImp) {
72
56
    ai_assert(nullptr != pImp);
73
74
    // get the current value of the property
75
56
    configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, 45.f);
76
56
    configMaxAngle = std::max(std::min(configMaxAngle, 45.0f), 0.0f);
77
56
    configMaxAngle = AI_DEG_TO_RAD(configMaxAngle);
78
79
56
    configSourceUV = pImp->GetPropertyInteger(AI_CONFIG_PP_CT_TEXTURE_CHANNEL_INDEX, 0);
80
56
}
81
82
// ------------------------------------------------------------------------------------------------
83
// Executes the post processing step on the given imported data.
84
56
void CalcTangentsProcess::Execute(aiScene *pScene) {
85
56
    ai_assert(nullptr != pScene);
86
87
56
    ASSIMP_LOG_DEBUG("CalcTangentsProcess begin");
88
89
56
    bool bHas = false;
90
1.90k
    for (unsigned int a = 0; a < pScene->mNumMeshes; a++) {
91
1.85k
        if (ProcessMesh(pScene->mMeshes[a], a)) bHas = true;
92
1.85k
    }
93
94
56
    if (bHas) {
95
3
        ASSIMP_LOG_INFO("CalcTangentsProcess finished. Tangents have been calculated");
96
53
    } else {
97
53
        ASSIMP_LOG_DEBUG("CalcTangentsProcess finished");
98
53
    }
99
56
}
100
101
// ------------------------------------------------------------------------------------------------
102
// Calculates tangents and bi-tangents for the given mesh
103
1.85k
bool CalcTangentsProcess::ProcessMesh(aiMesh *pMesh, unsigned int meshIndex) {
104
    // we assume that the mesh is still in the verbose vertex format where each face has its own set
105
    // of vertices and no vertices are shared between faces. Sadly I don't know any quick test to
106
    // assert() it here.
107
    // assert( must be verbose, dammit);
108
109
1.85k
    if (pMesh->mTangents) // this implies that mBitangents is also there
110
0
        return false;
111
112
    // If the mesh consists of lines and/or points but not of
113
    // triangles or higher-order polygons the normal vectors
114
    // are undefined.
115
1.85k
    if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON))) {
116
1.30k
        ASSIMP_LOG_INFO("Tangents are undefined for line and point meshes");
117
1.30k
        return false;
118
1.30k
    }
119
120
    // what we can check, though, is if the mesh has normals and texture coordinates. That's a requirement
121
548
    if (pMesh->mNormals == nullptr) {
122
0
        ASSIMP_LOG_ERROR("Failed to compute tangents; need normals");
123
0
        return false;
124
0
    }
125
548
    if (configSourceUV >= AI_MAX_NUMBER_OF_TEXTURECOORDS || !pMesh->mTextureCoords[configSourceUV]) {
126
542
        ASSIMP_LOG_ERROR("Failed to compute tangents; need UV data in channel", configSourceUV);
127
542
        return false;
128
542
    }
129
130
6
    const float angleEpsilon = 0.9999f;
131
132
6
    std::vector<bool> vertexDone(pMesh->mNumVertices, false);
133
6
    const float qnan = get_qnan();
134
135
    // create space for the tangents and bitangents
136
6
    pMesh->mTangents = new aiVector3D[pMesh->mNumVertices];
137
6
    pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices];
138
139
6
    const aiVector3D *meshPos = pMesh->mVertices;
140
6
    const aiVector3D *meshNorm = pMesh->mNormals;
141
6
    const aiVector3D *meshTex = pMesh->mTextureCoords[configSourceUV];
142
6
    aiVector3D *meshTang = pMesh->mTangents;
143
6
    aiVector3D *meshBitang = pMesh->mBitangents;
144
145
    // calculate the tangent and bitangent for every face
146
18
    for (unsigned int a = 0; a < pMesh->mNumFaces; a++) {
147
12
        const aiFace &face = pMesh->mFaces[a];
148
12
        if (face.mNumIndices < 3) {
149
            // There are less than three indices, thus the tangent vector
150
            // is not defined. We are finished with these vertices now,
151
            // their tangent vectors are set to qnan.
152
0
            for (unsigned int i = 0; i < face.mNumIndices; ++i) {
153
0
                unsigned int idx = face.mIndices[i];
154
0
                vertexDone[idx] = true;
155
0
                meshTang[idx] = aiVector3D(qnan);
156
0
                meshBitang[idx] = aiVector3D(qnan);
157
0
            }
158
159
0
            continue;
160
0
        }
161
162
        // triangle or polygon... we always use only the first three indices. A polygon
163
        // is supposed to be planar anyways....
164
        // FIXME: (thom) create correct calculation for multi-vertex polygons maybe?
165
12
        const unsigned int p0 = face.mIndices[0], p1 = face.mIndices[1], p2 = face.mIndices[2];
166
167
        // position differences p1->p2 and p1->p3
168
12
        aiVector3D v = meshPos[p1] - meshPos[p0], w = meshPos[p2] - meshPos[p0];
169
170
        // texture offset p1->p2 and p1->p3
171
12
        float sx = meshTex[p1].x - meshTex[p0].x, sy = meshTex[p1].y - meshTex[p0].y;
172
12
        float tx = meshTex[p2].x - meshTex[p0].x, ty = meshTex[p2].y - meshTex[p0].y;
173
12
        float dirCorrection = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f;
174
        // when t1, t2, t3 in same position in UV space, just use default UV direction.
175
12
        if (sx * ty == sy * tx) {
176
12
            sx = 0.0;
177
12
            sy = 1.0;
178
12
            tx = 1.0;
179
12
            ty = 0.0;
180
12
        }
181
182
        // tangent points in the direction where to positive X axis of the texture coord's would point in model space
183
        // bitangent's points along the positive Y axis of the texture coord's, respectively
184
12
        aiVector3D tangent, bitangent;
185
12
        tangent.x = (w.x * sy - v.x * ty) * dirCorrection;
186
12
        tangent.y = (w.y * sy - v.y * ty) * dirCorrection;
187
12
        tangent.z = (w.z * sy - v.z * ty) * dirCorrection;
188
12
        bitangent.x = (w.x * sx - v.x * tx) * dirCorrection;
189
12
        bitangent.y = (w.y * sx - v.y * tx) * dirCorrection;
190
12
        bitangent.z = (w.z * sx - v.z * tx) * dirCorrection;
191
192
        // store for every vertex of that face
193
48
        for (unsigned int b = 0; b < face.mNumIndices; ++b) {
194
36
            unsigned int p = face.mIndices[b];
195
196
            // project tangent and bitangent into the plane formed by the vertex' normal
197
36
            aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
198
36
            aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
199
36
            localTangent.NormalizeSafe();
200
36
            localBitangent.NormalizeSafe();
201
202
            // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN.
203
36
            bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z)
204
36
                || (-0.5f < localTangent.x && localTangent.x < 0.5f && -0.5f < localTangent.y && localTangent.y < 0.5f && -0.5f < localTangent.z && localTangent.z < 0.5f);
205
36
            bool invalid_bitangent = is_special_float(localBitangent.x) || is_special_float(localBitangent.y) || is_special_float(localBitangent.z)
206
36
                || (-0.5f < localBitangent.x && localBitangent.x < 0.5f && -0.5f < localBitangent.y && localBitangent.y < 0.5f && -0.5f < localBitangent.z && localBitangent.z < 0.5f);
207
36
            if (invalid_tangent != invalid_bitangent) {
208
0
                if (invalid_tangent) {
209
0
                    localTangent = meshNorm[p] ^ localBitangent;
210
0
                    localTangent.NormalizeSafe();
211
0
                } else {
212
0
                    localBitangent = localTangent ^ meshNorm[p];
213
0
                    localBitangent.NormalizeSafe();
214
0
                }
215
0
            }
216
217
            // and write it into the mesh.
218
36
            meshTang[p] = localTangent;
219
36
            meshBitang[p] = localBitangent;
220
36
        }
221
12
    }
222
223
    // create a helper to quickly find locally close vertices among the vertex array
224
    // FIX: check whether we can reuse the SpatialSort of a previous step
225
6
    SpatialSort *vertexFinder = nullptr;
226
6
    SpatialSort _vertexFinder;
227
6
    float posEpsilon = 10e-6f;
228
6
    if (shared) {
229
6
        std::vector<std::pair<SpatialSort, float>> *avf;
230
6
        shared->GetProperty(AI_SPP_SPATIAL_SORT, avf);
231
6
        if (avf) {
232
6
            std::pair<SpatialSort, float> &blubb = avf->operator[](meshIndex);
233
6
            vertexFinder = &blubb.first;
234
6
            posEpsilon = blubb.second;
235
6
            ;
236
6
        }
237
6
    }
238
6
    if (!vertexFinder) {
239
0
        _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof(aiVector3D));
240
0
        vertexFinder = &_vertexFinder;
241
0
        posEpsilon = ComputePositionEpsilon(pMesh);
242
0
    }
243
6
    std::vector<unsigned int> verticesFound;
244
245
6
    const float fLimit = std::cos(configMaxAngle);
246
6
    std::vector<unsigned int> closeVertices;
247
248
    // in the second pass we now smooth out all tangents and bitangents at the same local position
249
    // if they are not too far off.
250
36
    for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
251
30
        if (vertexDone[a])
252
9
            continue;
253
254
21
        const aiVector3D &origPos = pMesh->mVertices[a];
255
21
        const aiVector3D &origNorm = pMesh->mNormals[a];
256
21
        const aiVector3D &origTang = pMesh->mTangents[a];
257
21
        const aiVector3D &origBitang = pMesh->mBitangents[a];
258
21
        closeVertices.resize(0);
259
260
        // find all vertices close to that position
261
21
        vertexFinder->FindPositions(origPos, posEpsilon, verticesFound);
262
263
21
        closeVertices.reserve(verticesFound.size() + 5);
264
21
        closeVertices.push_back(a);
265
266
        // look among them for other vertices sharing the same normal and a close-enough tangent/bitangent
267
78
        for (unsigned int b = 0; b < verticesFound.size(); b++) {
268
57
            unsigned int idx = verticesFound[b];
269
57
            if (vertexDone[idx])
270
9
                continue;
271
48
            if (meshNorm[idx] * origNorm < angleEpsilon)
272
0
                continue;
273
48
            if (meshTang[idx] * origTang < fLimit)
274
24
                continue;
275
24
            if (meshBitang[idx] * origBitang < fLimit)
276
0
                continue;
277
278
            // it's similar enough -> add it to the smoothing group
279
24
            closeVertices.push_back(idx);
280
24
            vertexDone[idx] = true;
281
24
        }
282
283
        // smooth the tangents and bitangents of all vertices that were found to be close enough
284
21
        aiVector3D smoothTangent(0, 0, 0), smoothBitangent(0, 0, 0);
285
66
        for (unsigned int b = 0; b < closeVertices.size(); ++b) {
286
45
            smoothTangent += meshTang[closeVertices[b]];
287
45
            smoothBitangent += meshBitang[closeVertices[b]];
288
45
        }
289
21
        smoothTangent.Normalize();
290
21
        smoothBitangent.Normalize();
291
292
        // and write it back into all affected tangents
293
66
        for (unsigned int b = 0; b < closeVertices.size(); ++b) {
294
45
            meshTang[closeVertices[b]] = smoothTangent;
295
45
            meshBitang[closeVertices[b]] = smoothBitangent;
296
45
        }
297
21
    }
298
6
    return true;
299
548
}