123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- /*
- Open Asset Import Library (assimp)
- ----------------------------------------------------------------------
- Copyright (c) 2006-2020, assimp team
- All rights reserved.
- Redistribution and use of this software in source and binary forms,
- with or without modification, are permitted provided that the
- following conditions are met:
- * Redistributions of source code must retain the above
- copyright notice, this list of conditions and the
- following disclaimer.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the
- following disclaimer in the documentation and/or other
- materials provided with the distribution.
- * Neither the name of the assimp team, nor the names of its
- contributors may be used to endorse or promote products
- derived from this software without specific prior
- written permission of the assimp team.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- ----------------------------------------------------------------------
- */
- /** @file A helper class that processes texture transformations */
- #include <assimp/Importer.hpp>
- #include <assimp/postprocess.h>
- #include <assimp/DefaultLogger.hpp>
- #include <assimp/scene.h>
- #include "TextureTransform.h"
- #include <assimp/StringUtils.h>
- using namespace Assimp;
- // ------------------------------------------------------------------------------------------------
- // Constructor to be privately used by Importer
- TextureTransformStep::TextureTransformStep() :
- configFlags()
- {
- // nothing to do here
- }
- // ------------------------------------------------------------------------------------------------
- // Destructor, private as well
- TextureTransformStep::~TextureTransformStep()
- {
- // nothing to do here
- }
- // ------------------------------------------------------------------------------------------------
- // Returns whether the processing step is present in the given flag field.
- bool TextureTransformStep::IsActive( unsigned int pFlags) const
- {
- return (pFlags & aiProcess_TransformUVCoords) != 0;
- }
- // ------------------------------------------------------------------------------------------------
- // Setup properties
- void TextureTransformStep::SetupProperties(const Importer* pImp)
- {
- configFlags = pImp->GetPropertyInteger(AI_CONFIG_PP_TUV_EVALUATE,AI_UVTRAFO_ALL);
- }
- // ------------------------------------------------------------------------------------------------
- void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info)
- {
- /* This function tries to simplify the input UV transformation.
- * That's very important as it allows us to reduce the number
- * of output UV channels. The order in which the transformations
- * are applied is - as always - scaling, rotation, translation.
- */
- int rounded;
- char szTemp[512];
- /* Optimize the rotation angle. That's slightly difficult as
- * we have an inprecise floating-point number (when comparing
- * UV transformations we'll take that into account by using
- * an epsilon of 5 degrees). If there is a rotation value, we can't
- * perform any further optimizations.
- */
- if (info.mRotation)
- {
- float out = info.mRotation;
- rounded = static_cast<int>((info.mRotation / static_cast<float>(AI_MATH_TWO_PI)));
- if (rounded)
- {
- out -= rounded * static_cast<float>(AI_MATH_PI);
- ASSIMP_LOG_INFO_F("Texture coordinate rotation ", info.mRotation, " can be simplified to ", out);
- }
- // Next step - convert negative rotation angles to positives
- if (out < 0.f)
- out = (float)AI_MATH_TWO_PI * 2 + out;
- info.mRotation = out;
- return;
- }
- /* Optimize UV translation in the U direction. To determine whether
- * or not we can optimize we need to look at the requested mapping
- * type (e.g. if mirroring is active there IS a difference between
- * offset 2 and 3)
- */
- rounded = (int)info.mTranslation.x;
- if (rounded) {
- float out = 0.0f;
- szTemp[0] = 0;
- if (aiTextureMapMode_Wrap == info.mapU) {
- // Wrap - simple take the fraction of the field
- out = info.mTranslation.x-(float)rounded;
- ai_snprintf(szTemp, 512, "[w] UV U offset %f can be simplified to %f", info.mTranslation.x, out);
- }
- else if (aiTextureMapMode_Mirror == info.mapU && 1 != rounded) {
- // Mirror
- if (rounded % 2)
- rounded--;
- out = info.mTranslation.x-(float)rounded;
- ai_snprintf(szTemp,512,"[m/d] UV U offset %f can be simplified to %f",info.mTranslation.x,out);
- }
- else if (aiTextureMapMode_Clamp == info.mapU || aiTextureMapMode_Decal == info.mapU) {
- // Clamp - translations beyond 1,1 are senseless
- ai_snprintf(szTemp,512,"[c] UV U offset %f can be clamped to 1.0f",info.mTranslation.x);
- out = 1.f;
- }
- if (szTemp[0]) {
- ASSIMP_LOG_INFO(szTemp);
- info.mTranslation.x = out;
- }
- }
- /* Optimize UV translation in the V direction. To determine whether
- * or not we can optimize we need to look at the requested mapping
- * type (e.g. if mirroring is active there IS a difference between
- * offset 2 and 3)
- */
- rounded = (int)info.mTranslation.y;
- if (rounded) {
- float out = 0.0f;
- szTemp[0] = 0;
- if (aiTextureMapMode_Wrap == info.mapV) {
- // Wrap - simple take the fraction of the field
- out = info.mTranslation.y-(float)rounded;
- ::ai_snprintf(szTemp,512,"[w] UV V offset %f can be simplified to %f",info.mTranslation.y,out);
- }
- else if (aiTextureMapMode_Mirror == info.mapV && 1 != rounded) {
- // Mirror
- if (rounded % 2)
- rounded--;
- out = info.mTranslation.x-(float)rounded;
- ::ai_snprintf(szTemp,512,"[m/d] UV V offset %f can be simplified to %f",info.mTranslation.y,out);
- }
- else if (aiTextureMapMode_Clamp == info.mapV || aiTextureMapMode_Decal == info.mapV) {
- // Clamp - translations beyond 1,1 are senseless
- ::ai_snprintf(szTemp,512,"[c] UV V offset %f canbe clamped to 1.0f",info.mTranslation.y);
- out = 1.f;
- }
- if (szTemp[0]) {
- ASSIMP_LOG_INFO(szTemp);
- info.mTranslation.y = out;
- }
- }
- }
- // ------------------------------------------------------------------------------------------------
- void UpdateUVIndex(const std::list<TTUpdateInfo>& l, unsigned int n)
- {
- // Don't set if == 0 && wasn't set before
- for (std::list<TTUpdateInfo>::const_iterator it = l.begin();it != l.end(); ++it) {
- const TTUpdateInfo& info = *it;
- if (info.directShortcut)
- *info.directShortcut = n;
- else if (!n)
- {
- info.mat->AddProperty<int>((int*)&n,1,AI_MATKEY_UVWSRC(info.semantic,info.index));
- }
- }
- }
- // ------------------------------------------------------------------------------------------------
- inline const char* MappingModeToChar(aiTextureMapMode map)
- {
- if (aiTextureMapMode_Wrap == map)
- return "-w";
- if (aiTextureMapMode_Mirror == map)
- return "-m";
- return "-c";
- }
- // ------------------------------------------------------------------------------------------------
- void TextureTransformStep::Execute( aiScene* pScene)
- {
- ASSIMP_LOG_DEBUG("TransformUVCoordsProcess begin");
- /* We build a per-mesh list of texture transformations we'll need
- * to apply. To achieve this, we iterate through all materials,
- * find all textures and get their transformations and UV indices.
- * Then we search for all meshes using this material.
- */
- typedef std::list<STransformVecInfo> MeshTrafoList;
- std::vector<MeshTrafoList> meshLists(pScene->mNumMeshes);
- for (unsigned int i = 0; i < pScene->mNumMaterials;++i) {
- aiMaterial* mat = pScene->mMaterials[i];
- for (unsigned int a = 0; a < mat->mNumProperties;++a) {
- aiMaterialProperty* prop = mat->mProperties[a];
- if (!::strcmp( prop->mKey.data, "$tex.file")) {
- STransformVecInfo info;
- // Setup a shortcut structure to allow for a fast updating
- // of the UV index later
- TTUpdateInfo update;
- update.mat = (aiMaterial*) mat;
- update.semantic = prop->mSemantic;
- update.index = prop->mIndex;
- // Get textured properties and transform
- for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2) {
- aiMaterialProperty* prop2 = mat->mProperties[a2];
- if (prop2->mSemantic != prop->mSemantic || prop2->mIndex != prop->mIndex) {
- continue;
- }
- if ( !::strcmp( prop2->mKey.data, "$tex.uvwsrc")) {
- info.uvIndex = *((int*)prop2->mData);
- // Store a direct pointer for later use
- update.directShortcut = (unsigned int*) prop2->mData;
- }
- else if ( !::strcmp( prop2->mKey.data, "$tex.mapmodeu")) {
- info.mapU = *((aiTextureMapMode*)prop2->mData);
- }
- else if ( !::strcmp( prop2->mKey.data, "$tex.mapmodev")) {
- info.mapV = *((aiTextureMapMode*)prop2->mData);
- }
- else if ( !::strcmp( prop2->mKey.data, "$tex.uvtrafo")) {
- // ValidateDS should check this
- ai_assert(prop2->mDataLength >= 20);
- ::memcpy(&info.mTranslation.x,prop2->mData,sizeof(float)*5);
- // Directly remove this property from the list
- mat->mNumProperties--;
- for (unsigned int a3 = a2; a3 < mat->mNumProperties;++a3) {
- mat->mProperties[a3] = mat->mProperties[a3+1];
- }
- delete prop2;
- // Warn: could be an underflow, but this does not invoke undefined behaviour
- --a2;
- }
- }
- // Find out which transformations are to be evaluated
- if (!(configFlags & AI_UVTRAFO_ROTATION)) {
- info.mRotation = 0.f;
- }
- if (!(configFlags & AI_UVTRAFO_SCALING)) {
- info.mScaling = aiVector2D(1.f,1.f);
- }
- if (!(configFlags & AI_UVTRAFO_TRANSLATION)) {
- info.mTranslation = aiVector2D(0.f,0.f);
- }
- // Do some preprocessing
- PreProcessUVTransform(info);
- info.uvIndex = std::min(info.uvIndex,AI_MAX_NUMBER_OF_TEXTURECOORDS -1u);
- // Find out whether this material is used by more than
- // one mesh. This will make our task much, much more difficult!
- unsigned int cnt = 0;
- for (unsigned int n = 0; n < pScene->mNumMeshes;++n) {
- if (pScene->mMeshes[n]->mMaterialIndex == i)
- ++cnt;
- }
- if (!cnt)
- continue;
- else if (1 != cnt) {
- // This material is referenced by more than one mesh!
- // So we need to make sure the UV index for the texture
- // is identical for each of it ...
- info.lockedPos = AI_TT_UV_IDX_LOCK_TBD;
- }
- // Get all corresponding meshes
- for (unsigned int n = 0; n < pScene->mNumMeshes;++n) {
- aiMesh* mesh = pScene->mMeshes[n];
- if (mesh->mMaterialIndex != i || !mesh->mTextureCoords[0])
- continue;
- unsigned int uv = info.uvIndex;
- if (!mesh->mTextureCoords[uv]) {
- // If the requested UV index is not available, take the first one instead.
- uv = 0;
- }
- if (mesh->mNumUVComponents[info.uvIndex] >= 3){
- ASSIMP_LOG_WARN("UV transformations on 3D mapping channels are not supported");
- continue;
- }
- MeshTrafoList::iterator it;
- // Check whether we have this transform setup already
- for (it = meshLists[n].begin();it != meshLists[n].end(); ++it) {
- if ((*it) == info && (*it).uvIndex == uv) {
- (*it).updateList.push_back(update);
- break;
- }
- }
- if (it == meshLists[n].end()) {
- meshLists[n].push_back(info);
- meshLists[n].back().uvIndex = uv;
- meshLists[n].back().updateList.push_back(update);
- }
- }
- }
- }
- }
- char buffer[1024]; // should be sufficiently large
- unsigned int outChannels = 0, inChannels = 0, transformedChannels = 0;
- // Now process all meshes. Important: we don't remove unreferenced UV channels.
- // This is a job for the RemoveUnreferencedData-Step.
- for (unsigned int q = 0; q < pScene->mNumMeshes;++q) {
- aiMesh* mesh = pScene->mMeshes[q];
- MeshTrafoList& trafo = meshLists[q];
- inChannels += mesh->GetNumUVChannels();
- if (!mesh->mTextureCoords[0] || trafo.empty() || (trafo.size() == 1 && trafo.begin()->IsUntransformed())) {
- outChannels += mesh->GetNumUVChannels();
- continue;
- }
- // Move untransformed UV channels to the first position in the list ....
- // except if we need a new locked index which should be as small as possible
- bool veto = false, need = false;
- unsigned int cnt = 0;
- unsigned int untransformed = 0;
- MeshTrafoList::iterator it,it2;
- for (it = trafo.begin();it != trafo.end(); ++it,++cnt) {
- if (!(*it).IsUntransformed()) {
- need = true;
- }
- if ((*it).lockedPos == AI_TT_UV_IDX_LOCK_TBD) {
- // Lock this index and make sure it won't be changed
- (*it).lockedPos = cnt;
- veto = true;
- continue;
- }
- if (!veto && it != trafo.begin() && (*it).IsUntransformed()) {
- for (it2 = trafo.begin();it2 != it; ++it2) {
- if (!(*it2).IsUntransformed())
- break;
- }
- trafo.insert(it2,*it);
- trafo.erase(it);
- break;
- }
- }
- if (!need)
- continue;
- // Find all that are not at their 'locked' position and move them to it.
- // Conflicts are possible but quite unlikely.
- cnt = 0;
- for (it = trafo.begin();it != trafo.end(); ++it,++cnt) {
- if ((*it).lockedPos != AI_TT_UV_IDX_LOCK_NONE && (*it).lockedPos != cnt) {
- it2 = trafo.begin();unsigned int t = 0;
- while (t != (*it).lockedPos)
- ++it2;
- if ((*it2).lockedPos != AI_TT_UV_IDX_LOCK_NONE) {
- ASSIMP_LOG_ERROR("Channel mismatch, can't compute all transformations properly [design bug]");
- continue;
- }
- std::swap(*it2,*it);
- if ((*it).lockedPos == untransformed)
- untransformed = cnt;
- }
- }
- // ... and add dummies for all unreferenced channels
- // at the end of the list
- bool ref[AI_MAX_NUMBER_OF_TEXTURECOORDS];
- for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n)
- ref[n] = !mesh->mTextureCoords[n];
- for (it = trafo.begin();it != trafo.end(); ++it)
- ref[(*it).uvIndex] = true;
- for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) {
- if (ref[n])
- continue;
- trafo.push_back(STransformVecInfo());
- trafo.back().uvIndex = n;
- }
- // Then check whether this list breaks the channel limit.
- // The unimportant ones are at the end of the list, so
- // it shouldn't be too worse if we remove them.
- unsigned int size = (unsigned int)trafo.size();
- if (size > AI_MAX_NUMBER_OF_TEXTURECOORDS) {
- if (!DefaultLogger::isNullLogger()) {
- ASSIMP_LOG_ERROR_F(static_cast<unsigned int>(trafo.size()), " UV channels required but just ",
- AI_MAX_NUMBER_OF_TEXTURECOORDS, " available");
- }
- size = AI_MAX_NUMBER_OF_TEXTURECOORDS;
- }
- aiVector3D* old[AI_MAX_NUMBER_OF_TEXTURECOORDS];
- for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n)
- old[n] = mesh->mTextureCoords[n];
- // Now continue and generate the output channels. Channels
- // that we're not going to need later can be overridden.
- it = trafo.begin();
- for (unsigned int n = 0; n < trafo.size();++n,++it) {
- if (n >= size) {
- // Try to use an untransformed channel for all channels we threw over board
- UpdateUVIndex((*it).updateList,untransformed);
- continue;
- }
- outChannels++;
- // Write to the log
- if (!DefaultLogger::isNullLogger()) {
- ::ai_snprintf(buffer,1024,"Mesh %u, channel %u: t(%.3f,%.3f), s(%.3f,%.3f), r(%.3f), %s%s",
- q,n,
- (*it).mTranslation.x,
- (*it).mTranslation.y,
- (*it).mScaling.x,
- (*it).mScaling.y,
- AI_RAD_TO_DEG( (*it).mRotation),
- MappingModeToChar ((*it).mapU),
- MappingModeToChar ((*it).mapV));
- ASSIMP_LOG_INFO(buffer);
- }
- // Check whether we need a new buffer here
- if (mesh->mTextureCoords[n]) {
- it2 = it;++it2;
- for (unsigned int m = n+1; m < size;++m, ++it2) {
- if ((*it2).uvIndex == n){
- it2 = trafo.begin();
- break;
- }
- }
- if (it2 == trafo.begin()){
- mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices];
- }
- }
- else mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices];
- aiVector3D* src = old[(*it).uvIndex];
- aiVector3D* dest, *end;
- dest = mesh->mTextureCoords[n];
- ai_assert(nullptr != src);
- // Copy the data to the destination array
- if (dest != src)
- ::memcpy(dest,src,sizeof(aiVector3D)*mesh->mNumVertices);
- end = dest + mesh->mNumVertices;
- // Build a transformation matrix and transform all UV coords with it
- if (!(*it).IsUntransformed()) {
- const aiVector2D& trl = (*it).mTranslation;
- const aiVector2D& scl = (*it).mScaling;
- // fixme: simplify ..
- ++transformedChannels;
- aiMatrix3x3 matrix;
- aiMatrix3x3 m2,m3,m4,m5;
- m4.a1 = scl.x;
- m4.b2 = scl.y;
- m2.a3 = m2.b3 = 0.5f;
- m3.a3 = m3.b3 = -0.5f;
- if ((*it).mRotation > AI_TT_ROTATION_EPSILON )
- aiMatrix3x3::RotationZ((*it).mRotation,matrix);
- m5.a3 += trl.x; m5.b3 += trl.y;
- matrix = m2 * m4 * matrix * m3 * m5;
- for (src = dest; src != end; ++src) { /* manual homogenious divide */
- src->z = 1.f;
- *src = matrix * *src;
- src->x /= src->z;
- src->y /= src->z;
- src->z = 0.f;
- }
- }
- // Update all UV indices
- UpdateUVIndex((*it).updateList,n);
- }
- }
- // Print some detailed statistics into the log
- if (!DefaultLogger::isNullLogger()) {
- if (transformedChannels) {
- ASSIMP_LOG_INFO_F("TransformUVCoordsProcess end: ", outChannels, " output channels (in: ", inChannels, ", modified: ", transformedChannels,")");
- } else {
- ASSIMP_LOG_DEBUG("TransformUVCoordsProcess finished");
- }
- }
- }
|