1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following conditions are met:
12
13* Redistributions of source code must retain the above
14 copyright notice, this list of conditions and the
15 following disclaimer.
16
17* Redistributions in binary form must reproduce the above
18 copyright notice, this list of conditions and the
19 following disclaimer in the documentation and/or other
20 materials provided with the distribution.
21
22* Neither the name of the assimp team, nor the names of its
23 contributors may be used to endorse or promote products
24 derived from this software without specific prior
25 written permission of the assimp team.
26
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39----------------------------------------------------------------------
40*/
41
42/** @file GenUVCoords step */
43
44
45#include "ComputeUVMappingProcess.h"
46#include "ProcessHelper.h"
47#include "Exceptional.h"
48
49using namespace Assimp;
50
51namespace {
52
53 const static aiVector3D base_axis_y(0.0,1.0,0.0);
54 const static aiVector3D base_axis_x(1.0,0.0,0.0);
55 const static aiVector3D base_axis_z(0.0,0.0,1.0);
56 const static ai_real angle_epsilon = ai_real( 0.95 );
57}
58
59// ------------------------------------------------------------------------------------------------
60// Constructor to be privately used by Importer
61ComputeUVMappingProcess::ComputeUVMappingProcess()
62{
63 // nothing to do here
64}
65
66// ------------------------------------------------------------------------------------------------
67// Destructor, private as well
68ComputeUVMappingProcess::~ComputeUVMappingProcess()
69{
70 // nothing to do here
71}
72
73// ------------------------------------------------------------------------------------------------
74// Returns whether the processing step is present in the given flag field.
75bool ComputeUVMappingProcess::IsActive( unsigned int pFlags) const
76{
77 return (pFlags & aiProcess_GenUVCoords) != 0;
78}
79
80// ------------------------------------------------------------------------------------------------
81// Check whether a ray intersects a plane and find the intersection point
82inline bool PlaneIntersect(const aiRay& ray, const aiVector3D& planePos,
83 const aiVector3D& planeNormal, aiVector3D& pos)
84{
85 const ai_real b = planeNormal * (planePos - ray.pos);
86 ai_real h = ray.dir * planeNormal;
87 if ((h < 10e-5 && h > -10e-5) || (h = b/h) < 0)
88 return false;
89
90 pos = ray.pos + (ray.dir * h);
91 return true;
92}
93
94// ------------------------------------------------------------------------------------------------
95// Find the first empty UV channel in a mesh
96inline unsigned int FindEmptyUVChannel (aiMesh* mesh)
97{
98 for (unsigned int m = 0; m < AI_MAX_NUMBER_OF_TEXTURECOORDS;++m)
99 if (!mesh->mTextureCoords[m])return m;
100
101 DefaultLogger::get()->error("Unable to compute UV coordinates, no free UV slot found");
102 return UINT_MAX;
103}
104
105// ------------------------------------------------------------------------------------------------
106// Try to remove UV seams
107void RemoveUVSeams (aiMesh* mesh, aiVector3D* out)
108{
109 // TODO: just a very rough algorithm. I think it could be done
110 // much easier, but I don't know how and am currently too tired to
111 // to think about a better solution.
112
113 const static ai_real LOWER_LIMIT = ai_real( 0.1 );
114 const static ai_real UPPER_LIMIT = ai_real( 0.9 );
115
116 const static ai_real LOWER_EPSILON = ai_real( 10e-3 );
117 const static ai_real UPPER_EPSILON = ai_real( 1.0-10e-3 );
118
119 for (unsigned int fidx = 0; fidx < mesh->mNumFaces;++fidx)
120 {
121 const aiFace& face = mesh->mFaces[fidx];
122 if (face.mNumIndices < 3) continue; // triangles and polygons only, please
123
124 unsigned int small = face.mNumIndices, large = small;
125 bool zero = false, one = false, round_to_zero = false;
126
127 // Check whether this face lies on a UV seam. We can just guess,
128 // but the assumption that a face with at least one very small
129 // on the one side and one very large U coord on the other side
130 // lies on a UV seam should work for most cases.
131 for (unsigned int n = 0; n < face.mNumIndices;++n)
132 {
133 if (out[face.mIndices[n]].x < LOWER_LIMIT)
134 {
135 small = n;
136
137 // If we have a U value very close to 0 we can't
138 // round the others to 0, too.
139 if (out[face.mIndices[n]].x <= LOWER_EPSILON)
140 zero = true;
141 else round_to_zero = true;
142 }
143 if (out[face.mIndices[n]].x > UPPER_LIMIT)
144 {
145 large = n;
146
147 // If we have a U value very close to 1 we can't
148 // round the others to 1, too.
149 if (out[face.mIndices[n]].x >= UPPER_EPSILON)
150 one = true;
151 }
152 }
153 if (small != face.mNumIndices && large != face.mNumIndices)
154 {
155 for (unsigned int n = 0; n < face.mNumIndices;++n)
156 {
157 // If the u value is over the upper limit and no other u
158 // value of that face is 0, round it to 0
159 if (out[face.mIndices[n]].x > UPPER_LIMIT && !zero)
160 out[face.mIndices[n]].x = 0.0;
161
162 // If the u value is below the lower limit and no other u
163 // value of that face is 1, round it to 1
164 else if (out[face.mIndices[n]].x < LOWER_LIMIT && !one)
165 out[face.mIndices[n]].x = 1.0;
166
167 // The face contains both 0 and 1 as UV coords. This can occur
168 // for faces which have an edge that lies directly on the seam.
169 // Due to numerical inaccuracies one U coord becomes 0, the
170 // other 1. But we do still have a third UV coord to determine
171 // to which side we must round to.
172 else if (one && zero)
173 {
174 if (round_to_zero && out[face.mIndices[n]].x >= UPPER_EPSILON)
175 out[face.mIndices[n]].x = 0.0;
176 else if (!round_to_zero && out[face.mIndices[n]].x <= LOWER_EPSILON)
177 out[face.mIndices[n]].x = 1.0;
178 }
179 }
180 }
181 }
182}
183
184// ------------------------------------------------------------------------------------------------
185void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
186{
187 aiVector3D center, min, max;
188 FindMeshCenter(mesh, center, min, max);
189
190 // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ...
191 // currently the mapping axis will always be one of x,y,z, except if the
192 // PretransformVertices step is used (it transforms the meshes into worldspace,
193 // thus changing the mapping axis)
194 if (axis * base_axis_x >= angle_epsilon) {
195
196 // For each point get a normalized projection vector in the sphere,
197 // get its longitude and latitude and map them to their respective
198 // UV axes. Problems occur around the poles ... unsolvable.
199 //
200 // The spherical coordinate system looks like this:
201 // x = cos(lon)*cos(lat)
202 // y = sin(lon)*cos(lat)
203 // z = sin(lat)
204 //
205 // Thus we can derive:
206 // lat = arcsin (z)
207 // lon = arctan (y/x)
208 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
209 const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize();
210 out[pnt] = aiVector3D((std::atan2(diff.z, diff.y) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
211 (std::asin (diff.x) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0);
212 }
213 }
214 else if (axis * base_axis_y >= angle_epsilon) {
215 // ... just the same again
216 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
217 const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize();
218 out[pnt] = aiVector3D((std::atan2(diff.x, diff.z) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
219 (std::asin (diff.y) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0);
220 }
221 }
222 else if (axis * base_axis_z >= angle_epsilon) {
223 // ... just the same again
224 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
225 const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize();
226 out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
227 (std::asin (diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0);
228 }
229 }
230 // slower code path in case the mapping axis is not one of the coordinate system axes
231 else {
232 aiMatrix4x4 mTrafo;
233 aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo);
234
235 // again the same, except we're applying a transformation now
236 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
237 const aiVector3D diff = ((mTrafo*mesh->mVertices[pnt])-center).Normalize();
238 out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
239 (std::asin(diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0);
240 }
241 }
242
243
244 // Now find and remove UV seams. A seam occurs if a face has a tcoord
245 // close to zero on the one side, and a tcoord close to one on the
246 // other side.
247 RemoveUVSeams(mesh,out);
248}
249
250// ------------------------------------------------------------------------------------------------
251void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
252{
253 aiVector3D center, min, max;
254
255 // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ...
256 // currently the mapping axis will always be one of x,y,z, except if the
257 // PretransformVertices step is used (it transforms the meshes into worldspace,
258 // thus changing the mapping axis)
259 if (axis * base_axis_x >= angle_epsilon) {
260 FindMeshCenter(mesh, center, min, max);
261 const ai_real diff = max.x - min.x;
262
263 // If the main axis is 'z', the z coordinate of a point 'p' is mapped
264 // directly to the texture V axis. The other axis is derived from
265 // the angle between ( p.x - c.x, p.y - c.y ) and (1,0), where
266 // 'c' is the center point of the mesh.
267 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
268 const aiVector3D& pos = mesh->mVertices[pnt];
269 aiVector3D& uv = out[pnt];
270
271 uv.y = (pos.x - min.x) / diff;
272 uv.x = (std::atan2( pos.z - center.z, pos.y - center.y) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI;
273 }
274 }
275 else if (axis * base_axis_y >= angle_epsilon) {
276 FindMeshCenter(mesh, center, min, max);
277 const ai_real diff = max.y - min.y;
278
279 // just the same ...
280 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
281 const aiVector3D& pos = mesh->mVertices[pnt];
282 aiVector3D& uv = out[pnt];
283
284 uv.y = (pos.y - min.y) / diff;
285 uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI;
286 }
287 }
288 else if (axis * base_axis_z >= angle_epsilon) {
289 FindMeshCenter(mesh, center, min, max);
290 const ai_real diff = max.z - min.z;
291
292 // just the same ...
293 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
294 const aiVector3D& pos = mesh->mVertices[pnt];
295 aiVector3D& uv = out[pnt];
296
297 uv.y = (pos.z - min.z) / diff;
298 uv.x = (std::atan2( pos.y - center.y, pos.x - center.x) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI;
299 }
300 }
301 // slower code path in case the mapping axis is not one of the coordinate system axes
302 else {
303 aiMatrix4x4 mTrafo;
304 aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo);
305 FindMeshCenterTransformed(mesh, center, min, max,mTrafo);
306 const ai_real diff = max.y - min.y;
307
308 // again the same, except we're applying a transformation now
309 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt){
310 const aiVector3D pos = mTrafo* mesh->mVertices[pnt];
311 aiVector3D& uv = out[pnt];
312
313 uv.y = (pos.y - min.y) / diff;
314 uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI;
315 }
316 }
317
318 // Now find and remove UV seams. A seam occurs if a face has a tcoord
319 // close to zero on the one side, and a tcoord close to one on the
320 // other side.
321 RemoveUVSeams(mesh,out);
322}
323
324// ------------------------------------------------------------------------------------------------
325void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
326{
327 ai_real diffu,diffv;
328 aiVector3D center, min, max;
329
330 // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ...
331 // currently the mapping axis will always be one of x,y,z, except if the
332 // PretransformVertices step is used (it transforms the meshes into worldspace,
333 // thus changing the mapping axis)
334 if (axis * base_axis_x >= angle_epsilon) {
335 FindMeshCenter(mesh, center, min, max);
336 diffu = max.z - min.z;
337 diffv = max.y - min.y;
338
339 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
340 const aiVector3D& pos = mesh->mVertices[pnt];
341 out[pnt].Set((pos.z - min.z) / diffu,(pos.y - min.y) / diffv,0.0);
342 }
343 }
344 else if (axis * base_axis_y >= angle_epsilon) {
345 FindMeshCenter(mesh, center, min, max);
346 diffu = max.x - min.x;
347 diffv = max.z - min.z;
348
349 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
350 const aiVector3D& pos = mesh->mVertices[pnt];
351 out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0);
352 }
353 }
354 else if (axis * base_axis_z >= angle_epsilon) {
355 FindMeshCenter(mesh, center, min, max);
356 diffu = max.y - min.y;
357 diffv = max.z - min.z;
358
359 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
360 const aiVector3D& pos = mesh->mVertices[pnt];
361 out[pnt].Set((pos.y - min.y) / diffu,(pos.x - min.x) / diffv,0.0);
362 }
363 }
364 // slower code path in case the mapping axis is not one of the coordinate system axes
365 else
366 {
367 aiMatrix4x4 mTrafo;
368 aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo);
369 FindMeshCenterTransformed(mesh, center, min, max,mTrafo);
370 diffu = max.x - min.x;
371 diffv = max.z - min.z;
372
373 // again the same, except we're applying a transformation now
374 for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) {
375 const aiVector3D pos = mTrafo * mesh->mVertices[pnt];
376 out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0);
377 }
378 }
379
380 // shouldn't be necessary to remove UV seams ...
381}
382
383// ------------------------------------------------------------------------------------------------
384void ComputeUVMappingProcess::ComputeBoxMapping( aiMesh*, aiVector3D* )
385{
386 DefaultLogger::get()->error("Mapping type currently not implemented");
387}
388
389// ------------------------------------------------------------------------------------------------
390void ComputeUVMappingProcess::Execute( aiScene* pScene)
391{
392 DefaultLogger::get()->debug("GenUVCoordsProcess begin");
393 char buffer[1024];
394
395 if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT)
396 throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here");
397
398 std::list<MappingInfo> mappingStack;
399
400 /* Iterate through all materials and search for non-UV mapped textures
401 */
402 for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
403 {
404 mappingStack.clear();
405 aiMaterial* mat = pScene->mMaterials[i];
406 for (unsigned int a = 0; a < mat->mNumProperties;++a)
407 {
408 aiMaterialProperty* prop = mat->mProperties[a];
409 if (!::strcmp( prop->mKey.data, "$tex.mapping"))
410 {
411 aiTextureMapping& mapping = *((aiTextureMapping*)prop->mData);
412 if (aiTextureMapping_UV != mapping)
413 {
414 if (!DefaultLogger::isNullLogger())
415 {
416 ai_snprintf(buffer, 1024, "Found non-UV mapped texture (%s,%u). Mapping type: %s",
417 TextureTypeToString((aiTextureType)prop->mSemantic),prop->mIndex,
418 MappingTypeToString(mapping));
419
420 DefaultLogger::get()->info(buffer);
421 }
422
423 if (aiTextureMapping_OTHER == mapping)
424 continue;
425
426 MappingInfo info (mapping);
427
428 // Get further properties - currently only the major axis
429 for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2)
430 {
431 aiMaterialProperty* prop2 = mat->mProperties[a2];
432 if (prop2->mSemantic != prop->mSemantic || prop2->mIndex != prop->mIndex)
433 continue;
434
435 if ( !::strcmp( prop2->mKey.data, "$tex.mapaxis")) {
436 info.axis = *((aiVector3D*)prop2->mData);
437 break;
438 }
439 }
440
441 unsigned int idx( 99999999 );
442
443 // Check whether we have this mapping mode already
444 std::list<MappingInfo>::iterator it = std::find (mappingStack.begin(),mappingStack.end(), info);
445 if (mappingStack.end() != it)
446 {
447 idx = (*it).uv;
448 }
449 else
450 {
451 /* We have found a non-UV mapped texture. Now
452 * we need to find all meshes using this material
453 * that we can compute UV channels for them.
454 */
455 for (unsigned int m = 0; m < pScene->mNumMeshes;++m)
456 {
457 aiMesh* mesh = pScene->mMeshes[m];
458 unsigned int outIdx = 0;
459 if ( mesh->mMaterialIndex != i || ( outIdx = FindEmptyUVChannel(mesh) ) == UINT_MAX ||
460 !mesh->mNumVertices)
461 {
462 continue;
463 }
464
465 // Allocate output storage
466 aiVector3D* p = mesh->mTextureCoords[outIdx] = new aiVector3D[mesh->mNumVertices];
467
468 switch (mapping)
469 {
470 case aiTextureMapping_SPHERE:
471 ComputeSphereMapping(mesh,info.axis,p);
472 break;
473 case aiTextureMapping_CYLINDER:
474 ComputeCylinderMapping(mesh,info.axis,p);
475 break;
476 case aiTextureMapping_PLANE:
477 ComputePlaneMapping(mesh,info.axis,p);
478 break;
479 case aiTextureMapping_BOX:
480 ComputeBoxMapping(mesh,p);
481 break;
482 default:
483 ai_assert(false);
484 }
485 if (m && idx != outIdx)
486 {
487 DefaultLogger::get()->warn("UV index mismatch. Not all meshes assigned to "
488 "this material have equal numbers of UV channels. The UV index stored in "
489 "the material structure does therefore not apply for all meshes. ");
490 }
491 idx = outIdx;
492 }
493 info.uv = idx;
494 mappingStack.push_back(info);
495 }
496
497 // Update the material property list
498 mapping = aiTextureMapping_UV;
499 ((aiMaterial*)mat)->AddProperty(&idx,1,AI_MATKEY_UVWSRC(prop->mSemantic,prop->mIndex));
500 }
501 }
502 }
503 }
504 DefaultLogger::get()->debug("GenUVCoordsProcess finished");
505}
506