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 A helper class that processes texture transformations */
43
44
45
46#include <assimp/Importer.hpp>
47#include <assimp/postprocess.h>
48#include <assimp/DefaultLogger.hpp>
49#include <assimp/scene.h>
50
51#include "TextureTransform.h"
52#include "StringUtils.h"
53
54using namespace Assimp;
55
56// ------------------------------------------------------------------------------------------------
57// Constructor to be privately used by Importer
58TextureTransformStep::TextureTransformStep() :
59 configFlags()
60{
61 // nothing to do here
62}
63
64// ------------------------------------------------------------------------------------------------
65// Destructor, private as well
66TextureTransformStep::~TextureTransformStep()
67{
68 // nothing to do here
69}
70
71// ------------------------------------------------------------------------------------------------
72// Returns whether the processing step is present in the given flag field.
73bool TextureTransformStep::IsActive( unsigned int pFlags) const
74{
75 return (pFlags & aiProcess_TransformUVCoords) != 0;
76}
77
78// ------------------------------------------------------------------------------------------------
79// Setup properties
80void TextureTransformStep::SetupProperties(const Importer* pImp)
81{
82 configFlags = pImp->GetPropertyInteger(AI_CONFIG_PP_TUV_EVALUATE,AI_UVTRAFO_ALL);
83}
84
85// ------------------------------------------------------------------------------------------------
86void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info)
87{
88 /* This function tries to simplify the input UV transformation.
89 * That's very important as it allows us to reduce the number
90 * of output UV channels. The oder in which the transformations
91 * are applied is - as always - scaling, rotation, translation.
92 */
93
94 char szTemp[512];
95 int rounded = 0;
96
97
98 /* Optimize the rotation angle. That's slightly difficult as
99 * we have an inprecise floating-point number (when comparing
100 * UV transformations we'll take that into account by using
101 * an epsilon of 5 degrees). If there is a rotation value, we can't
102 * perform any further optimizations.
103 */
104 if (info.mRotation)
105 {
106 float out = info.mRotation;
107 if ((rounded = (int)(info.mRotation / (float)AI_MATH_TWO_PI)))
108 {
109 out -= rounded*(float)AI_MATH_PI;
110
111 ai_snprintf(szTemp, 512, "Texture coordinate rotation %f can be simplified to %f",info.mRotation,out);
112 DefaultLogger::get()->info(szTemp);
113 }
114
115 // Next step - convert negative rotation angles to positives
116 if (out < 0.f)
117 out = (float)AI_MATH_TWO_PI * 2 + out;
118
119 info.mRotation = out;
120 return;
121 }
122
123
124 /* Optimize UV translation in the U direction. To determine whether
125 * or not we can optimize we need to look at the requested mapping
126 * type (e.g. if mirroring is active there IS a difference between
127 * offset 2 and 3)
128 */
129 if ((rounded = (int)info.mTranslation.x)) {
130 float out = 0.0f;
131 szTemp[0] = 0;
132 if (aiTextureMapMode_Wrap == info.mapU) {
133 // Wrap - simple take the fraction of the field
134 out = info.mTranslation.x-(float)rounded;
135 ai_snprintf(szTemp, 512, "[w] UV U offset %f can be simplified to %f", info.mTranslation.x, out);
136 }
137 else if (aiTextureMapMode_Mirror == info.mapU && 1 != rounded) {
138 // Mirror
139 if (rounded % 2)
140 rounded--;
141 out = info.mTranslation.x-(float)rounded;
142
143 ai_snprintf(szTemp,512,"[m/d] UV U offset %f can be simplified to %f",info.mTranslation.x,out);
144 }
145 else if (aiTextureMapMode_Clamp == info.mapU || aiTextureMapMode_Decal == info.mapU) {
146 // Clamp - translations beyond 1,1 are senseless
147 ai_snprintf(szTemp,512,"[c] UV U offset %f can be clamped to 1.0f",info.mTranslation.x);
148
149 out = 1.f;
150 }
151 if (szTemp[0]) {
152 DefaultLogger::get()->info(szTemp);
153 info.mTranslation.x = out;
154 }
155 }
156
157 /* Optimize UV translation in the V direction. To determine whether
158 * or not we can optimize we need to look at the requested mapping
159 * type (e.g. if mirroring is active there IS a difference between
160 * offset 2 and 3)
161 */
162 if ((rounded = (int)info.mTranslation.y)) {
163 float out = 0.0f;
164 szTemp[0] = 0;
165 if (aiTextureMapMode_Wrap == info.mapV) {
166 // Wrap - simple take the fraction of the field
167 out = info.mTranslation.y-(float)rounded;
168 ::ai_snprintf(szTemp,512,"[w] UV V offset %f can be simplified to %f",info.mTranslation.y,out);
169 }
170 else if (aiTextureMapMode_Mirror == info.mapV && 1 != rounded) {
171 // Mirror
172 if (rounded % 2)
173 rounded--;
174 out = info.mTranslation.x-(float)rounded;
175
176 ::ai_snprintf(szTemp,512,"[m/d] UV V offset %f can be simplified to %f",info.mTranslation.y,out);
177 }
178 else if (aiTextureMapMode_Clamp == info.mapV || aiTextureMapMode_Decal == info.mapV) {
179 // Clamp - translations beyond 1,1 are senseless
180 ::ai_snprintf(szTemp,512,"[c] UV V offset %f canbe clamped to 1.0f",info.mTranslation.y);
181
182 out = 1.f;
183 }
184 if (szTemp[0]) {
185 DefaultLogger::get()->info(szTemp);
186 info.mTranslation.y = out;
187 }
188 }
189 return;
190}
191
192// ------------------------------------------------------------------------------------------------
193void UpdateUVIndex(const std::list<TTUpdateInfo>& l, unsigned int n)
194{
195 // Don't set if == 0 && wasn't set before
196 for (std::list<TTUpdateInfo>::const_iterator it = l.begin();it != l.end(); ++it) {
197 const TTUpdateInfo& info = *it;
198
199 if (info.directShortcut)
200 *info.directShortcut = n;
201 else if (!n)
202 {
203 info.mat->AddProperty<int>((int*)&n,1,AI_MATKEY_UVWSRC(info.semantic,info.index));
204 }
205 }
206}
207
208// ------------------------------------------------------------------------------------------------
209inline const char* MappingModeToChar(aiTextureMapMode map)
210{
211 if (aiTextureMapMode_Wrap == map)
212 return "-w";
213
214 if (aiTextureMapMode_Mirror == map)
215 return "-m";
216
217 return "-c";
218}
219
220// ------------------------------------------------------------------------------------------------
221void TextureTransformStep::Execute( aiScene* pScene)
222{
223 DefaultLogger::get()->debug("TransformUVCoordsProcess begin");
224
225
226 /* We build a per-mesh list of texture transformations we'll need
227 * to apply. To achieve this, we iterate through all materials,
228 * find all textures and get their transformations and UV indices.
229 * Then we search for all meshes using this material.
230 */
231 typedef std::list<STransformVecInfo> MeshTrafoList;
232 std::vector<MeshTrafoList> meshLists(pScene->mNumMeshes);
233
234 for (unsigned int i = 0; i < pScene->mNumMaterials;++i) {
235
236 aiMaterial* mat = pScene->mMaterials[i];
237 for (unsigned int a = 0; a < mat->mNumProperties;++a) {
238
239 aiMaterialProperty* prop = mat->mProperties[a];
240 if (!::strcmp( prop->mKey.data, "$tex.file")) {
241 STransformVecInfo info;
242
243 // Setup a shortcut structure to allow for a fast updating
244 // of the UV index later
245 TTUpdateInfo update;
246 update.mat = (aiMaterial*) mat;
247 update.semantic = prop->mSemantic;
248 update.index = prop->mIndex;
249
250 // Get textured properties and transform
251 for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2) {
252 aiMaterialProperty* prop2 = mat->mProperties[a2];
253 if (prop2->mSemantic != prop->mSemantic || prop2->mIndex != prop->mIndex) {
254 continue;
255 }
256
257 if ( !::strcmp( prop2->mKey.data, "$tex.uvwsrc")) {
258 info.uvIndex = *((int*)prop2->mData);
259
260 // Store a direct pointer for later use
261 update.directShortcut = (unsigned int*) prop2->mData;
262 }
263
264 else if ( !::strcmp( prop2->mKey.data, "$tex.mapmodeu")) {
265 info.mapU = *((aiTextureMapMode*)prop2->mData);
266 }
267 else if ( !::strcmp( prop2->mKey.data, "$tex.mapmodev")) {
268 info.mapV = *((aiTextureMapMode*)prop2->mData);
269 }
270 else if ( !::strcmp( prop2->mKey.data, "$tex.uvtrafo")) {
271 // ValidateDS should check this
272 ai_assert(prop2->mDataLength >= 20);
273 ::memcpy(&info.mTranslation.x,prop2->mData,sizeof(float)*5);
274
275 // Directly remove this property from the list
276 mat->mNumProperties--;
277 for (unsigned int a3 = a2; a3 < mat->mNumProperties;++a3) {
278 mat->mProperties[a3] = mat->mProperties[a3+1];
279 }
280
281 delete prop2;
282
283 // Warn: could be an underflow, but this does not invoke undefined behaviour
284 --a2;
285 }
286 }
287
288 // Find out which transformations are to be evaluated
289 if (!(configFlags & AI_UVTRAFO_ROTATION)) {
290 info.mRotation = 0.f;
291 }
292 if (!(configFlags & AI_UVTRAFO_SCALING)) {
293 info.mScaling = aiVector2D(1.f,1.f);
294 }
295 if (!(configFlags & AI_UVTRAFO_TRANSLATION)) {
296 info.mTranslation = aiVector2D(0.f,0.f);
297 }
298
299 // Do some preprocessing
300 PreProcessUVTransform(info);
301 info.uvIndex = std::min(info.uvIndex,AI_MAX_NUMBER_OF_TEXTURECOORDS -1u);
302
303 // Find out whether this material is used by more than
304 // one mesh. This will make our task much, much more difficult!
305 unsigned int cnt = 0;
306 for (unsigned int n = 0; n < pScene->mNumMeshes;++n) {
307 if (pScene->mMeshes[n]->mMaterialIndex == i)
308 ++cnt;
309 }
310
311 if (!cnt)
312 continue;
313 else if (1 != cnt) {
314 // This material is referenced by more than one mesh!
315 // So we need to make sure the UV index for the texture
316 // is identical for each of it ...
317 info.lockedPos = AI_TT_UV_IDX_LOCK_TBD;
318 }
319
320 // Get all corresponding meshes
321 for (unsigned int n = 0; n < pScene->mNumMeshes;++n) {
322 aiMesh* mesh = pScene->mMeshes[n];
323 if (mesh->mMaterialIndex != i || !mesh->mTextureCoords[0])
324 continue;
325
326 unsigned int uv = info.uvIndex;
327 if (!mesh->mTextureCoords[uv]) {
328 // If the requested UV index is not available, take the first one instead.
329 uv = 0;
330 }
331
332 if (mesh->mNumUVComponents[info.uvIndex] >= 3){
333 DefaultLogger::get()->warn("UV transformations on 3D mapping channels are not supported");
334 continue;
335 }
336
337 MeshTrafoList::iterator it;
338
339 // Check whether we have this transform setup already
340 for (it = meshLists[n].begin();it != meshLists[n].end(); ++it) {
341
342 if ((*it) == info && (*it).uvIndex == uv) {
343 (*it).updateList.push_back(update);
344 break;
345 }
346 }
347
348 if (it == meshLists[n].end()) {
349 meshLists[n].push_back(info);
350 meshLists[n].back().uvIndex = uv;
351 meshLists[n].back().updateList.push_back(update);
352 }
353 }
354 }
355 }
356 }
357
358 char buffer[1024]; // should be sufficiently large
359 unsigned int outChannels = 0, inChannels = 0, transformedChannels = 0;
360
361 // Now process all meshes. Important: we don't remove unreferenced UV channels.
362 // This is a job for the RemoveUnreferencedData-Step.
363 for (unsigned int q = 0; q < pScene->mNumMeshes;++q) {
364
365 aiMesh* mesh = pScene->mMeshes[q];
366 MeshTrafoList& trafo = meshLists[q];
367
368 inChannels += mesh->GetNumUVChannels();
369
370 if (!mesh->mTextureCoords[0] || trafo.empty() || (trafo.size() == 1 && trafo.begin()->IsUntransformed())) {
371 outChannels += mesh->GetNumUVChannels();
372 continue;
373 }
374
375 // Move untransformed UV channels to the first position in the list ....
376 // except if we need a new locked index which should be as small as possible
377 bool veto = false, need = false;
378 unsigned int cnt = 0;
379 unsigned int untransformed = 0;
380
381 MeshTrafoList::iterator it,it2;
382 for (it = trafo.begin();it != trafo.end(); ++it,++cnt) {
383
384 if (!(*it).IsUntransformed()) {
385 need = true;
386 }
387
388 if ((*it).lockedPos == AI_TT_UV_IDX_LOCK_TBD) {
389 // Lock this index and make sure it won't be changed
390 (*it).lockedPos = cnt;
391 veto = true;
392 continue;
393 }
394
395 if (!veto && it != trafo.begin() && (*it).IsUntransformed()) {
396 for (it2 = trafo.begin();it2 != it; ++it2) {
397 if (!(*it2).IsUntransformed())
398 break;
399 }
400 trafo.insert(it2,*it);
401 trafo.erase(it);
402 break;
403 }
404 }
405 if (!need)
406 continue;
407
408 // Find all that are not at their 'locked' position and move them to it.
409 // Conflicts are possible but quite unlikely.
410 cnt = 0;
411 for (it = trafo.begin();it != trafo.end(); ++it,++cnt) {
412 if ((*it).lockedPos != AI_TT_UV_IDX_LOCK_NONE && (*it).lockedPos != cnt) {
413 it2 = trafo.begin();unsigned int t = 0;
414 while (t != (*it).lockedPos)
415 ++it2;
416
417 if ((*it2).lockedPos != AI_TT_UV_IDX_LOCK_NONE) {
418 DefaultLogger::get()->error("Channel mismatch, can't compute all transformations properly [design bug]");
419 continue;
420 }
421
422 std::swap(*it2,*it);
423 if ((*it).lockedPos == untransformed)
424 untransformed = cnt;
425 }
426 }
427
428 // ... and add dummies for all unreferenced channels
429 // at the end of the list
430 bool ref[AI_MAX_NUMBER_OF_TEXTURECOORDS];
431 for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n)
432 ref[n] = (!mesh->mTextureCoords[n] ? true : false);
433
434 for (it = trafo.begin();it != trafo.end(); ++it)
435 ref[(*it).uvIndex] = true;
436
437 for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) {
438 if (ref[n])
439 continue;
440 trafo.push_back(STransformVecInfo());
441 trafo.back().uvIndex = n;
442 }
443
444 // Then check whether this list breaks the channel limit.
445 // The unimportant ones are at the end of the list, so
446 // it shouldn't be too worse if we remove them.
447 unsigned int size = (unsigned int)trafo.size();
448 if (size > AI_MAX_NUMBER_OF_TEXTURECOORDS) {
449
450 if (!DefaultLogger::isNullLogger()) {
451 ::ai_snprintf(buffer,1024,"%u UV channels required but just %u available",
452 static_cast<unsigned int>(trafo.size()),AI_MAX_NUMBER_OF_TEXTURECOORDS);
453
454 DefaultLogger::get()->error(buffer);
455 }
456 size = AI_MAX_NUMBER_OF_TEXTURECOORDS;
457 }
458
459
460 aiVector3D* old[AI_MAX_NUMBER_OF_TEXTURECOORDS];
461 for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n)
462 old[n] = mesh->mTextureCoords[n];
463
464 // Now continue and generate the output channels. Channels
465 // that we're not going to need later can be overridden.
466 it = trafo.begin();
467 for (unsigned int n = 0; n < trafo.size();++n,++it) {
468
469 if (n >= size) {
470 // Try to use an untransformed channel for all channels we threw over board
471 UpdateUVIndex((*it).updateList,untransformed);
472 continue;
473 }
474
475 outChannels++;
476
477 // Write to the log
478 if (!DefaultLogger::isNullLogger()) {
479 ::ai_snprintf(buffer,1024,"Mesh %u, channel %u: t(%.3f,%.3f), s(%.3f,%.3f), r(%.3f), %s%s",
480 q,n,
481 (*it).mTranslation.x,
482 (*it).mTranslation.y,
483 (*it).mScaling.x,
484 (*it).mScaling.y,
485 AI_RAD_TO_DEG( (*it).mRotation),
486 MappingModeToChar ((*it).mapU),
487 MappingModeToChar ((*it).mapV));
488
489 DefaultLogger::get()->info(buffer);
490 }
491
492 // Check whether we need a new buffer here
493 if (mesh->mTextureCoords[n]) {
494
495 it2 = it;++it2;
496 for (unsigned int m = n+1; m < size;++m, ++it2) {
497
498 if ((*it2).uvIndex == n){
499 it2 = trafo.begin();
500 break;
501 }
502 }
503 if (it2 == trafo.begin()){
504 mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices];
505 }
506 }
507 else mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices];
508
509 aiVector3D* src = old[(*it).uvIndex];
510 aiVector3D* dest, *end;
511 dest = mesh->mTextureCoords[n];
512
513 ai_assert(NULL != src);
514
515 // Copy the data to the destination array
516 if (dest != src)
517 ::memcpy(dest,src,sizeof(aiVector3D)*mesh->mNumVertices);
518
519 end = dest + mesh->mNumVertices;
520
521 // Build a transformation matrix and transform all UV coords with it
522 if (!(*it).IsUntransformed()) {
523 const aiVector2D& trl = (*it).mTranslation;
524 const aiVector2D& scl = (*it).mScaling;
525
526 // fixme: simplify ..
527 ++transformedChannels;
528 aiMatrix3x3 matrix;
529
530 aiMatrix3x3 m2,m3,m4,m5;
531
532 m4.a1 = scl.x;
533 m4.b2 = scl.y;
534
535 m2.a3 = m2.b3 = 0.5f;
536 m3.a3 = m3.b3 = -0.5f;
537
538 if ((*it).mRotation > AI_TT_ROTATION_EPSILON )
539 aiMatrix3x3::RotationZ((*it).mRotation,matrix);
540
541 m5.a3 += trl.x; m5.b3 += trl.y;
542 matrix = m2 * m4 * matrix * m3 * m5;
543
544 for (src = dest; src != end; ++src) { /* manual homogenious divide */
545 src->z = 1.f;
546 *src = matrix * *src;
547 src->x /= src->z;
548 src->y /= src->z;
549 src->z = 0.f;
550 }
551 }
552
553 // Update all UV indices
554 UpdateUVIndex((*it).updateList,n);
555 }
556 }
557
558 // Print some detailed statistics into the log
559 if (!DefaultLogger::isNullLogger()) {
560
561 if (transformedChannels) {
562 ::ai_snprintf(buffer,1024,"TransformUVCoordsProcess end: %u output channels (in: %u, modified: %u)",
563 outChannels,inChannels,transformedChannels);
564
565 DefaultLogger::get()->info(buffer);
566 }
567 else DefaultLogger::get()->debug("TransformUVCoordsProcess finished");
568 }
569}
570
571
572