1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4 
5 Copyright (c) 2006-2012, 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 "AssimpPCH.h"
45 #include "ComputeUVMappingProcess.h"
46 #include "ProcessHelper.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((math::atan2 (diff.z, diff.y) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
210 				(math::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((math::atan2 (diff.x, diff.z) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
218 				(math::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((math::atan2 (diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
226 				(math::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((math::atan2 (diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
238 				(math::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 = (math::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 = (math::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 = (math::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 = (math::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* /*mesh*/, aiVector3D* /*out*/)
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 						sprintf(buffer, "Found non-UV mapped texture (%s,%i). 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;
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