Coverage Report

Created: 2024-08-02 07:04

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