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

source code of qt3d/src/3rdparty/assimp/src/code/PostProcessing/CalcTangentsProcess.cpp