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 |

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 |

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. |

72 | 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. |

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. |

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 |

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 |