1 | /* |
2 | --------------------------------------------------------------------------- |
3 | Open Asset Import Library (assimp) |
4 | --------------------------------------------------------------------------- |
5 | |
6 | Copyright (c) 2006-2021, 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 | configMaxAngle(AI_DEG_TO_RAD(45.f)), configSourceUV(0) { |
60 | // nothing to do here |
61 | } |
62 | |
63 | // ------------------------------------------------------------------------------------------------ |
64 | // Destructor, private as well |
65 | CalcTangentsProcess::~CalcTangentsProcess() { |
66 | // nothing to do here |
67 | } |
68 | |
69 | // ------------------------------------------------------------------------------------------------ |
70 | // Returns whether the processing step is present in the given flag field. |
71 | bool 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. |
77 | void 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. |
90 | void 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 |
109 | bool 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 | |