Coverage Report

Created: 2026-05-23 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/PostProcessing/CalcTangentsProcess.cpp
Line
Count
Source
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2026, 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
12.0k
        configMaxAngle(float(AI_DEG_TO_RAD(45.f))), configSourceUV(0) {
60
    // nothing to do here
61
12.0k
}
62
63
// ------------------------------------------------------------------------------------------------
64
// Returns whether the processing step is present in the given flag field.
65
3.12k
bool CalcTangentsProcess::IsActive(unsigned int pFlags) const {
66
3.12k
    return (pFlags & aiProcess_CalcTangentSpace) != 0;
67
3.12k
}
68
69
// ------------------------------------------------------------------------------------------------
70
// Executes the post processing step on the given imported data.
71
3.12k
void CalcTangentsProcess::SetupProperties(const Importer *pImp) {
72
3.12k
    ai_assert(nullptr != pImp);
73
74
    // get the current value of the property
75
3.12k
    configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, 45.f);
76
3.12k
    configMaxAngle = std::max(std::min(configMaxAngle, 45.0f), 0.0f);
77
3.12k
    configMaxAngle = AI_DEG_TO_RAD(configMaxAngle);
78
79
3.12k
    configSourceUV = pImp->GetPropertyInteger(AI_CONFIG_PP_CT_TEXTURE_CHANNEL_INDEX, 0);
80
3.12k
}
81
82
// ------------------------------------------------------------------------------------------------
83
// Executes the post processing step on the given imported data.
84
3.12k
void CalcTangentsProcess::Execute(aiScene *pScene) {
85
3.12k
    ai_assert(nullptr != pScene);
86
87
3.12k
    ASSIMP_LOG_DEBUG("CalcTangentsProcess begin");
88
89
3.12k
    bool bHas = false;
90
91.8k
    for (unsigned int a = 0; a < pScene->mNumMeshes; a++) {
91
88.7k
        if (ProcessMesh(pScene->mMeshes[a], a)) bHas = true;
92
88.7k
    }
93
94
3.12k
    if (bHas) {
95
523
        ASSIMP_LOG_INFO("CalcTangentsProcess finished. Tangents have been calculated");
96
2.60k
    } else {
97
2.60k
        ASSIMP_LOG_DEBUG("CalcTangentsProcess finished");
98
2.60k
    }
99
3.12k
}
100
101
// ------------------------------------------------------------------------------------------------
102
// Calculates tangents and bi-tangents for the given mesh
103
88.7k
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
88.7k
    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
88.7k
    if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON))) {
116
37.8k
        ASSIMP_LOG_INFO("Tangents are undefined for line and point meshes");
117
37.8k
        return false;
118
37.8k
    }
119
120
    // what we can check, though, is if the mesh has normals and texture coordinates. That's a requirement
121
50.8k
    if (pMesh->mNormals == nullptr) {
122
0
        ASSIMP_LOG_ERROR("Failed to compute tangents; need normals");
123
0
        return false;
124
0
    }
125
50.8k
    if (configSourceUV >= AI_MAX_NUMBER_OF_TEXTURECOORDS || !pMesh->mTextureCoords[configSourceUV]) {
126
45.9k
        ASSIMP_LOG_ERROR("Failed to compute tangents; need UV data in channel", configSourceUV);
127
45.9k
        return false;
128
45.9k
    }
129
130
4.97k
    const float angleEpsilon = 0.9999f;
131
132
4.97k
    std::vector<bool> vertexDone(pMesh->mNumVertices, false);
133
4.97k
    const float qnan = get_qnan();
134
135
    // create space for the tangents and bitangents
136
4.97k
    pMesh->mTangents = new aiVector3D[pMesh->mNumVertices];
137
4.97k
    pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices];
138
139
4.97k
    const aiVector3D *meshPos = pMesh->mVertices;
140
4.97k
    const aiVector3D *meshNorm = pMesh->mNormals;
141
4.97k
    const aiVector3D *meshTex = pMesh->mTextureCoords[configSourceUV];
142
4.97k
    aiVector3D *meshTang = pMesh->mTangents;
143
4.97k
    aiVector3D *meshBitang = pMesh->mBitangents;
144
145
    // calculate the tangent and bitangent for every face
146
218k
    for (unsigned int a = 0; a < pMesh->mNumFaces; a++) {
147
213k
        const aiFace &face = pMesh->mFaces[a];
148
213k
        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
213k
        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
213k
        aiVector3D v = meshPos[p1] - meshPos[p0], w = meshPos[p2] - meshPos[p0];
169
170
        // texture offset p1->p2 and p1->p3
171
213k
        float sx = meshTex[p1].x - meshTex[p0].x, sy = meshTex[p1].y - meshTex[p0].y;
172
213k
        float tx = meshTex[p2].x - meshTex[p0].x, ty = meshTex[p2].y - meshTex[p0].y;
173
213k
        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
213k
        if (sx * ty == sy * tx) {
176
203k
            sx = 0.0;
177
203k
            sy = 1.0;
178
203k
            tx = 1.0;
179
203k
            ty = 0.0;
180
203k
        }
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
213k
        aiVector3D tangent, bitangent;
185
213k
        tangent.x = (w.x * sy - v.x * ty) * dirCorrection;
186
213k
        tangent.y = (w.y * sy - v.y * ty) * dirCorrection;
187
213k
        tangent.z = (w.z * sy - v.z * ty) * dirCorrection;
188
213k
        bitangent.x = (w.x * sx - v.x * tx) * dirCorrection;
189
213k
        bitangent.y = (w.y * sx - v.y * tx) * dirCorrection;
190
213k
        bitangent.z = (w.z * sx - v.z * tx) * dirCorrection;
191
192
        // store for every vertex of that face
193
855k
        for (unsigned int b = 0; b < face.mNumIndices; ++b) {
194
641k
            unsigned int p = face.mIndices[b];
195
196
            // project tangent and bitangent into the plane formed by the vertex' normal
197
641k
            aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
198
641k
            aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
199
641k
            localTangent.NormalizeSafe();
200
641k
            localBitangent.NormalizeSafe();
201
202
            // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN.
203
641k
            bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z)
204
638k
                || (-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
641k
            bool invalid_bitangent = is_special_float(localBitangent.x) || is_special_float(localBitangent.y) || is_special_float(localBitangent.z)
206
639k
                || (-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
641k
            if (invalid_tangent != invalid_bitangent) {
208
6.43k
                if (invalid_tangent) {
209
3.15k
                    localTangent = meshNorm[p] ^ localBitangent;
210
3.15k
                    localTangent.NormalizeSafe();
211
3.28k
                } else {
212
3.28k
                    localBitangent = localTangent ^ meshNorm[p];
213
3.28k
                    localBitangent.NormalizeSafe();
214
3.28k
                }
215
6.43k
            }
216
217
            // and write it into the mesh.
218
641k
            meshTang[p] = localTangent;
219
641k
            meshBitang[p] = localBitangent;
220
641k
        }
221
213k
    }
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
4.97k
    SpatialSort *vertexFinder = nullptr;
226
4.97k
    SpatialSort _vertexFinder;
227
4.97k
    float posEpsilon = 10e-6f;
228
4.97k
    if (shared) {
229
4.97k
        std::vector<std::pair<SpatialSort, float>> *avf;
230
4.97k
        shared->GetProperty(AI_SPP_SPATIAL_SORT, avf);
231
4.97k
        if (avf) {
232
4.97k
            std::pair<SpatialSort, float> &blubb = avf->operator[](meshIndex);
233
4.97k
            vertexFinder = &blubb.first;
234
4.97k
            posEpsilon = blubb.second;
235
4.97k
            ;
236
4.97k
        }
237
4.97k
    }
238
4.97k
    if (!vertexFinder) {
239
0
        _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof(aiVector3D));
240
0
        vertexFinder = &_vertexFinder;
241
0
        posEpsilon = ComputePositionEpsilon(pMesh);
242
0
    }
243
4.97k
    std::vector<unsigned int> verticesFound;
244
245
4.97k
    const float fLimit = std::cos(configMaxAngle);
246
4.97k
    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
575k
    for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
251
570k
        if (vertexDone[a])
252
326k
            continue;
253
254
244k
        const aiVector3D &origPos = pMesh->mVertices[a];
255
244k
        const aiVector3D &origNorm = pMesh->mNormals[a];
256
244k
        const aiVector3D &origTang = pMesh->mTangents[a];
257
244k
        const aiVector3D &origBitang = pMesh->mBitangents[a];
258
244k
        closeVertices.resize(0);
259
260
        // find all vertices close to that position
261
244k
        vertexFinder->FindPositions(origPos, posEpsilon, verticesFound);
262
263
244k
        closeVertices.reserve(verticesFound.size() + 5);
264
244k
        closeVertices.push_back(a);
265
266
        // look among them for other vertices sharing the same normal and a close-enough tangent/bitangent
267
93.8M
        for (unsigned int b = 0; b < verticesFound.size(); b++) {
268
93.6M
            unsigned int idx = verticesFound[b];
269
93.6M
            if (vertexDone[idx])
270
5.91M
                continue;
271
87.7M
            if (meshNorm[idx] * origNorm < angleEpsilon)
272
23.1M
                continue;
273
64.5M
            if (meshTang[idx] * origTang < fLimit)
274
63.8M
                continue;
275
785k
            if (meshBitang[idx] * origBitang < fLimit)
276
292k
                continue;
277
278
            // it's similar enough -> add it to the smoothing group
279
493k
            closeVertices.push_back(idx);
280
493k
            vertexDone[idx] = true;
281
493k
        }
282
283
        // smooth the tangents and bitangents of all vertices that were found to be close enough
284
244k
        aiVector3D smoothTangent(0, 0, 0), smoothBitangent(0, 0, 0);
285
981k
        for (unsigned int b = 0; b < closeVertices.size(); ++b) {
286
737k
            smoothTangent += meshTang[closeVertices[b]];
287
737k
            smoothBitangent += meshBitang[closeVertices[b]];
288
737k
        }
289
244k
        smoothTangent.Normalize();
290
244k
        smoothBitangent.Normalize();
291
292
        // and write it back into all affected tangents
293
981k
        for (unsigned int b = 0; b < closeVertices.size(); ++b) {
294
737k
            meshTang[closeVertices[b]] = smoothTangent;
295
737k
            meshBitang[closeVertices[b]] = smoothBitangent;
296
737k
        }
297
244k
    }
298
4.97k
    return true;
299
50.8k
}