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