diff options
Diffstat (limited to 'indra/newview')
| -rw-r--r-- | indra/newview/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | indra/newview/gltf/asset.cpp | 198 | ||||
| -rw-r--r-- | indra/newview/gltf/asset.h | 13 | ||||
| -rw-r--r-- | indra/newview/gltf/buffer_util.h | 11 | ||||
| -rw-r--r-- | indra/newview/gltf/llgltfloader.cpp | 1710 | ||||
| -rw-r--r-- | indra/newview/gltf/llgltfloader.h | 203 | ||||
| -rw-r--r-- | indra/newview/gltfscenemanager.cpp | 2 | ||||
| -rw-r--r-- | indra/newview/llfilepicker.cpp | 6 | ||||
| -rw-r--r-- | indra/newview/llfloatermodelpreview.cpp | 3 | ||||
| -rw-r--r-- | indra/newview/llmaterialeditor.cpp | 3 | ||||
| -rw-r--r-- | indra/newview/llmodelpreview.cpp | 50 | ||||
| -rw-r--r-- | indra/newview/llskinningutil.cpp | 9 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/floater_model_preview.xml | 20 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/notifications.xml | 5 | 
14 files changed, 2123 insertions, 112 deletions
| diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 98151e2f4d..dee3d5ed59 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -76,6 +76,7 @@ set(viewer_SOURCE_FILES      gltf/accessor.cpp      gltf/primitive.cpp      gltf/animation.cpp +    gltf/llgltfloader.cpp      groupchatlistener.cpp      llaccountingcostmanager.cpp      llaisapi.cpp @@ -746,6 +747,7 @@ set(viewer_HEADER_FILES      gltf/buffer_util.h      gltf/primitive.h      gltf/animation.h +    gltf/llgltfloader.h      llaccountingcost.h      llaccountingcostmanager.h      llaisapi.h diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index c210b9c61d..8c9f77686a 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -50,6 +50,10 @@ namespace LL              "KHR_texture_transform"          }; +        static std::unordered_set<std::string> ExtensionsIgnored = { +            "KHR_materials_pbrSpecularGlossiness" +        }; +          Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode)          {              if (alpha_mode == "OPAQUE") @@ -472,11 +476,14 @@ void Asset::update()              for (auto& image : mImages)              { -                if (image.mTexture.notNull()) -                { // HACK - force texture to be loaded full rez -                    // TODO: calculate actual vsize -                    image.mTexture->addTextureStats(2048.f * 2048.f); -                    image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); +                if (image.mLoadIntoTexturePipe) +                { +                    if (image.mTexture.notNull()) +                    { // HACK - force texture to be loaded full rez +                        // TODO: calculate actual vsize +                        image.mTexture->addTextureStats(2048.f * 2048.f); +                        image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); +                    }                  }              }          } @@ -486,18 +493,23 @@ void Asset::update()  bool Asset::prep()  {      LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; -    // check required extensions and fail if not supported -    bool unsupported = false; +    // check required extensions      for (auto& extension : mExtensionsRequired)      {          if (ExtensionsSupported.find(extension) == ExtensionsSupported.end())          { -            LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; -            unsupported = true; +            if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end()) +            { +                LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; +                mUnsupportedExtensions.push_back(extension); +            } +            else +            { +                mIgnoredExtensions.push_back(extension); +            }          }      } - -    if (unsupported) +    if (mUnsupportedExtensions.size() > 0)      {          return false;      } @@ -513,7 +525,7 @@ bool Asset::prep()      for (auto& image : mImages)      { -        if (!image.prep(*this)) +        if (!image.prep(*this, mLoadIntoVRAM))          {              return false;          } @@ -542,102 +554,106 @@ bool Asset::prep()              return false;          }      } +    if (mLoadIntoVRAM) +    { +        // prepare vertex buffers -    // prepare vertex buffers - -    // material count is number of materials + 1 for default material -    U32 mat_count = (U32) mMaterials.size() + 1; - -    if (LLGLSLShader::sCurBoundShaderPtr == nullptr) -    { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer -        gDebugProgram.bind(); -    } +        // material count is number of materials + 1 for default material +        U32 mat_count = (U32) mMaterials.size() + 1; -    for (S32 double_sided = 0; double_sided < 2; ++double_sided) -    { -        RenderData& rd = mRenderData[double_sided]; -        for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) -        { -            rd.mBatches[i].resize(mat_count); +        if (LLGLSLShader::sCurBoundShaderPtr == nullptr) +        { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer +            gDebugProgram.bind();          } -        // for each material -        for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) +        for (S32 double_sided = 0; double_sided < 2; ++double_sided)          { -            // for each shader variant -            U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; -            U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; - -            S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; -            if (ds_mat != double_sided) +            RenderData& rd = mRenderData[double_sided]; +            for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)              { -                continue; +                rd.mBatches[i].resize(mat_count);              } -            for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) +            // for each material +            for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id)              { -                U32 attribute_mask = 0; -                // for each mesh -                for (auto& mesh : mMeshes) +                // for each shader variant +                U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; +                U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + +                S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; +                if (ds_mat != double_sided)                  { -                    // for each primitive -                    for (auto& primitive : mesh.mPrimitives) +                    continue; +                } + +                for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) +                { +                    U32 attribute_mask = 0; +                    // for each mesh +                    for (auto& mesh : mMeshes)                      { -                        if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) +                        // for each primitive +                        for (auto& primitive : mesh.mPrimitives)                          { -                            // accumulate vertex and index counts -                            primitive.mVertexOffset = vertex_count[variant]; -                            primitive.mIndexOffset = index_count[variant]; +                            if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) +                            { +                                // accumulate vertex and index counts +                                primitive.mVertexOffset = vertex_count[variant]; +                                primitive.mIndexOffset = index_count[variant]; -                            vertex_count[variant] += primitive.getVertexCount(); -                            index_count[variant] += primitive.getIndexCount(); +                                vertex_count[variant] += primitive.getVertexCount(); +                                index_count[variant] += primitive.getIndexCount(); -                            // all primitives of a given variant and material should all have the same attribute mask -                            llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); -                            attribute_mask |= primitive.mAttributeMask; +                                // all primitives of a given variant and material should all have the same attribute mask +                                llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); +                                attribute_mask |= primitive.mAttributeMask; +                            }                          }                      } -                } -                // allocate vertex buffer and pack it -                if (vertex_count[variant] > 0) -                { -                    U32 mat_idx = mat_id + 1; -                    LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); +                    // allocate vertex buffer and pack it +                    if (vertex_count[variant] > 0) +                    { +                        U32 mat_idx = mat_id + 1; +                        #if 0 +                        LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); -                    rd.mBatches[variant][mat_idx].mVertexBuffer = vb; -                    vb->allocateBuffer(vertex_count[variant], -                        index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used -                    vb->setBuffer(); +                        rd.mBatches[variant][mat_idx].mVertexBuffer = vb; +                        vb->allocateBuffer(vertex_count[variant], +                            index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used +                        vb->setBuffer(); -                    for (auto& mesh : mMeshes) -                    { -                        for (auto& primitive : mesh.mPrimitives) +                        for (auto& mesh : mMeshes)                          { -                            if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) +                            for (auto& primitive : mesh.mPrimitives)                              { -                                primitive.upload(vb); +                                if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) +                                { +                                    primitive.upload(vb); +                                }                              }                          } -                    } -                    vb->unmapBuffer(); +                        vb->unmapBuffer(); -                    vb->unbind(); +                        vb->unbind(); +                        #endif +                    }                  }              }          } -    } -    // sanity check that all primitives have a vertex buffer -    for (auto& mesh : mMeshes) -    { -        for (auto& primitive : mesh.mPrimitives) +        // sanity check that all primitives have a vertex buffer +        for (auto& mesh : mMeshes)          { -            llassert(primitive.mVertexBuffer.notNull()); +            for (auto& primitive : mesh.mPrimitives) +            { +                //llassert(primitive.mVertexBuffer.notNull()); +            }          }      } - +    #if 0      // build render batches      for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)      { @@ -664,6 +680,7 @@ bool Asset::prep()              }          }      } +    #endif      return true;  } @@ -672,9 +689,10 @@ Asset::Asset(const Value& src)      *this = src;  } -bool Asset::load(std::string_view filename) +bool Asset::load(std::string_view filename, bool loadIntoVRAM)  {      LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; +    mLoadIntoVRAM = loadIntoVRAM;      mFilename = filename;      std::string ext = gDirUtilp->getExtension(mFilename); @@ -692,7 +710,7 @@ bool Asset::load(std::string_view filename)          }          else if (ext == "glb")          { -            return loadBinary(str); +            return loadBinary(str, mLoadIntoVRAM);          }          else          { @@ -709,8 +727,9 @@ bool Asset::load(std::string_view filename)      return false;  } -bool Asset::loadBinary(const std::string& data) +bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM)  { +    mLoadIntoVRAM = loadIntoVRAM;      // load from binary gltf      const U8* ptr = (const U8*)data.data();      const U8* end = ptr + data.size(); @@ -935,8 +954,9 @@ void Asset::eraseBufferView(S32 bufferView)  LLViewerFetchedTexture* fetch_texture(const LLUUID& id); -bool Image::prep(Asset& asset) +bool Image::prep(Asset& asset, bool loadIntoVRAM)  { +    mLoadIntoTexturePipe = loadIntoVRAM;      LLUUID id;      if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull())      { // loaded from an asset, fetch the texture from the asset system @@ -951,12 +971,12 @@ bool Image::prep(Asset& asset)      { // embedded in a buffer, load the texture from the buffer          BufferView& bufferView = asset.mBufferViews[mBufferView];          Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; - -        U8* data = buffer.mData.data() + bufferView.mByteOffset; - -        mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); - -        if (mTexture.isNull()) +        if (mLoadIntoTexturePipe) +        { +            U8* data = buffer.mData.data() + bufferView.mByteOffset; +            mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); +        } +        else if (mTexture.isNull() && mLoadIntoTexturePipe)          {              LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL;              LL_WARNS("GLTF") << "  image: " << mName << LL_ENDL; @@ -971,12 +991,12 @@ bool Image::prep(Asset& asset)          std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri;          LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file); -        if (tracking_id.notNull()) +        if (tracking_id.notNull() && mLoadIntoTexturePipe)          {              LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id);              mTexture = LLViewerTextureManager::getFetchedTexture(world_id);          } -        else +        else if (mLoadIntoTexturePipe)          {              LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL;              LL_WARNS("GLTF") << "  image: " << mName << LL_ENDL; @@ -991,7 +1011,7 @@ bool Image::prep(Asset& asset)          return false;      } -    if (!asset.mFilename.empty()) +    if (!asset.mFilename.empty() && mLoadIntoTexturePipe)      { // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload          mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW);          mTexture->forceToSaveRawImage(0, F32_MAX); diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 27821659db..b9554d753c 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -286,6 +286,7 @@ namespace LL              void serialize(boost::json::object& dst) const;          }; +        // Image is for images that we want to load for the given asset.  This acts as an interface into the viewer's texture pipe.          class Image          {          public: @@ -301,6 +302,8 @@ namespace LL              S32 mBits = -1;              S32 mPixelType = -1; +            bool mLoadIntoTexturePipe = false; +              LLPointer<LLViewerFetchedTexture> mTexture;              const Image& operator=(const Value& src); @@ -316,7 +319,7 @@ namespace LL              // preserve only uri and name              void clearData(Asset& asset); -            bool prep(Asset& asset); +            bool prep(Asset& asset, bool loadIntoVRAM);          };          // Render Batch -- vertex buffer and list of primitives to render using @@ -391,6 +394,10 @@ namespace LL              // UBO for storing material data              U32 mMaterialsUBO = 0; +            bool mLoadIntoVRAM = false; + +            std::vector<std::string> mUnsupportedExtensions; +            std::vector<std::string> mIgnoredExtensions;              // prepare for first time use              bool prep(); @@ -428,12 +435,12 @@ namespace LL              // accepts .gltf and .glb files              // Any existing data will be lost              // returns result of prep() on success -            bool load(std::string_view filename); +            bool load(std::string_view filename, bool loadIntoVRAM);              // load .glb contents from memory              // data - binary contents of .glb file              // returns result of prep() on success -            bool loadBinary(const std::string& data); +            bool loadBinary(const std::string& data, bool loadIntoVRAM);              const Asset& operator=(const Value& src);              void serialize(boost::json::object& dst) const; diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index ef9bba8128..be36c5e90b 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -159,6 +159,12 @@ namespace LL          }          template<> +        inline void copyVec3<F32, LLColor4U>(F32* src, LLColor4U& dst) +        { +            dst.set((U8)(src[0] * 255.f), (U8)(src[1] * 255.f), (U8)(src[2] * 255.f), 255); +        } + +        template<>          inline void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)          {              dst.set((U8)(src[0]), (U8)(src[1]), (U8)(src[2]), 255); @@ -369,6 +375,11 @@ namespace LL          template<class T>          inline void copy(Asset& asset, Accessor& accessor, LLStrider<T>& dst)          { +            if (accessor.mBufferView == INVALID_INDEX) +            { +                LL_WARNS("GLTF") << "Invalid buffer" << LL_ENDL; +                return; +            }              const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView];              const Buffer& buffer = asset.mBuffers[bufferView.mBuffer];              const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp new file mode 100644 index 0000000000..7ae255e054 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.cpp @@ -0,0 +1,1710 @@ +/** + * @file LLGLTFLoader.cpp + * @brief LLGLTFLoader class implementation + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "llgltfloader.h" +#include "meshoptimizer.h" +#include <glm/gtc/packing.hpp> + +// Import & define single-header gltf import/export lib +#define TINYGLTF_IMPLEMENTATION +#define TINYGLTF_USE_CPP14  // default is C++ 11 + +// tinygltf by default loads image files using STB +#define STB_IMAGE_IMPLEMENTATION +// to use our own image loading: +// 1. replace this definition with TINYGLTF_NO_STB_IMAGE +// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) + +// tinygltf saves image files using STB +#define STB_IMAGE_WRITE_IMPLEMENTATION +// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) + +// Additionally, disable inclusion of STB header files entirely with +// TINYGLTF_NO_INCLUDE_STB_IMAGE +// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE +#include "tinygltf/tiny_gltf.h" + + +// TODO: includes inherited from dae loader.  Validate / prune + +#include "llsdserialize.h" +#include "lljoint.h" +#include "llbase64.h" +#include "lldir.h" + +#include "llmatrix4a.h" + +#include <boost/regex.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <fstream> + +static const std::string lod_suffix[LLModel::NUM_LODS] = +{ +    "_LOD0", +    "_LOD1", +    "_LOD2", +    "", +    "_PHYS", +}; + +// Premade rotation matrix, GLTF is Y-up while SL is Z-up +static const glm::mat4 coord_system_rotation( +    1.f, 0.f, 0.f, 0.f, +    0.f, 0.f, 1.f, 0.f, +    0.f, -1.f, 0.f, 0.f, +    0.f, 0.f, 0.f, 1.f +); + + +static const glm::mat4 coord_system_rotationxy( +    0.f, 1.f, 0.f, 0.f, +    -1.f, 0.f, 0.f, 0.f, +    0.f, 0.f, 1.f, 0.f, +    0.f, 0.f, 0.f, 1.f +); + +LLGLTFLoader::LLGLTFLoader(std::string filename, +    S32                                 lod, +    LLModelLoader::load_callback_t      load_cb, +    LLModelLoader::joint_lookup_func_t  joint_lookup_func, +    LLModelLoader::texture_load_func_t  texture_load_func, +    LLModelLoader::state_callback_t     state_cb, +    void *                              opaque_userdata, +    JointTransformMap &                 jointTransformMap, +    JointNameSet &                      jointsFromNodes, +    std::map<std::string, std::string> &jointAliasMap, +    U32                                 maxJointsPerMesh, +    U32                                 modelLimit, +    std::vector<LLJointData>            viewer_skeleton) //, +    //bool                                preprocess) +    : LLModelLoader( filename, +                     lod, +                     load_cb, +                     joint_lookup_func, +                     texture_load_func, +                     state_cb, +                     opaque_userdata, +                     jointTransformMap, +                     jointsFromNodes, +                     jointAliasMap, +                     maxJointsPerMesh ) +    , mGeneratedModelLimit(modelLimit) +    , mViewerJointData(viewer_skeleton) +{ +} + +LLGLTFLoader::~LLGLTFLoader() {} + +bool LLGLTFLoader::OpenFile(const std::string &filename) +{ +    tinygltf::TinyGLTF loader; +    std::string filename_lc(filename); +    LLStringUtil::toLower(filename_lc); + +    mGltfLoaded = mGLTFAsset.load(filename, false); + +    if (!mGltfLoaded) +    { +        notifyUnsupportedExtension(true); + +        for (const auto& buffer : mGLTFAsset.mBuffers) +        { +            if (buffer.mByteLength > 0 && buffer.mData.empty()) +            { +                bool bin_file = buffer.mUri.ends_with(".bin"); +                LLSD args; +                args["Message"] = bin_file ? "ParsingErrorMissingBufferBin" : "ParsingErrorMissingBuffer"; +                args["BUFFER_NAME"] = buffer.mName; +                args["BUFFER_URI"] = buffer.mUri; +                mWarningsArray.append(args); +            } +        } +        setLoadState(ERROR_PARSING); +        return false; +    } + +    notifyUnsupportedExtension(false); + +    bool meshesLoaded = parseMeshes(); + +    setLoadState(DONE); + +    return meshesLoaded; +} + +void LLGLTFLoader::addModelToScene( +    LLModel* pModel, +    U32 submodel_limit, +    const LLMatrix4& transformation, +    const LLVolumeParams& volume_params, +    const material_map& mats) +{ +    U32 volume_faces = pModel->getNumVolumeFaces(); + +    // Side-steps all manner of issues when splitting models +    // and matching lower LOD materials to base models +    // +    pModel->sortVolumeFacesByMaterialName(); + +    int submodelID = 0; + +    // remove all faces that definitely won't fit into one model and submodel limit +    U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES; +    if (face_limit < volume_faces) +    { +        pModel->setNumVolumeFaces(face_limit); +    } + +    LLVolume::face_list_t remainder; +    std::vector<LLModel*> ready_models; +    LLModel* current_model = pModel; +    do +    { +        current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); + +        volume_faces = static_cast<U32>(remainder.size()); + +        // Don't add to scene yet because weights and materials aren't ready. +        // Just save it +        ready_models.push_back(current_model); + +        // If we have left-over volume faces, create another model +        // to absorb them. +        if (volume_faces) +        { +            LLModel* next = new LLModel(volume_params, 0.f); +            next->ClearFacesAndMaterials(); +            next->mSubmodelID = ++submodelID; +            next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; +            next->getVolumeFaces() = remainder; +            next->mNormalizedScale = current_model->mNormalizedScale; +            next->mNormalizedTranslation = current_model->mNormalizedTranslation; +            next->mSkinWeights = current_model->mSkinWeights; +            next->mPosition = current_model->mPosition; + +            const LLMeshSkinInfo& current_skin_info = current_model->mSkinInfo; +            LLMeshSkinInfo& next_skin_info = next->mSkinInfo; +            next_skin_info.mJointNames = current_skin_info.mJointNames; +            next_skin_info.mJointNums = current_skin_info.mJointNums; +            next_skin_info.mBindShapeMatrix = current_skin_info.mBindShapeMatrix; +            next_skin_info.mInvBindMatrix = current_skin_info.mInvBindMatrix; +            next_skin_info.mAlternateBindMatrix = current_skin_info.mAlternateBindMatrix; +            next_skin_info.mPelvisOffset = current_skin_info.mPelvisOffset; + + +            if (current_model->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES) +            { +                next->mMaterialList.assign(current_model->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, current_model->mMaterialList.end()); +                current_model->mMaterialList.resize(LL_SCULPT_MESH_MAX_FACES); +            } + +            current_model = next; +        } + +        remainder.clear(); + +    } while (volume_faces); + +    for (auto model : ready_models) +    { +        // remove unused/redundant vertices +        model->remapVolumeFaces(); + +        mModelList.push_back(model); + +        std::map<std::string, LLImportMaterial> materials; +        for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) +        { +            material_map::const_iterator found = mats.find(model->mMaterialList[i]); +            if (found != mats.end()) +            { +                materials[model->mMaterialList[i]] = found->second; +            } +            else +            { +                materials[model->mMaterialList[i]] = LLImportMaterial(); +            } +        } +        mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); +        stretch_extents(model, transformation); +    } +} + +bool LLGLTFLoader::parseMeshes() +{ +    if (!mGltfLoaded) return false; + +    // 2022-04 DJH Volume params from dae example. TODO understand PCODE +    LLVolumeParams volume_params; +    volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + +    mTransform.setIdentity(); + +    for (auto& node : mGLTFAsset.mNodes) +    { +        // Make node matrix valid for correct transformation +        node.makeMatrixValid(); +    } + +    if (mGLTFAsset.mSkins.size() > 0) +    { +        checkForXYrotation(mGLTFAsset.mSkins[0]); +        populateJointGroups(); +    } + +    // Populate the joints from skins first. +    // There's not many skins - and you can pretty easily iterate through the nodes from that. +    for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) +    { +        populateJointsFromSkin(i); +    } + +    // Track how many times each mesh name has been used +    std::map<std::string, S32> mesh_name_counts; +    U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; + +    // Check if we have scenes defined +    if (!mGLTFAsset.mScenes.empty()) +    { +        // Process the default scene (or first scene if no default) +        S32 scene_idx = mGLTFAsset.mScene >= 0 ? mGLTFAsset.mScene : 0; + +        if (scene_idx < mGLTFAsset.mScenes.size()) +        { +            const LL::GLTF::Scene& scene = mGLTFAsset.mScenes[scene_idx]; + +            LL_INFOS("GLTF_IMPORT") << "Processing scene " << scene_idx << " with " << scene.mNodes.size() << " root nodes" << LL_ENDL; + +            // Process all root nodes defined in the scene +            for (S32 root_idx : scene.mNodes) +            { +                if (root_idx >= 0 && root_idx < static_cast<S32>(mGLTFAsset.mNodes.size())) +                { +                    processNodeHierarchy(root_idx, mesh_name_counts, submodel_limit, volume_params); +                } +            } +        } +    } +    else +    { +        LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + +        LLSD args; +        args["Message"] = "NoScenesFound"; +        mWarningsArray.append(args); +        return false; +    } + +    // Check total model count against limit +    U32 total_models = static_cast<U32>(mModelList.size()); +    if (total_models > mGeneratedModelLimit) +    { +        LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models +                                << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL; + +        LLSD args; +        args["Message"] = "TooManyMeshParts"; +        args["PART_COUNT"] = static_cast<S32>(total_models); +        args["LIMIT"] = static_cast<S32>(mGeneratedModelLimit); +        mWarningsArray.append(args); +        return false; +    } + +    return true; +} + +void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params) +{ +    if (node_idx < 0 || node_idx >= static_cast<S32>(mGLTFAsset.mNodes.size())) +        return; + +    auto& node = mGLTFAsset.mNodes[node_idx]; + +    LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" +                            << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") +                            << " - children: " << node.mChildren.size() << LL_ENDL; + +    // Process this node's mesh if it has one +    if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) +    { +        LLMatrix4    transformation; +        material_map mats; + +        LLModel* pModel = new LLModel(volume_params, 0.f); +        auto& mesh = mGLTFAsset.mMeshes[node.mMesh]; + +        // Get base mesh name and track usage +        std::string base_name = mesh.mName; +        if (base_name.empty()) +        { +            base_name = "mesh_" + std::to_string(node.mMesh); +        } + +        S32 instance_count = mesh_name_counts[base_name]++; + +        if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && +            (LLModel::NO_ERRORS == pModel->getStatus()) && +            validate_model(pModel)) +        { +            mTransform.setIdentity(); +            transformation = mTransform; + +            // adjust the transformation to compensate for mesh normalization +            LLVector3 mesh_scale_vector; +            LLVector3 mesh_translation_vector; +            pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + +            LLMatrix4 mesh_translation; +            mesh_translation.setTranslation(mesh_translation_vector); +            mesh_translation *= transformation; +            transformation = mesh_translation; + +            LLMatrix4 mesh_scale; +            mesh_scale.initScale(mesh_scale_vector); +            mesh_scale *= transformation; +            transformation = mesh_scale; + +            if (node.mSkin >= 0) +            { +                // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh +                // into the coordinate space of the joints. +                // In GLTF, this matrix is omitted, and it is assumed that this transform is either +                // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. +                // +                // TODO: There appears to be missing rotation when joints rotate the model +                // or inverted bind matrices are missing inherited rotation +                // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly +                // prior to skinning) + +                pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); +                LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; +            } + +            if (transformation.determinant() < 0) +            { // negative scales are not supported +                LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform.  domInstance_geometry: " +                           << pModel->mLabel << LL_ENDL; +                LLSD args; +                args["Message"] = "NegativeScaleNormTrans"; +                args["LABEL"]   = pModel->mLabel; +                mWarningsArray.append(args); +            } + +            addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); +            mats.clear(); +        } +        else +        { +            setLoadState(ERROR_MODEL + pModel->getStatus()); +            delete pModel; +            return; +        } +    } +    else if (node.mMesh >= 0) +    { +        // Log invalid mesh reference +        LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " (" << node.mName +                                << ") references invalid mesh " << node.mMesh +                                << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + +        LLSD args; +        args["Message"] = "InvalidMeshReference"; +        args["NODE_NAME"] = node.mName; +        args["MESH_INDEX"] = node.mMesh; +        args["TOTAL_MESHES"] = static_cast<S32>(mGLTFAsset.mMeshes.size()); +        mWarningsArray.append(args); +    } + +    // Process all children recursively +    for (S32 child_idx : node.mChildren) +    { +        processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params); +    } +} + +void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const +{ +    if (node_index < 0 || node_index >= static_cast<S32>(asset.mNodes.size())) +    { +        combined_transform = glm::mat4(1.0f); +        return; +    } + +    const auto& node = asset.mNodes[node_index]; + +    // Ensure the node's matrix is valid +    const_cast<LL::GLTF::Node&>(node).makeMatrixValid(); + +    // Start with this node's transform +    combined_transform = node.mMatrix; + +    // Find and apply parent transform if it exists +    for (size_t i = 0; i < asset.mNodes.size(); ++i) +    { +        const auto& potential_parent = asset.mNodes[i]; +        auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), node_index); + +        if (it != potential_parent.mChildren.end()) +        { +            // Found parent - recursively get its combined transform and apply it +            glm::mat4 parent_transform; +            computeCombinedNodeTransform(asset, static_cast<S32>(i), parent_transform); +            combined_transform = parent_transform * combined_transform; +            return; // Early exit - a node can only have one parent +        } +    } +} + +bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const +{ +    const std::string& legal_name = mJointNames[gltf_skin_idx][gltf_joint_idx]; +    if (legal_name.empty()) +    { +        llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 +        return false; +    } +    skin_info.mJointNames.push_back(legal_name); +    skin_info.mJointNums.push_back(-1); + +    // In scope of same skin multiple meshes reuse same bind matrices +    skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[gltf_skin_idx][gltf_joint_idx]); +    skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[gltf_skin_idx][gltf_joint_idx]); + +    return true; +} + +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) +{ +    // Set the requested label for the floater display and uploading +    pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + +    // Create unique model name +    std::string base_name = mesh.mName; +    if (base_name.empty()) +    { +        S32 mesh_index = static_cast<S32>(&mesh - &mGLTFAsset.mMeshes[0]); +        base_name = "mesh_" + std::to_string(mesh_index); +    } + +    LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; + +    if (instance_count > 0) +    { +        pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); +    } +    else +    { +        pModel->mLabel = base_name; +    } + +    pModel->ClearFacesAndMaterials(); + +    S32 skinIdx = nodeno.mSkin; + +    // Compute final combined transform matrix (hierarchy + coordinate rotation) +    S32 node_index = static_cast<S32>(&nodeno - &mGLTFAsset.mNodes[0]); +    glm::mat4 hierarchy_transform; +    computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform); + +    // Combine transforms: coordinate rotation applied to hierarchy transform +    glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; +    if (mApplyXYRotation) +    { +        final_transform = coord_system_rotationxy * final_transform; +    } + +    // Check if we have a negative scale (flipped coordinate system) +    bool hasNegativeScale = glm::determinant(final_transform) < 0.0f; + +    // Pre-compute normal transform matrix (transpose of inverse of upper-left 3x3) +    const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(final_transform))); + +    // Mark unsuported joints with '-1' so that they won't get added into weights +    // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. +    std::vector<S32> gltf_joint_index_use; +    if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx) +    { +        LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; + +        size_t jointCnt = gltf_skin.mJoints.size(); +        gltf_joint_index_use.resize(jointCnt); + +        for (size_t i = 0; i < jointCnt; ++i) +        { +            if (mJointNames[i].empty()) +            { +                // This might need to hold a substitute index +                gltf_joint_index_use[i] = -1; // mark as unsupported +            } +        } +    } + +    for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) +    { +        const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; + +        // So primitives already have all of the data we need for a given face in SL land. +        // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call +        // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 +        LLVolumeFace face; +        std::vector<GLTFVertex> vertices; + +        LLImportMaterial impMat; +        impMat.mDiffuseColor = LLColor4::white; // Default color + +        // Process material if available +        if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) +        { +            LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + +            // Set diffuse color from base color factor +            impMat.mDiffuseColor = LLColor4( +                material->mPbrMetallicRoughness.mBaseColorFactor[0], +                material->mPbrMetallicRoughness.mBaseColorFactor[1], +                material->mPbrMetallicRoughness.mBaseColorFactor[2], +                material->mPbrMetallicRoughness.mBaseColorFactor[3] +            ); + +            // Process base color texture if it exists +            if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) +            { +                S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; +                if (texIndex < mGLTFAsset.mTextures.size()) +                { +                    S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; +                    if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) +                    { +                        LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + +                        // Use URI as texture file name +                        if (!image.mUri.empty()) +                        { +                            // URI might be a remote URL or a local path +                            std::string filename = image.mUri; + +                            // Extract just the filename from the URI +                            size_t pos = filename.find_last_of("/\\"); +                            if (pos != std::string::npos) +                            { +                                filename = filename.substr(pos + 1); +                            } + +                            // Store the texture filename +                            impMat.mDiffuseMapFilename = filename; +                            impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; + +                            LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename +                                << " for material: " << material->mName << LL_ENDL; + +                            LLSD args; +                            args["Message"] = "TextureFound"; +                            args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; +                            args["MATERIAL_NAME"] = material->mName; +                            mWarningsArray.append(args); + +                            // If the image has a texture loaded already, use it +                            if (image.mTexture.notNull()) +                            { +                                impMat.setDiffuseMap(image.mTexture->getID()); +                                LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; +                            } +                            else +                            { +                                // Texture will be loaded later through the callback system +                                LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; +                            } +                        } +                        else if (image.mTexture.notNull()) +                        { +                            // No URI but we have a texture, use it directly +                            impMat.setDiffuseMap(image.mTexture->getID()); +                            LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; +                        } +                        else if (image.mBufferView >= 0) +                        { +                            // For embedded textures (no URI but has buffer data) +                            std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); +                            if (!temp_filename.empty()) +                            { +                                impMat.mDiffuseMapFilename = temp_filename; +                                impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; +                            } +                        } +                    } +                } +            } +        } + +        if (prim.getIndexCount() % 3 != 0) +        { +            LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx +                << ": Invalid index count " << prim.getIndexCount() +                << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; + +            LLSD args; +            args["Message"] = "InvalidGeometryNonTriangulated"; +            args["MESH_NAME"] = mesh.mName; +            args["PRIMITIVE_INDEX"] = static_cast<S32>(prim_idx); +            args["INDEX_COUNT"] = static_cast<S32>(prim.getIndexCount()); +            mWarningsArray.append(args); +            return false; // Skip this primitive +        } + +        // Apply the global scale and center offset to all vertices +        for (U32 i = 0; i < prim.getVertexCount(); i++) +        { +            // Use pre-computed final_transform +            glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); +            glm::vec4 transformed_pos = final_transform * pos; + +            GLTFVertex vert; +            vert.position = glm::vec3(transformed_pos); + +            if (!prim.mNormals.empty()) +            { +                // Use pre-computed normal_transform +                glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); +                vert.normal = glm::normalize(normal_transform * normal_vec); +            } +            else +            { +                // Use default normal (pointing up in model space) +                vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); +                LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; +            } + +            vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + +            if (skinIdx >= 0) +            { +                vert.weights = glm::vec4(prim.mWeights[i]); + +                auto accessorIdx = prim.mAttributes.at("JOINTS_0"); +                LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; +                if (accessorIdx >= 0) +                { +                    auto accessor = mGLTFAsset.mAccessors[accessorIdx]; +                    componentType = accessor.mComponentType; +                } + +                // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. +                // Detect and unpack accordingly. +                if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) +                { +                    auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); +                    vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); +                } +                else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) +                { +                    vert.joints = glm::unpackUint4x16(prim.mJoints[i]); +                } +                else +                { +                    vert.joints = glm::zero<glm::u16vec4>(); +                    vert.weights = glm::zero<glm::vec4>(); +                } +            } +            vertices.push_back(vert); +        } + +        // Check for empty vertex array before processing +        if (vertices.empty()) +        { +            LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive " << prim_idx << " in model " << mesh.mName << LL_ENDL; +            LLSD args; +            args["Message"] = "EmptyVertexArray"; +            args["MESH_NAME"] = mesh.mName; +            args["PRIMITIVE_INDEX"] = static_cast<S32>(prim_idx); +            args["INDEX_COUNT"] = static_cast<S32>(prim.getIndexCount()); +            mWarningsArray.append(args); +            return false; // Skip this primitive +        } + +        std::vector<LLVolumeFace::VertexData> faceVertices; +        glm::vec3 min = glm::vec3(FLT_MAX); +        glm::vec3 max = glm::vec3(-FLT_MAX); + +        for (U32 i = 0; i < vertices.size(); i++) +        { +            LLVolumeFace::VertexData vert; + +            // Update min/max bounds +            if (i == 0) +            { +                min = max = vertices[i].position; +            } +            else +            { +                min.x = std::min(min.x, vertices[i].position.x); +                min.y = std::min(min.y, vertices[i].position.y); +                min.z = std::min(min.z, vertices[i].position.z); +                max.x = std::max(max.x, vertices[i].position.x); +                max.y = std::max(max.y, vertices[i].position.y); +                max.z = std::max(max.z, vertices[i].position.z); +            } + +            LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); +            LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); +            vert.setPosition(position); +            vert.setNormal(normal); +            vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); +            faceVertices.push_back(vert); + +            if (skinIdx >= 0) +            { +                // create list of weights that influence this vertex +                LLModel::weight_list weight_list; + +                // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count) +                // don't reindex them yet, more indexes will be removed +                // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be +                // 'empty' ones +                if (gltf_joint_index_use[vertices[i].joints.x] >= 0 +                    && vertices[i].weights.x > 0.f) +                { +                    weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); +                    gltf_joint_index_use[vertices[i].joints.x]++; +                } +                if (gltf_joint_index_use[vertices[i].joints.y] >= 0 +                    && vertices[i].weights.y > 0.f) +                { +                    weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); +                    gltf_joint_index_use[vertices[i].joints.y]++; +                } +                if (gltf_joint_index_use[vertices[i].joints.z] >= 0 +                    && vertices[i].weights.z > 0.f) +                { +                    weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); +                    gltf_joint_index_use[vertices[i].joints.z]++; +                } +                if (gltf_joint_index_use[vertices[i].joints.w] >= 0 +                    && vertices[i].weights.w > 0.f) +                { +                    weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); +                    gltf_joint_index_use[vertices[i].joints.w]++; +                } + +                std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + +                std::vector<LLModel::JointWeight> wght; +                F32                               total = 0.f; + +                for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j) +                { +                    // take up to 4 most significant weights +                    // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. +                    wght.push_back(weight_list[j]); +                    total += weight_list[j].mWeight; +                } + +                if (total != 0.f) +                { +                    F32 scale = 1.f / total; +                    if (scale != 1.f) +                    { // normalize weights +                        for (U32 j = 0; j < wght.size(); ++j) +                        { +                            wght[j].mWeight *= scale; +                        } +                    } +                } + +                if (wght.size() > 0) +                { +                    pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; +                } +            } +        } + +        // Create a unique material name for this primitive +        std::string materialName; +        if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) +        { +            LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; +            materialName = material->mName; + +            if (materialName.empty()) +            { +                materialName = "mat" + std::to_string(prim.mMaterial); +            } +        } +        else +        { +            materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); +        } +        mats[materialName] = impMat; + +        // Indices handling +        if (faceVertices.size() >= USHRT_MAX) +        { +            // Will have to remap 32 bit indices into 16 bit indices +            // For the sake of simplicity build vector of 32 bit indices first +            std::vector<U32> indices_32; +            for (U32 i = 0; i < prim.getIndexCount(); i += 3) +            { +                // When processing indices, flip winding order if needed +                if (hasNegativeScale) +                { +                    // Flip winding order for negative scale +                    indices_32.push_back(prim.mIndexArray[i]); +                    indices_32.push_back(prim.mIndexArray[i + 2]); // Swap these two +                    indices_32.push_back(prim.mIndexArray[i + 1]); +                } +                else +                { +                    indices_32.push_back(prim.mIndexArray[i]); +                    indices_32.push_back(prim.mIndexArray[i + 1]); +                    indices_32.push_back(prim.mIndexArray[i + 2]); +                } +            } + +            // remap 32 bit into multiple 16 bit ones +            std::vector<U16> indices_16; +            std::vector<S64> vertices_remap; // should it be a point map? +            vertices_remap.resize(faceVertices.size(), -1); +            std::vector<LLVolumeFace::VertexData> face_verts; +            min = glm::vec3(FLT_MAX); +            max = glm::vec3(-FLT_MAX); +            for (size_t idx = 0; idx < indices_32.size(); idx++) +            { +                size_t vert_index = indices_32[idx]; +                if (vertices_remap[vert_index] == -1) +                { +                    // First encounter, add it +                    size_t new_vert_idx = face_verts.size(); +                    vertices_remap[vert_index] = (S64)new_vert_idx; +                    face_verts.push_back(faceVertices[vert_index]); +                    vert_index = new_vert_idx; + +                    // Update min/max bounds +                    const LLVector4a& vec = face_verts[new_vert_idx].getPosition(); +                    if (new_vert_idx == 0) +                    { +                        min.x = vec[0]; +                        min.y = vec[1]; +                        min.z = vec[2]; +                        max = min; +                    } +                    else +                    { +                        min.x = std::min(min.x, vec[0]); +                        min.y = std::min(min.y, vec[1]); +                        min.z = std::min(min.z, vec[2]); +                        max.x = std::max(max.x, vec[0]); +                        max.y = std::max(max.y, vec[1]); +                        max.z = std::max(max.z, vec[2]); +                    } +                } +                else +                { +                    // already in vector, get position +                    vert_index = (size_t)vertices_remap[vert_index]; +                } +                indices_16.push_back((U16)vert_index); + +                if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532) +                { +                    LLVolumeFace face; +                    face.fillFromLegacyData(face_verts, indices_16); +                    face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); +                    face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); +                    pModel->getVolumeFaces().push_back(face); +                    pModel->getMaterialList().push_back(materialName); + +                    std::fill(vertices_remap.begin(), vertices_remap.end(), -1); +                    indices_16.clear(); +                    face_verts.clear(); + +                    min = glm::vec3(FLT_MAX); +                    max = glm::vec3(-FLT_MAX); +                } +            } +            if (indices_16.size() > 0 && face_verts.size() > 0) +            { +                LLVolumeFace face; +                face.fillFromLegacyData(face_verts, indices_16); +                face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); +                face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); +                pModel->getVolumeFaces().push_back(face); +                pModel->getMaterialList().push_back(materialName); +            } +        } +        else +        { +            // can use indices directly +            std::vector<U16> indices; +            for (U32 i = 0; i < prim.getIndexCount(); i += 3) +            { +                // When processing indices, flip winding order if needed +                if (hasNegativeScale) +                { +                    // Flip winding order for negative scale +                    indices.push_back(prim.mIndexArray[i]); +                    indices.push_back(prim.mIndexArray[i + 2]); // Swap these two +                    indices.push_back(prim.mIndexArray[i + 1]); +                } +                else +                { +                    indices.push_back(prim.mIndexArray[i]); +                    indices.push_back(prim.mIndexArray[i + 1]); +                    indices.push_back(prim.mIndexArray[i + 2]); +                } +            } + +            face.fillFromLegacyData(faceVertices, indices); +            face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); +            face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + +            pModel->getVolumeFaces().push_back(face); +            pModel->getMaterialList().push_back(materialName); +        } +    } + +    // Call normalizeVolumeFacesAndWeights to compute proper extents +    pModel->normalizeVolumeFacesAndWeights(); + +    // Fill joint names, bind matrices and remap weight indices +    if (skinIdx >= 0) +    { +        LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; +        LLMeshSkinInfo& skin_info = pModel->mSkinInfo; +        S32 valid_joints_count = mValidJointsCount[skinIdx]; + +        S32 replacement_index = 0; +        std::vector<S32> gltfindex_to_joitindex_map; +        size_t jointCnt = gltf_skin.mJoints.size(); +        gltfindex_to_joitindex_map.resize(jointCnt); + +        if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) +        { +            std::map<std::string, S32> goup_use_count; +            // Assume that 'Torso' group is always in use since that's what everything else is attached to +            goup_use_count["Torso"] = 1; +            // Note that Collisions and Extra groups are all over the place, might want to include them from the start +            // or add individual when parents are added + +            // Check which groups are in use +            for (size_t i = 0; i < jointCnt; ++i) +            { +                std::string& joint_name = mJointNames[skinIdx][i]; +                if (!joint_name.empty()) +                { +                    if (gltf_joint_index_use[i] > 0) +                    { +                        const JointGroups &group = mJointGroups[joint_name]; +                        // Joint in use, increment it's groups +                        goup_use_count[group.mGroup]++; +                        goup_use_count[group.mParentGroup]++; +                    } +                } +            } + +            // 1. add joints that are in use directly +            for (size_t i = 0; i < jointCnt; ++i) +            { +                // Process joint name and idnex +                S32 joint = gltf_skin.mJoints[i]; +                if (gltf_joint_index_use[i] <= 0) +                { +                    // unsupported (-1) joint, drop it +                    // unused (0) joint, drop it +                    continue; +                } + +                if (addJointToModelSkin(skin_info, skinIdx, i)) +                { +                    gltfindex_to_joitindex_map[i] = replacement_index++; +                } +            } + +            // 2. add joints from groups that this model's joints belong to +            // It's perfectly valid to have more joints than is in use +            // Ex: sandals that make your legs digitigrade despite not skining to +            // knees or the like. +            // Todo: sort and add by usecount +            for (size_t i = 0; i < jointCnt; ++i) +            { +                S32 joint = gltf_skin.mJoints[i]; +                if (gltf_joint_index_use[i] != 0) +                { +                    // this step needs only joints that have zero uses +                    continue; +                } +                if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) +                { +                    break; +                } +                const std::string& legal_name = mJointNames[skinIdx][i]; +                std::string group_name = mJointGroups[legal_name].mGroup; +                if (goup_use_count[group_name] > 0) +                { +                    if (addJointToModelSkin(skin_info, skinIdx, i)) +                    { +                        gltfindex_to_joitindex_map[i] = replacement_index++; +                    } +                } +            } +        } +        else +        { +            // Less than 110, just add every valid joint +            for (size_t i = 0; i < jointCnt; ++i) +            { +                // Process joint name and idnex +                S32 joint = gltf_skin.mJoints[i]; +                if (gltf_joint_index_use[i] < 0) +                { +                    // unsupported (-1) joint, drop it +                    continue; +                } + +                if (addJointToModelSkin(skin_info, skinIdx, i)) +                { +                    gltfindex_to_joitindex_map[i] = replacement_index++; +                } +            } +        } + +        if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) +        { +            LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel +                << " Count: " << (S32)skin_info.mInvBindMatrix.size() +                << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; +            LLSD args; +            args["Message"] = "ModelTooManyJoint"; +            args["MODEL_NAME"] = pModel->mLabel; +            args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); +            args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; +            mWarningsArray.append(args); +        } + +        // Remap indices for pModel->mSkinWeights +        for (auto& weights : pModel->mSkinWeights) +        { +            for (auto& weight : weights.second) +            { +                weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; +            } +        } +    } + +    return true; +} + +void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) +{ +    const LL::GLTF::Skin& skin = mGLTFAsset.mSkins[skin_idx]; + +    LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing skin " << skin_idx << " with " << skin.mJoints.size() << " joints" << LL_ENDL; + +    if (skin.mInverseBindMatrices > 0 && skin.mJoints.size() != skin.mInverseBindMatricesData.size()) +    { +        LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; +        LLSD args; +        args["Message"] = "InvBindCountMismatch"; +        mWarningsArray.append(args); +    } + +    S32 joint_count = (S32)skin.mJoints.size(); +    S32 inverse_count = (S32)skin.mInverseBindMatricesData.size(); +    if (mInverseBindMatrices.size() <= skin_idx) +    { +        mInverseBindMatrices.resize(skin_idx + 1); +        mAlternateBindMatrices.resize(skin_idx + 1); +        mJointNames.resize(skin_idx + 1); +        mValidJointsCount.resize(skin_idx + 1, 0); +    } + +    // fill up joints related data +    joints_data_map_t joints_data; +    joints_name_to_node_map_t names_to_nodes; +    for (S32 i = 0; i < joint_count; i++) +    { +        S32 joint = skin.mJoints[i]; +        LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; +        JointNodeData& data = joints_data[joint]; +        data.mNodeIdx = joint; +        data.mJointListIdx = i; +        data.mGltfRestMatrix = buildGltfRestMatrix(joint, skin); +        data.mGltfMatrix = jointNode.mMatrix; +        data.mOverrideMatrix = glm::mat4(1.f); + +        if (mJointMap.find(jointNode.mName) != mJointMap.end()) +        { +            data.mName = mJointMap[jointNode.mName]; +            data.mIsValidViewerJoint = true; +            mValidJointsCount[skin_idx]++; +        } +        else +        { +            data.mName = jointNode.mName; +            data.mIsValidViewerJoint = false; +        } +        names_to_nodes[data.mName] = joint; + +        for (S32 child : jointNode.mChildren) +        { +            JointNodeData& child_data = joints_data[child]; +            child_data.mParentNodeIdx = joint; +            child_data.mIsParentValidViewerJoint = data.mIsValidViewerJoint; +        } +    } + +    if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) +    { +        LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" +            << " Count: " << mValidJointsCount[skin_idx] +            << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + +        LLSD args; +        args["Message"] = "SkinJointsOverLimit"; +        args["SKIN_INDEX"] = (S32)skin_idx; +        args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; +        args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; +        mWarningsArray.append(args); +    } + +    // Go over viewer joints and build overrides +    glm::mat4 ident(1.0); +    for (auto &viewer_data : mViewerJointData) +    { +        buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); +    } + +    for (S32 i = 0; i < joint_count; i++) +    { +        S32 joint = skin.mJoints[i]; +        LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; +        std::string legal_name(jointNode.mName); +        bool legal_joint = false; +        if (mJointMap.find(legal_name) != mJointMap.end()) +        { +            legal_name = mJointMap[legal_name]; +            legal_joint = true; +            mJointNames[skin_idx].push_back(legal_name); +        } +        else +        { +            mJointNames[skin_idx].emplace_back(); +        } + +        // Compute bind matrices + +        if (!legal_joint) +        { +            // Add placeholder to not break index. +            // Not going to be used by viewer, will be stripped from skin_info. +            LLMatrix4 gltf_transform; +            gltf_transform.setIdentity(); +            mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); +        } +        else if (inverse_count > i) +        { +            // Transalte existing bind matrix to viewer's skeleton +            // todo: probably should be 'to viewer's overriden skeleton' +            glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); +            glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; +            glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); +            glm::mat4 tranlated_original = skeleton_transform * rotated_original; +            glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); + +            LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); +            LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; +            mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); +        } +        else +        { +            // If bind matrices aren't present (they are optional in gltf), +            // assume an identy matrix +            // todo: find a model with this, might need to use rotated matrix +            glm::mat4 inv_bind(1.0f); +            glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); +            inv_bind = glm::inverse(skeleton_transform * inv_bind); + +            LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); +            LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; +            mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); +        } + +        // Compute Alternative matrices also known as overrides +        LLMatrix4 original_joint_transform(glm::value_ptr(joints_data[joint].mOverrideMatrix)); + +        // Viewer seems to care only about translation part, +        // but for parity with collada taking original value +        LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); +        newInverse.setTranslation(original_joint_transform.getTranslation()); + +        LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; +        mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); + +        if (legal_joint) +        { +            // Might be needed for uploader UI to correctly identify overriden joints +            // but going to be incorrect if multiple skins are present +            mJointList[legal_name] = newInverse; +            mJointsFromNode.push_front(legal_name); +        } +    } +} + +void LLGLTFLoader::populateJointGroups() +{ +    std::string parent; +    for (auto& viewer_data : mViewerJointData) +    { +        buildJointGroup(viewer_data, parent); +    } +} + + +S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const +{ +    S32 source_joint_node = gltf_skin.mJoints[source_joint]; +    S32 root_node = source_joint_node; +    S32 found_node = source_joint_node; +    S32 size = (S32)gltf_skin.mJoints.size(); +    do +    { +        root_node = found_node; +        for (S32 i = 0; i < size; i++) +        { +            S32 joint = gltf_skin.mJoints[i]; +            const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; +            std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); +            if (it != jointNode.mChildren.end()) +            { +                // Found node's parent +                found_node = joint; +                if (mJointMap.find(jointNode.mName) != mJointMap.end()) +                { +                    return i; +                } +                break; +            } +        } +    } while (root_node != found_node); + +    return -1; +} + +S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const +{ +    S32 root_node = 0; +    S32 found_node = source_joint_node; +    S32 size = (S32)gltf_skin.mJoints.size(); +    do +    { +        root_node = found_node; +        for (S32 i = 0; i < size; i++) +        { +            S32 joint = gltf_skin.mJoints[i]; +            const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + +            if (mJointMap.find(jointNode.mName) != mJointMap.end()) +            { +                std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); +                if (it != jointNode.mChildren.end()) +                { +                    // Found node's parent +                    found_node = joint; +                    break; +                } +            } +        } +    } while (root_node != found_node); + +    return root_node; +} + +S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const +{ +    S32 root_node = 0; +    S32 found_node = 0; +    S32 size = (S32)gltf_skin.mJoints.size(); +    do +    { +        root_node = found_node; +        for (S32 i = 0; i < size; i++) +        { +            S32 joint = gltf_skin.mJoints[i]; +            const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; +            std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); +            if (it != jointNode.mChildren.end()) +            { +                // Found node's parent +                found_node = joint; +                break; +            } +        } +    } while (root_node != found_node); + +    LL_INFOS("GLTF_DEBUG") << "mJointList name: "; +    const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node]; +    LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL; +    return root_node; +} + +S32 LLGLTFLoader::findParentNode(S32 node) const +{ +    S32 size = (S32)mGLTFAsset.mNodes.size(); +    for (S32 i = 0; i < size; i++) +    { +        const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i]; +        std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node); +        if (it != jointNode.mChildren.end()) +        { +            return i; +        } +    } +    return -1; +} + +void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group) +{ +    JointGroups& jount_group_data = mJointGroups[viewer_data.mName]; +    jount_group_data.mGroup = viewer_data.mGroup; +    jount_group_data.mParentGroup = parent_group; + +    for (LLJointData& child_data : viewer_data.mChildren) +    { +        buildJointGroup(child_data, viewer_data.mGroup); +    } +} + +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const +{ +    glm::mat4 new_lefover(1.f); +    glm::mat4 rest(1.f); +    joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName); +    if (found_node != names_to_nodes.end()) +    { +        S32 gltf_node_idx = found_node->second; +        JointNodeData& node = gltf_nodes[gltf_node_idx]; +        node.mIsOverrideValid = true; +        node.mViewerRestMatrix = viewer_data.mRestMatrix; + +        glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; +        if (mApplyXYRotation) +        { +            gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; +        } + +        glm::mat4 translated_joint; +        // Example: +        // Viewer has pelvis->spine1->spine2->torso. +        // gltf example model has pelvis->torso +        // By doing glm::inverse(transalted_rest_spine2) * gltf_rest_torso +        // We get what torso would have looked like if gltf had a spine2 +        if (viewer_data.mIsJoint) +        { +            translated_joint = glm::inverse(parent_rest) * gltf_joint_rest_pose; +        } +        else +        { +            translated_joint = glm::inverse(parent_support_rest) * gltf_joint_rest_pose; +        } + +        glm::vec3 translation_override; +        glm::vec3 skew; +        glm::vec3 scale; +        glm::vec4 perspective; +        glm::quat rotation; +        glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + +        node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity<glm::quat>(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); + +        glm::mat4 override_joint = node.mOverrideMatrix; +        override_joint = glm::scale(override_joint, viewer_data.mScale); + +        rest = parent_rest * override_joint; +        node.mOverrideRestMatrix = rest; +    } +    else +    { +        // No override for this joint +        rest = parent_rest * viewer_data.mJointMatrix; +    } + +    glm::mat4 support_rest(1.f); +    if (viewer_data.mSupport == LLJointData::SUPPORT_BASE) +    { +        support_rest = rest; +    } +    else +    { +        support_rest = parent_support_rest; +    } + +    for (LLJointData& child_data : viewer_data.mChildren) +    { +        buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, support_rest); +    } +} + +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const +{ +    // This is inefficient since we are recalculating some joints multiple times over +    // Todo: cache it? + +    if (joint_node_index < 0 || joint_node_index >= static_cast<S32>(mGLTFAsset.mNodes.size())) +    { +        return glm::mat4(1.0f); +    } + +    const auto& node = mGLTFAsset.mNodes[joint_node_index]; + +    // Find and apply parent transform if it exists +    for (size_t i = 0; i < mGLTFAsset.mNodes.size(); ++i) +    { +        const auto& potential_parent = mGLTFAsset.mNodes[i]; +        auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), joint_node_index); + +        if (it != potential_parent.mChildren.end()) +        { +            // Found parent +            if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end()) +            { +                // parent is a joint - recursively combine transform +                // assumes that matrix is already valid +                return buildGltfRestMatrix(static_cast<S32>(i), gltf_skin) * node.mMatrix; +            } +        } +    } +    // Should we return armature or stop earlier? +    return node.mMatrix; +} + +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const +{ +    // This is inefficient since we are recalculating some joints multiple times over +    // Todo: cache it? + +    if (joint_node_index < 0 || joint_node_index >= static_cast<S32>(mGLTFAsset.mNodes.size())) +    { +        return glm::mat4(1.0f); +    } + +    auto& data = joint_data.at(joint_node_index); + +    if (data.mParentNodeIdx >=0) +    { +        return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix; +    } +    // Should we return armature or stop earlier? +    return data.mGltfMatrix; +} + +// This function computes the transformation matrix needed to convert from GLTF skeleton space +// to viewer skeleton space for a specific joint + +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const +{ +    const JointNodeData& node_data = joints_data_map.at(gltf_node_index); +    if (!node_data.mIsOverrideValid) +    { +        // For now assume they are identical and return an identity (for ease of debuging) +        return glm::mat4(1.0f); +    } + +    // Get the GLTF joint's rest pose (in GLTF coordinate system) +    const glm::mat4 &gltf_joint_rest_pose = node_data.mGltfRestMatrix; +    glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; + +    LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; + +    LLMatrix4 transform(glm::value_ptr(rest_pose)); + +    LL_CONT << transform << LL_ENDL; + +    // Compute transformation from GLTF space to viewer space +    // This assumes both skeletons are in rest pose initially +    return node_data.mOverrideRestMatrix * glm::inverse(rest_pose); +} + +bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) +{ +    glm::mat4 gltf_joint_rest = buildGltfRestMatrix(joint_idx, gltf_skin); +    glm::mat4 test_mat = glm::inverse(gltf_joint_rest) * gltf_skin.mInverseBindMatricesData[bind_indx]; +    // Normally for shoulders it should be something close to +    // {1,0,0,0;0,-1,0,0;0,0,-1,0;0,0,0,1} +    // rotated one will look like +    // {0,0,0,-1;1,0,0,0;0,-1,0,0;0,0,0,1} +    // Todo: This is a cheap hack, +    // figure out how rotation is supposed to work +    return abs(test_mat[0][0]) < 0.5 && abs(test_mat[1][1]) < 0.5 && abs(test_mat[2][2]) < 0.5; +} + +void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) +{ +    // HACK: figure out model's rotation from shoulders' matrix. +    // This is wrong on many levels: +    // Too limited (only models that have shoulders), +    // Will not work well with things that emulate 3 hands in some manner +    // Only supports xy 90 degree rotation +    // Todo: figure out how to find skeleton's orientation Correctly +    // when model is rotated at a triangle level +    constexpr char right_shoulder_str[] = "mShoulderRight"; +    constexpr char left_shoulder_str[] = "mShoulderLeft"; + +    S32 size = (S32)gltf_skin.mJoints.size(); +    S32 joints_found = 0; +    for (S32 i= 0; i < size; i++) +    { +        S32 joint = gltf_skin.mJoints[i]; +        auto joint_node = mGLTFAsset.mNodes[joint]; + +        // todo: we are doing this search thing everywhere, +        // just pre-translate every joint +        JointMap::iterator found = mJointMap.find(joint_node.mName); +        if (found == mJointMap.end()) +        { +            // unsupported joint +            continue; +        } +        if (found->second == right_shoulder_str || found->second == left_shoulder_str) +        { +            if (checkForXYrotation(gltf_skin, joint, i)) +            { +                joints_found++; +            } +            else +            { +                return; +            } +        } +    } + +    if (joints_found == 2) +    { +        // Both joints in a weird position/rotation, assume rotated model +        mApplyXYRotation = true; +    } +} + +std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) +{ +    if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) +        return ""; + +    S32 sourceIndex = mGLTFAsset.mTextures[textureIndex].mSource; +    if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) +        return ""; + +    LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + +    // Handle URI-based textures +    if (!image.mUri.empty()) +    { +        return image.mUri; // Return URI directly +    } + +    // Handle embedded textures +    if (image.mBufferView >= 0) +    { +        if (image.mBufferView < mGLTFAsset.mBufferViews.size()) +        { +            const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; +            if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) +            { +                const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + +                if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) +                { +                    // Extract image data +                    const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; +                    U32 data_size = buffer_view.mByteLength; + +                    // Determine the file extension +                    std::string extension = ".png"; // Default +                    if (!image.mMimeType.empty()) +                    { +                        if (image.mMimeType == "image/jpeg") +                            extension = ".jpg"; +                        else if (image.mMimeType == "image/png") +                            extension = ".png"; +                    } +                    else if (data_size >= 4) +                    { +                        if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) +                            extension = ".jpg"; // JPEG magic bytes +                        else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) +                            extension = ".png"; // PNG magic bytes +                    } + +                    // Create a temporary file +                    std::string temp_dir = gDirUtilp->getTempDir(); +                    std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + +                                               "gltf_embedded_" + texture_type + "_" + std::to_string(sourceIndex) + extension; + +                    // Write the image data to the temporary file +                    std::ofstream temp_file(temp_filename, std::ios::binary); +                    if (temp_file.is_open()) +                    { +                        temp_file.write(reinterpret_cast<const char*>(data_ptr), data_size); +                        temp_file.close(); + +                        LL_INFOS("GLTF_IMPORT") << "Extracted embedded " << texture_type << " texture to: " << temp_filename << LL_ENDL; +                        return temp_filename; +                    } +                    else +                    { +                        LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for " << texture_type << " texture: " << temp_filename << LL_ENDL; + +                        LLSD args; +                        args["Message"] = "FailedToCreateTempFile"; +                        args["TEXTURE_INDEX"] = sourceIndex; +                        args["TEXTURE_TYPE"]  = texture_type; +                        args["TEMP_FILE"] = temp_filename; +                        mWarningsArray.append(args); +                    } +                } +            } +        } +    } + +    return ""; +} + +void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) +{ +    std::vector<std::string> extensions = unsupported ? mGLTFAsset.mUnsupportedExtensions : mGLTFAsset.mIgnoredExtensions; +    if (extensions.size() > 0) +    { +        LLSD args; +        args["Message"] = unsupported ? "UnsupportedExtension" : "IgnoredExtension"; +        std::string del; +        std::string ext; +        for (auto& extension : extensions) +        { +            ext += del; +            ext += extension; +            del = ","; +        } +        args["EXT"] = ext; +        mWarningsArray.append(args); +    } +} + diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h new file mode 100644 index 0000000000..048f7df7f5 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.h @@ -0,0 +1,203 @@ +/** + * @file LLGLTFLoader.h + * @brief LLGLTFLoader class definition + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLGLTFLoader_H +#define LL_LLGLTFLoader_H + +#include "tinygltf/tiny_gltf.h" + +#include "asset.h" + +#include "llglheaders.h" +#include "lljointdata.h" +#include "llmodelloader.h" + +class LLGLTFLoader : public LLModelLoader +{ +  public: +    typedef std::map<std::string, LLImportMaterial> material_map; +    typedef std::map<std::string, std::string> joint_viewer_parent_map_t; +    typedef std::map<std::string, glm::mat4> joint_viewer_rest_map_t; +    typedef std::map<S32, glm::mat4> joint_node_mat4_map_t; + +    struct JointNodeData +    { +        JointNodeData() +            : mJointListIdx(-1) +            , mNodeIdx(-1) +            , mParentNodeIdx(-1) +            , mIsValidViewerJoint(false) +            , mIsParentValidViewerJoint(false) +            , mIsOverrideValid(false) +        { + +        } +        S32 mJointListIdx; +        S32 mNodeIdx; +        S32 mParentNodeIdx; +        glm::mat4 mGltfRestMatrix; +        glm::mat4 mViewerRestMatrix; +        glm::mat4 mOverrideRestMatrix; +        glm::mat4 mGltfMatrix; +        glm::mat4 mOverrideMatrix; +        std::string mName; +        bool mIsValidViewerJoint; +        bool mIsParentValidViewerJoint; +        bool mIsOverrideValid; +    }; +    typedef std::map <S32, JointNodeData> joints_data_map_t; +    typedef std::map <std::string, S32> joints_name_to_node_map_t; + +    LLGLTFLoader(std::string filename, +                    S32                                 lod, +                    LLModelLoader::load_callback_t      load_cb, +                    LLModelLoader::joint_lookup_func_t  joint_lookup_func, +                    LLModelLoader::texture_load_func_t  texture_load_func, +                    LLModelLoader::state_callback_t     state_cb, +                    void *                              opaque_userdata, +                    JointTransformMap &                 jointTransformMap, +                    JointNameSet &                      jointsFromNodes, +                    std::map<std::string, std::string> &jointAliasMap, +                    U32                                 maxJointsPerMesh, +                    U32                                 modelLimit, +                    std::vector<LLJointData>            viewer_skeleton); //, +                    //bool                                preprocess ); +    virtual ~LLGLTFLoader(); + +    virtual bool OpenFile(const std::string &filename); + +    struct GLTFVertex +    { +        glm::vec3 position; +        glm::vec3 normal; +        glm::vec2 uv0; +        glm::u16vec4 joints; +        glm::vec4 weights; +    }; + +protected: +    LL::GLTF::Asset mGLTFAsset; +    tinygltf::Model mGltfModel; +    bool            mGltfLoaded; +    bool            mApplyXYRotation = false; +    U32             mGeneratedModelLimit; + +    // GLTF isn't aware of viewer's skeleton and uses it's own, +    // so need to take viewer's joints and use them to +    // recalculate iverse bind matrices +    std::vector<LLJointData>             mViewerJointData; + +    // vector of vectors because of a posibility of having more than one skin +    typedef std::vector<LLMeshSkinInfo::matrix_list_t> bind_matrices_t; +    typedef std::vector<std::vector<std::string> > joint_names_t; +    bind_matrices_t                     mInverseBindMatrices; +    bind_matrices_t                     mAlternateBindMatrices; +    joint_names_t                       mJointNames; // empty string when no legal name for a given idx + +    // what group a joint belongs to. +    // For purpose of stripping unused groups when joints are over limit. +    struct JointGroups +    { +        std::string mGroup; +        std::string mParentGroup; +    }; +    typedef std::map<std::string, JointGroups> joint_to_group_map_t; +    joint_to_group_map_t mJointGroups; + +    // per skin joint count, needs to be tracked for the sake of limits check. +    std::vector<S32>                    mValidJointsCount; + +private: +    bool parseMeshes(); +    void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; +    void processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); +    bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const; +    bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); +    void populateJointsFromSkin(S32 skin_idx); +    void populateJointGroups(); +    void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); +    S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; +    S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; +    S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint +    S32 findParentNode(S32 node) const; +    void buildJointGroup(LLJointData& viewer_data, const std::string& parent_group); +    void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_rest) const; +    glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; +    glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const; +    glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const; +    bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx); +    void checkForXYrotation(const LL::GLTF::Skin& gltf_skin); + +    std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type); + +    void notifyUnsupportedExtension(bool unsupported); + +    //    bool mPreprocessGLTF; + +    /*  Below inherited from dae loader - unknown if/how useful here + +    void processElement(gltfElement *element, bool &badElement, GLTF *gltf); +    void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin); + +    material_map     getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf); +    LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf); +    LLColor4         getGltfColor(gltfElement *element); + +    gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name); + +    bool isNodeAJoint(gltfNode *pNode); +    void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms); +    void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform); +    void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform); +    void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform); +    void buildJointToNodeMappingFromScene(gltfElement *pRoot); +    void processJointToNodeMapping(gltfNode *pNode); +    void processChildJoints(gltfNode *pParentNode); + +    bool verifyCount(int expected, int result); + +    // Verify that a controller matches vertex counts +    bool verifyController(gltfController *pController); + +    static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg); +    static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh); + +    static LLModel *loadModelFromGltfMesh(gltfMesh *mesh); + +    // Loads a mesh breaking it into one or more models as necessary +    // to get around volume face limitations while retaining >8 materials +    // +    bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit); + +    static std::string getElementLabel(gltfElement *element); +    static size_t      getSuffixPosition(std::string label); +    static std::string getLodlessLabel(gltfElement *element); + +    static std::string preprocessGLTF(std::string filename); +    */ + +}; +#endif  // LL_LLGLTFLLOADER_H diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index 9faead9533..3cb5e9a0d7 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -317,7 +317,7 @@ void GLTFSceneManager::load(const std::string& filename)  {      std::shared_ptr<Asset> asset = std::make_shared<Asset>(); -    if (asset->load(filename)) +    if (asset->load(filename, true))      {          gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions          asset->updateTransforms(); diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 716e6cd9e3..41e954b7fa 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -59,7 +59,7 @@ LLFilePicker LLFilePicker::sInstance;  #define XML_FILTER L"XML files (*.xml)\0*.xml\0"  #define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0"  #define RAW_FILTER L"RAW files (*.raw)\0*.raw\0" -#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0" +#define MODEL_FILTER L"Model files (*.dae, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0"  #define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0"  #define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0"  #define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" @@ -217,6 +217,8 @@ bool LLFilePicker::setupFilter(ELoadFilter filter)          break;      case FFLOAD_MODEL:          mOFN.lpstrFilter = MODEL_FILTER \ +            COLLADA_FILTER \ +            MATERIAL_FILTER \              L"\0";          break;      case FFLOAD_MATERIAL: @@ -671,6 +673,8 @@ std::unique_ptr<std::vector<std::string>> LLFilePicker::navOpenFilterProc(ELoadF          case FFLOAD_HDRI:              allowedv->push_back("exr");          case FFLOAD_MODEL: +            allowedv->push_back("gltf"); +            allowedv->push_back("glb");          case FFLOAD_COLLADA:              allowedv->push_back("dae");              break; diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 47471edb92..5cb5f8b961 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -64,6 +64,7 @@  #include "llcallbacklist.h"  #include "llviewertexteditor.h"  #include "llviewernetwork.h" +#include "llmaterialeditor.h"  //static @@ -619,11 +620,9 @@ void LLFloaterModelPreview::onJointListSelection()      LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");      LLScrollListCtrl *joints_list = panel->getChild<LLScrollListCtrl>("joints_list");      LLScrollListCtrl *joints_pos = panel->getChild<LLScrollListCtrl>("pos_overrides_list"); -    LLScrollListCtrl *joints_scale = panel->getChild<LLScrollListCtrl>("scale_overrides_list");      LLTextBox *joint_pos_descr = panel->getChild<LLTextBox>("pos_overrides_descr");      joints_pos->deleteAllItems(); -    joints_scale->deleteAllItems();      LLScrollListItem *selected = joints_list->getFirstSelected();      if (selected) diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 28160177f6..378f5fdf91 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -137,7 +137,8 @@ LLFloaterComboOptions* LLFloaterComboOptions::showUI(          {              combo_picker->mComboOptions->addSimpleElement(*iter);          } -        combo_picker->mComboOptions->selectFirstItem(); +        // select 'Bulk Upload All' option +        combo_picker->mComboOptions->selectNthItem((S32)options.size() - 1);          combo_picker->openFloater(LLSD(title));          combo_picker->setFocus(true); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 49c0006f66..6843f7375d 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -30,7 +30,7 @@  #include "llmodelloader.h"  #include "lldaeloader.h" -#include "llgltfloader.h" +#include "gltf/llgltfloader.h"  #include "llfloatermodelpreview.h"  #include "llagent.h" @@ -40,6 +40,7 @@  #include "lldrawable.h"  #include "llface.h"  #include "lliconctrl.h" +#include "lljointdata.h"  #include "llmatrix4a.h"  #include "llmeshrepository.h"  #include "llmeshoptimizer.h" @@ -810,6 +811,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable      }      else      { +        LLVOAvatar* av = getPreviewAvatar(); +        std::vector<LLJointData> viewer_skeleton; +        av->getJointMatricesAndHierarhy(viewer_skeleton);          mModelLoader = new LLGLTFLoader(              filename,              lod, @@ -822,7 +826,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable              mJointsFromNode,              joint_alias_map,              LLSkinningUtil::getMaxJointCount(), -            gSavedSettings.getU32("ImporterModelLimit")); +            gSavedSettings.getU32("ImporterModelLimit"), +            viewer_skeleton);      }      if (force_disable_slm) @@ -3090,25 +3095,48 @@ void LLModelPreview::lookupLODModelFiles(S32 lod)      S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS;      std::string lod_filename = mLODFile[LLModel::LOD_HIGH]; -    std::string ext = ".dae";      std::string lod_filename_lower(lod_filename);      LLStringUtil::toLower(lod_filename_lower); -    std::string::size_type i = lod_filename_lower.rfind(ext); -    if (i != std::string::npos) + +    // Check for each supported file extension +    std::vector<std::string> supported_exts = { ".dae", ".gltf", ".glb" }; +    std::string found_ext; +    std::string::size_type ext_pos = std::string::npos; + +    for (const auto& ext : supported_exts)      { -        lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext); +        std::string::size_type i = lod_filename_lower.rfind(ext); +        if (i != std::string::npos) +        { +            ext_pos = i; +            found_ext = ext; +            break; +        }      } -    if (gDirUtilp->fileExists(lod_filename)) + +    if (ext_pos != std::string::npos)      { -        LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; -        if (fmp) +        // Replace extension with LOD suffix + original extension +        std::string lod_file_to_check = lod_filename; +        lod_file_to_check.replace(ext_pos, found_ext.size(), getLodSuffix(next_lod) + found_ext); + +        if (gDirUtilp->fileExists(lod_file_to_check)) +        { +            LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; +            if (fmp) +            { +                fmp->setCtrlLoadFromFile(next_lod); +            } +            loadModel(lod_file_to_check, next_lod); +        } +        else          { -            fmp->setCtrlLoadFromFile(next_lod); +            lookupLODModelFiles(next_lod);          } -        loadModel(lod_filename, next_lod);      }      else      { +        // No recognized extension found, continue with next LOD          lookupLODModelFiles(next_lod);      }  } diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index cee43f3cff..47f58afa00 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -135,6 +135,12 @@ void LLSkinningUtil::initSkinningMatrixPalette(      initJointNums(const_cast<LLMeshSkinInfo*>(skin), avatar); +    if (skin->mInvBindMatrix.size() < count ) +    { +        // faulty model? mInvBindMatrix.size() should have matched mJointNames.size() +        return; +    } +      LLMatrix4a world[LL_CHARACTER_MAX_ANIMATED_JOINTS];      for (S32 j = 0; j < count; ++j) @@ -354,7 +360,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a                              {                                  rig_info_tab[joint_num].setIsRiggedTo(true); -                                const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index]; +                                size_t bind_poses_size = skin->mBindPoseMatrix.size(); +                                const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity();                                  LLVector4a pos_joint_space;                                  mat.affineTransform(pos, pos_joint_space); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 90223fcda8..f8543ba1aa 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -14,7 +14,7 @@   legacy_header_height="25">    <string name="status_idle"></string> -  <string name="status_parse_error">Error: Dae parsing issue - see log for details.</string> +  <string name="status_parse_error">Error: Model parsing issue - see log for details.</string>    <string name="status_bind_shape_orientation">Warning: bind shape matrix is not in standard X-forward orientation.</string>    <string name="status_material_mismatch">Error: Material of model is not a subset of reference model.</string>    <string name="status_reading_file">Loading...</string> @@ -45,6 +45,7 @@    <string name="UnrecognizedJoint">Rigged to unrecognized joint name [NAME]</string>    <string name="UnknownJoints">Skinning disabled due to [COUNT] unknown joints</string>    <string name="ModelLoaded">Model [MODEL_NAME] loaded</string> +  <string name="InvBindCountMismatch">Bind matrices count mismatch joints count</string>    <string name="IncompleteTC">Texture coordinates data is not complete.</string>    <string name="PositionNaN">Found NaN while loading position data from DAE-Model, invalid model.</string> @@ -61,6 +62,22 @@    <string name="ParsingErrorNoScene">Document has no visual_scene</string>    <string name="ParsingErrorPositionInvalidModel">Unable to process mesh without position data. Invalid model.</string> + <!-- GLTF specific messages --> +  <string name="NoScenesFound">No scenes defined in GLTF file</string> +  <string name="InvalidMeshReference">Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES])</string> +  <string name="InvalidGeometryNonTriangulated">Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated)</string> +  <string name="EmptyVertexArray">Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Empty vertex array</string> +  <string name="ErrorIndexLimit">Unable to process mesh [MESH_NAME] due to 65,534 vertex limit. Vertex count: [VERTEX_COUNT]</string> +  <string name="TextureFound">Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME]</string> +  <string name="IgnoredExtension">Model uses unsupported extension: [EXT], related material properties are ignored</string> +  <string name="UnsupportedExtension">Unable to load model, unsupported extension: [EXT]</string> +  <string name="TooManyMeshParts">Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT]</string> +  <string name="FailedToCreateTempFile">Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE]</string> +  <string name="SkinJointsOverLimit">Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis.</string> +  <string name="ModelTooManyJoint">Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail</string> +  <string name="ParsingErrorMissingBuffer">Buffer is either missing or empty [BUFFER_NAME].</string> +  <string name="ParsingErrorMissingBufferBin">Buffer is either missing or empty. Check presence of [BUFFER_URI] file.</string> +    <panel      follows="top|left"      height="595" @@ -1706,7 +1723,6 @@ Analysed:     height="408"/>    <panel      follows="right|bottom" -    can_resize="false"      height="140"      layout="topleft"      name="right_panel" diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index a51feeb7ab..82e77119ab 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -9453,8 +9453,11 @@ Unable to upload texture: '[NAME]'     icon="alertmodal.tga"     name="CannotUploadMaterial"     type="alertmodal"> -There was a problem uploading the file +Unable to upload material file. The file may be corrupted, in an unsupported format, or contain invalid data. Please check that you're using a valid GLTF/GLB file with proper material definitions.      <tag>fail</tag> +    <usetemplate +     name="okbutton" +     yestext="OK"/>    </notification>    <notification | 
