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