vrshoot

view libs/assimp/ComputeUVMappingProcess.cpp @ 0:b2f14e535253

initial commit
author John Tsiombikas <nuclear@member.fsf.org>
date Sat, 01 Feb 2014 19:58:19 +0200
parents
children
line source
1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
5 Copyright (c) 2006-2012, assimp team
6 All rights reserved.
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:
12 * Redistributions of source code must retain the above
13 copyright notice, this list of conditions and the
14 following disclaimer.
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.
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.
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.
38 ----------------------------------------------------------------------
39 */
41 /** @file GenUVCoords step */
44 #include "AssimpPCH.h"
45 #include "ComputeUVMappingProcess.h"
46 #include "ProcessHelper.h"
48 using namespace Assimp;
50 namespace {
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 }
58 // ------------------------------------------------------------------------------------------------
59 // Constructor to be privately used by Importer
60 ComputeUVMappingProcess::ComputeUVMappingProcess()
61 {
62 // nothing to do here
63 }
65 // ------------------------------------------------------------------------------------------------
66 // Destructor, private as well
67 ComputeUVMappingProcess::~ComputeUVMappingProcess()
68 {
69 // nothing to do here
70 }
72 // ------------------------------------------------------------------------------------------------
73 // Returns whether the processing step is present in the given flag field.
74 bool ComputeUVMappingProcess::IsActive( unsigned int pFlags) const
75 {
76 return (pFlags & aiProcess_GenUVCoords) != 0;
77 }
79 // ------------------------------------------------------------------------------------------------
80 // Check whether a ray intersects a plane and find the intersection point
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;
89 pos = ray.pos + (ray.dir * h);
90 return true;
91 }
93 // ------------------------------------------------------------------------------------------------
94 // Find the first empty UV channel in a 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;
100 DefaultLogger::get()->error("Unable to compute UV coordinates, no free UV slot found");
101 return UINT_MAX;
102 }
104 // ------------------------------------------------------------------------------------------------
105 // Try to remove UV seams
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.
112 const static float LOWER_LIMIT = 0.1f;
113 const static float UPPER_LIMIT = 0.9f;
115 const static float LOWER_EPSILON = 10e-3f;
116 const static float UPPER_EPSILON = 1.f-10e-3f;
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
123 unsigned int small = face.mNumIndices, large = small;
124 bool zero = false, one = false, round_to_zero = false;
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;
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;
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;
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;
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 }
183 // ------------------------------------------------------------------------------------------------
184 void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
185 {
186 aiVector3D center, min, max;
187 FindMeshCenter(mesh, center, min, max);
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) {
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 (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 (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 (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);
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 }
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 }
249 // ------------------------------------------------------------------------------------------------
250 void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
251 {
252 aiVector3D center, min, max;
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;
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];
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;
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];
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;
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];
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;
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];
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 }
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 }
323 // ------------------------------------------------------------------------------------------------
324 void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
325 {
326 float diffu,diffv;
327 aiVector3D center, min, max;
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;
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;
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;
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;
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 }
379 // shouldn't be necessary to remove UV seams ...
380 }
382 // ------------------------------------------------------------------------------------------------
383 void ComputeUVMappingProcess::ComputeBoxMapping(aiMesh* /*mesh*/, aiVector3D* /*out*/)
384 {
385 DefaultLogger::get()->error("Mapping type currently not implemented");
386 }
388 // ------------------------------------------------------------------------------------------------
389 void ComputeUVMappingProcess::Execute( aiScene* pScene)
390 {
391 DefaultLogger::get()->debug("GenUVCoordsProcess begin");
392 char buffer[1024];
394 if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT)
395 throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here");
397 std::list<MappingInfo> mappingStack;
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));
419 DefaultLogger::get()->info(buffer);
420 }
422 if (aiTextureMapping_OTHER == mapping)
423 continue;
425 MappingInfo info (mapping);
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;
434 if ( !::strcmp( prop2->mKey.data, "$tex.mapaxis")) {
435 info.axis = *((aiVector3D*)prop2->mData);
436 break;
437 }
438 }
440 unsigned int idx;
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 }
464 // Allocate output storage
465 aiVector3D* p = mesh->mTextureCoords[outIdx] = new aiVector3D[mesh->mNumVertices];
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 }
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 }