diff options
author | Jonathan "Geenz" Goodman <geenz@geenzo.com> | 2025-06-27 21:28:58 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-06-27 21:28:58 -0400 |
commit | b0c951ffe348f478f27a85720cc7aeffea32fe73 (patch) | |
tree | 9e473b5ecbd873062b29d91ee94cc06453e12dd7 | |
parent | f48fe44684a535ed2eefc64c134551ce72e9ecf4 (diff) |
Revert "Merge develop into glTF mesh import"
38 files changed, 772 insertions, 2544 deletions
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4bf2af644a..198785d39b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -218,10 +218,8 @@ jobs: prefix=${ba[0]} if [ "$prefix" == "project" ]; then IFS='_' read -ra prj <<< "${ba[1]}" - prj_str="${prj[*]}" # uppercase first letter of each word - capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}') - export viewer_channel="Second Life Project $capitalized" + export viewer_channel="Second Life Project ${prj[*]^}" elif [[ "$prefix" == "release" || "$prefix" == "main" ]]; then export viewer_channel="Second Life Release" @@ -457,6 +455,7 @@ jobs: prerelease: true generate_release_notes: true target_commitish: ${{ github.sha }} + previous_tag: release append_body: true fail_on_unmatched_files: true files: | diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index 6744c8d8a4..c3be8bc78e 100644 --- a/indra/llappearance/CMakeLists.txt +++ b/indra/llappearance/CMakeLists.txt @@ -14,7 +14,6 @@ set(llappearance_SOURCE_FILES llavatarjoint.cpp llavatarjointmesh.cpp lldriverparam.cpp - lljointdata.h lllocaltextureobject.cpp llpolyskeletaldistortion.cpp llpolymesh.cpp diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index dab18c240d..3d66809ed6 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -29,17 +29,16 @@ #include "llavatarappearance.h" #include "llavatarappearancedefines.h" #include "llavatarjointmesh.h" -#include "lljointdata.h" #include "llstl.h" #include "lldir.h" #include "llpolymorph.h" #include "llpolymesh.h" #include "llpolyskeletaldistortion.h" +#include "llstl.h" #include "lltexglobalcolor.h" #include "llwearabledata.h" #include "boost/bind.hpp" #include "boost/tokenizer.hpp" -#include "v4math.h" using namespace LLAvatarAppearanceDefines; @@ -72,13 +71,11 @@ public: mChildren.clear(); } bool parseXml(LLXmlTreeNode* node); - glm::mat4 getJointMatrix(); private: std::string mName; std::string mSupport; std::string mAliases; - std::string mGroup; bool mIsJoint; LLVector3 mPos; LLVector3 mEnd; @@ -109,16 +106,10 @@ public: S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; } private: - typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t; - static void getJointMatricesAndHierarhy( - LLAvatarBoneInfo* bone_info, - LLJointData& data, - const glm::mat4& parent_mat); - -private: S32 mNumBones; S32 mNumCollisionVolumes; LLAvatarAppearance::joint_alias_map_t mJointAliasMap; + typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t; bone_info_list_t mBoneInfoList; }; @@ -1607,15 +1598,6 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node) mSupport = "base"; } - // Skeleton has 133 bones, but shader only allows 110 (LL_MAX_JOINTS_PER_MESH_OBJECT) - // Groups can be used by importer to cut out unused groups of joints - static LLStdStringHandle group_string = LLXmlTree::addAttributeString("group"); - if (!node->getFastAttributeString(group_string, mGroup)) - { - LL_WARNS() << "Bone without group " << mName << LL_ENDL; - mGroup = "global"; - } - if (mIsJoint) { static LLStdStringHandle pivot_string = LLXmlTree::addAttributeString("pivot"); @@ -1641,21 +1623,6 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node) return true; } - -glm::mat4 LLAvatarBoneInfo::getJointMatrix() -{ - glm::mat4 mat(1.0f); - // 1. Scaling - mat = glm::scale(mat, glm::vec3(mScale[0], mScale[1], mScale[2])); - // 2. Rotation (Euler angles rad) - mat = glm::rotate(mat, mRot[0], glm::vec3(1, 0, 0)); - mat = glm::rotate(mat, mRot[1], glm::vec3(0, 1, 0)); - mat = glm::rotate(mat, mRot[2], glm::vec3(0, 0, 1)); - // 3. Position - mat = glm::translate(mat, glm::vec3(mPos[0], mPos[1], mPos[2])); - return mat; -} - //----------------------------------------------------------------------------- // LLAvatarSkeletonInfo::parseXml() //----------------------------------------------------------------------------- @@ -1686,25 +1653,6 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node) return true; } -void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy( - LLAvatarBoneInfo* bone_info, - LLJointData& data, - const glm::mat4& parent_mat) -{ - data.mName = bone_info->mName; - data.mJointMatrix = bone_info->getJointMatrix(); - data.mScale = glm::vec3(bone_info->mScale[0], bone_info->mScale[1], bone_info->mScale[2]); - data.mRotation = bone_info->mRot; - data.mRestMatrix = parent_mat * data.mJointMatrix; - data.mIsJoint = bone_info->mIsJoint; - data.mGroup = bone_info->mGroup; - for (LLAvatarBoneInfo* child_info : bone_info->mChildren) - { - LLJointData& child_data = data.mChildren.emplace_back(); - getJointMatricesAndHierarhy(child_info, child_data, data.mRestMatrix); - } -} - //Make aliases for joint and push to map. void LLAvatarAppearance::makeJointAliases(LLAvatarBoneInfo *bone_info) { @@ -1766,16 +1714,6 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases return mJointAliasMap; } -void LLAvatarAppearance::getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const -{ - glm::mat4 identity(1.f); - for (LLAvatarBoneInfo* bone_info : sAvatarSkeletonInfo->mBoneInfoList) - { - LLJointData& child_data = data.emplace_back(); - LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(bone_info, child_data, identity); - } -} - //----------------------------------------------------------------------------- // parseXmlSkeletonNode(): parses <skeleton> nodes from XML tree diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 2748da9a1d..99bc5eeef5 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -34,7 +34,6 @@ #include "lltexlayer.h" #include "llviewervisualparam.h" #include "llxmltree.h" -#include "v4math.h" class LLTexLayerSet; class LLTexGlobalColor; @@ -42,7 +41,6 @@ class LLTexGlobalColorInfo; class LLWearableData; class LLAvatarBoneInfo; class LLAvatarSkeletonInfo; -class LLJointData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // LLAvatarAppearance @@ -140,7 +138,7 @@ public: LLVector3 mHeadOffset{}; // current head position LLAvatarJoint* mRoot{ nullptr }; - typedef std::map<std::string, LLJoint*> joint_map_t; + typedef std::map<std::string, LLJoint*, std::less<>> joint_map_t; joint_map_t mJointMap; typedef std::map<std::string, LLVector3> joint_state_map_t; @@ -153,11 +151,9 @@ public: public: typedef std::vector<LLAvatarJoint*> avatar_joint_list_t; const avatar_joint_list_t& getSkeleton() { return mSkeleton; } - typedef std::map<std::string, std::string> joint_alias_map_t; + typedef std::map<std::string, std::string, std::less<>> joint_alias_map_t; const joint_alias_map_t& getJointAliases(); - typedef std::map<std::string, std::string> joint_parent_map_t; // matrix plus parent - typedef std::map<std::string, glm::mat4> joint_rest_map_t; - void getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const; + protected: static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/llappearance/lljointdata.h b/indra/llappearance/lljointdata.h deleted file mode 100644 index 2fc26198ee..0000000000 --- a/indra/llappearance/lljointdata.h +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @file lljointdata.h - * @brief LLJointData class for holding individual joint data and skeleton - * - * $LicenseInfo:firstyear=2025&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2025, 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_LLJOINTDATA_H -#define LL_LLJOINTDATA_H - -#include "v4math.h" - -// may be just move LLAvatarBoneInfo -class LLJointData -{ -public: - std::string mName; - std::string mGroup; - glm::mat4 mJointMatrix; - glm::mat4 mRestMatrix; - glm::vec3 mScale; - LLVector3 mRotation; - - typedef std::vector<LLJointData> bones_t; - bones_t mChildren; - - bool mIsJoint; // if not, collision_volume - enum SupportCategory - { - SUPPORT_BASE, - SUPPORT_EXTENDED - }; - SupportCategory mSupport; - void setSupport(const std::string& support) - { - if (support == "extended") - { - mSupport = SUPPORT_EXTENDED; - } - else - { - mSupport = SUPPORT_BASE; - } - } -}; - -#endif //LL_LLJOINTDATA_H diff --git a/indra/llcharacter/llbvhloader.cpp b/indra/llcharacter/llbvhloader.cpp index 9dace08e6f..581e9f62d5 100644 --- a/indra/llcharacter/llbvhloader.cpp +++ b/indra/llcharacter/llbvhloader.cpp @@ -131,7 +131,7 @@ LLQuaternion::Order bvhStringToOrder( char *str ) // LLBVHLoader() //----------------------------------------------------------------------------- -LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string>& joint_alias_map ) +LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string, std::less<>>& joint_alias_map ) { reset(); errorLine = 0; @@ -156,9 +156,9 @@ LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &error } // Recognize all names we've been told are legal. - for (std::map<std::string, std::string>::value_type& alias_pair : joint_alias_map) + for (const auto& [alias, joint] : joint_alias_map) { - makeTranslation( alias_pair.first , alias_pair.second ); + makeTranslation(alias, joint); } char error_text[128]; /* Flawfinder: ignore */ diff --git a/indra/llcharacter/llbvhloader.h b/indra/llcharacter/llbvhloader.h index de31f76dd3..ae2e347ba1 100644 --- a/indra/llcharacter/llbvhloader.h +++ b/indra/llcharacter/llbvhloader.h @@ -227,7 +227,7 @@ class LLBVHLoader friend class LLKeyframeMotion; public: // Constructor - LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string>& joint_alias_map ); + LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string, std::less<>>& joint_alias_map ); ~LLBVHLoader(); /* diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp index ecbcdb3bf5..8efcd9dd29 100644 --- a/indra/llcharacter/llcharacter.cpp +++ b/indra/llcharacter/llcharacter.cpp @@ -77,12 +77,11 @@ LLCharacter::~LLCharacter() //----------------------------------------------------------------------------- // getJoint() //----------------------------------------------------------------------------- -LLJoint *LLCharacter::getJoint( const std::string &name ) +LLJoint* LLCharacter::getJoint(std::string_view name) { - LLJoint* joint = NULL; + LLJoint* joint = nullptr; - LLJoint *root = getRootJoint(); - if (root) + if (LLJoint* root = getRootJoint()) { joint = root->findJoint(name); } diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 6143ec8cd1..0046c5da7e 100644 --- a/indra/llcharacter/llcharacter.h +++ b/indra/llcharacter/llcharacter.h @@ -76,7 +76,7 @@ public: // get the specified joint // default implementation does recursive search, // subclasses may optimize/cache results. - virtual LLJoint *getJoint( const std::string &name ); + virtual LLJoint* getJoint(std::string_view name); // get the position of the character virtual LLVector3 getCharacterPosition() = 0; diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index e13f0bbd96..3d8e02cb16 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -12,6 +12,7 @@ include(TinyGLTF) set(llprimitive_SOURCE_FILES lldaeloader.cpp + llgltfloader.cpp llgltfmaterial.cpp llmaterialid.cpp llmaterial.cpp @@ -31,6 +32,7 @@ set(llprimitive_SOURCE_FILES set(llprimitive_HEADER_FILES CMakeLists.txt lldaeloader.h + llgltfloader.h llgltfmaterial.h llgltfmaterial_templates.h legacy_object_types.h diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index 0759447902..eadb052b38 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -880,7 +880,7 @@ LLDAELoader::LLDAELoader( void* opaque_userdata, JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, - std::map<std::string, std::string>& jointAliasMap, + std::map<std::string, std::string, std::less<>>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, bool preprocess) diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index 4531e03474..dc20feca52 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -47,19 +47,19 @@ public: dae_model_map mModelsMap; LLDAELoader( - 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, - bool preprocess); + 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, std::less<>>& jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + bool preprocess); virtual ~LLDAELoader() ; virtual bool OpenFile(const std::string& filename); diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp new file mode 100644 index 0000000000..724b1a88b2 --- /dev/null +++ b/indra/llprimitive/llgltfloader.cpp @@ -0,0 +1,404 @@ +/** + * @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" + +// 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 "llmatrix4a.h" + +#include <boost/regex.hpp> +#include <boost/algorithm/string/replace.hpp> + +static const std::string lod_suffix[LLModel::NUM_LODS] = +{ + "_LOD0", + "_LOD1", + "_LOD2", + "", + "_PHYS", +}; + + +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, std::less<>> & jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit) //, + //bool preprocess) + : LLModelLoader( filename, + lod, + load_cb, + joint_lookup_func, + texture_load_func, + state_cb, + opaque_userdata, + jointTransformMap, + jointsFromNodes, + jointAliasMap, + maxJointsPerMesh ), + //mPreprocessGLTF(preprocess), + mMeshesLoaded(false), + mMaterialsLoaded(false) +{ +} + +LLGLTFLoader::~LLGLTFLoader() {} + +bool LLGLTFLoader::OpenFile(const std::string &filename) +{ + tinygltf::TinyGLTF loader; + std::string error_msg; + std::string warn_msg; + std::string filename_lc(filename); + LLStringUtil::toLower(filename_lc); + + // Load a tinygltf model fom a file. Assumes that the input filename has already been + // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish. + if (std::string::npos == filename_lc.rfind(".gltf")) + { // file is binary + mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename); + } + else + { // file is ascii + mGltfLoaded = loader.LoadASCIIFromFile(&mGltfModel, &error_msg, &warn_msg, filename); + } + + if (!mGltfLoaded) + { + if (!warn_msg.empty()) + LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL; + if (!error_msg.empty()) + LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL; + return false; + } + + mMeshesLoaded = parseMeshes(); + if (mMeshesLoaded) uploadMeshes(); + + mMaterialsLoaded = parseMaterials(); + if (mMaterialsLoaded) uploadMaterials(); + + return (mMeshesLoaded || mMaterialsLoaded); +} + +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); + + for (tinygltf::Mesh mesh : mGltfModel.meshes) + { + LLModel *pModel = new LLModel(volume_params, 0.f); + + if (populateModelFromMesh(pModel, mesh) && + (LLModel::NO_ERRORS == pModel->getStatus()) && + validate_model(pModel)) + { + mModelList.push_back(pModel); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete(pModel); + return false; + } + } + return true; +} + +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh) +{ + pModel->mLabel = mesh.name; + int pos_idx; + tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a; + + auto prims = mesh.primitives; + for (auto prim : prims) + { + if (prim.indices >= 0) indices_a = mGltfModel.accessors[prim.indices]; + + pos_idx = (prim.attributes.count("POSITION") > 0) ? prim.attributes.at("POSITION") : -1; + if (pos_idx >= 0) + { + positions_a = mGltfModel.accessors[pos_idx]; + if (TINYGLTF_COMPONENT_TYPE_FLOAT != positions_a.componentType) + continue; + auto positions_bv = mGltfModel.bufferViews[positions_a.bufferView]; + auto positions_buf = mGltfModel.buffers[positions_bv.buffer]; + //auto type = positions_vb. + //if (positions_buf.name + } + +#if 0 + int norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx; + norm_idx = (prim.attributes.count("NORMAL") > 0) ? prim.attributes.at("NORMAL") : -1; + tan_idx = (prim.attributes.count("TANGENT") > 0) ? prim.attributes.at("TANGENT") : -1; + uv0_idx = (prim.attributes.count("TEXCOORDS_0") > 0) ? prim.attributes.at("TEXCOORDS_0") : -1; + uv1_idx = (prim.attributes.count("TEXCOORDS_1") > 0) ? prim.attributes.at("TEXCOORDS_1") : -1; + color0_idx = (prim.attributes.count("COLOR_0") > 0) ? prim.attributes.at("COLOR_0") : -1; + color1_idx = (prim.attributes.count("COLOR_1") > 0) ? prim.attributes.at("COLOR_1") : -1; +#endif + + if (prim.mode == TINYGLTF_MODE_TRIANGLES) + { + //auto pos = mesh. TODO resume here DJH 2022-04 + } + } + + //pModel->addFace() + return false; +} + +bool LLGLTFLoader::parseMaterials() +{ + if (!mGltfLoaded) return false; + + // fill local texture data structures + mSamplers.clear(); + for (auto in_sampler : mGltfModel.samplers) + { + gltf_sampler sampler; + sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR; + sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;; + sampler.wrapS = in_sampler.wrapS; + sampler.wrapT = in_sampler.wrapT; + sampler.name = in_sampler.name; // unused + mSamplers.push_back(sampler); + } + + mImages.clear(); + for (auto in_image : mGltfModel.images) + { + gltf_image image; + image.numChannels = in_image.component; + image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes + image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc + image.size = static_cast<U32>(in_image.image.size()); + image.height = in_image.height; + image.width = in_image.width; + image.data = in_image.image.data(); + + if (in_image.as_is) + { + LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; + return false; + } + + if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) + { + LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; + return false; + } + + mImages.push_back(image); + } + + mTextures.clear(); + for (auto in_tex : mGltfModel.textures) + { + gltf_texture tex; + tex.imageIdx = in_tex.source; + tex.samplerIdx = in_tex.sampler; + tex.imageUuid.setNull(); + + if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) + { + LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; + return false; + } + + mTextures.push_back(tex); + } + + // parse each material + for (tinygltf::Material gltf_material : mGltfModel.materials) + { + gltf_render_material mat; + mat.name = gltf_material.name; + + tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; + mat.hasPBR = true; // Always true, for now + + mat.baseColor.set(pbr.baseColorFactor.data()); + mat.hasBaseTex = pbr.baseColorTexture.index >= 0; + mat.baseColorTexIdx = pbr.baseColorTexture.index; + mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; + + mat.metalness = pbr.metallicFactor; + mat.roughness = pbr.roughnessFactor; + mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; + mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; + mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; + + mat.normalScale = gltf_material.normalTexture.scale; + mat.hasNormalTex = gltf_material.normalTexture.index >= 0; + mat.normalTexIdx = gltf_material.normalTexture.index; + mat.normalTexCoords = gltf_material.normalTexture.texCoord; + + mat.occlusionScale = gltf_material.occlusionTexture.strength; + mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; + mat.occlusionTexIdx = gltf_material.occlusionTexture.index; + mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; + + mat.emissiveColor.set(gltf_material.emissiveFactor.data()); + mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; + mat.emissiveTexIdx = gltf_material.emissiveTexture.index; + mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; + + mat.alphaMode = gltf_material.alphaMode; + mat.alphaMask = gltf_material.alphaCutoff; + + if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) || + (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) || + (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) || + (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) || + (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size()))) + { + LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL; + return false; + } + + if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV + (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || + (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || + (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || + (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) + { + LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; + return false; + } + + mMaterials.push_back(mat); + } + + return true; +} + +// TODO: convert raw vertex buffers to UUIDs +void LLGLTFLoader::uploadMeshes() +{ + llassert(0); +} + +// convert raw image buffers to texture UUIDs & assemble into a render material +void LLGLTFLoader::uploadMaterials() +{ + for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple + { + if (mat.hasBaseTex) + { + gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasMRTex) + { + gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasNormalTex) + { + gltf_texture& gtex = mTextures[mat.normalTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasOcclusionTex) + { + gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasEmissiveTex) + { + gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + } +} + +LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) +{ + //gltf_image& image = mImages[tex.imageIdx]; + //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + + // fill an LLSD container with image+sampler data + + // upload texture + + // retrieve UUID + + return LLUUID::null; +} diff --git a/indra/llprimitive/llgltfloader.h b/indra/llprimitive/llgltfloader.h new file mode 100644 index 0000000000..848a07c1e4 --- /dev/null +++ b/indra/llprimitive/llgltfloader.h @@ -0,0 +1,206 @@ +/** + * @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 "llglheaders.h" +#include "llmodelloader.h" + +// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD + +class gltf_sampler +{ +public: + // Uses GL enums + S32 minFilter; // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR + S32 magFilter; // GL_NEAREST or GL_LINEAR + S32 wrapS; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT + S32 wrapT; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT + //S32 wrapR; // Found in some sample files, but not part of glTF 2.0 spec. Ignored. + std::string name; // optional, currently unused + // extensions and extras are sampler optional fields that we don't support - at least initially +}; + +class gltf_image +{ +public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL) + U8* data; // ptr to decoded image data + U32 size; // in bytes, regardless of channel width + U32 width; + U32 height; + U32 numChannels; // range 1..4 + U32 bytesPerChannel; // converted from gltf "bits", expects only 8, 16 or 32 as input + U32 pixelType; // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT +}; + +class gltf_texture +{ +public: + U32 imageIdx; + U32 samplerIdx; + LLUUID imageUuid = LLUUID::null; +}; + +class gltf_render_material +{ +public: + std::string name; + + // scalar values + LLColor4 baseColor; // linear encoding. Multiplied with vertex color, if present. + double metalness; + double roughness; + double normalScale; // scale applies only to X,Y components of normal + double occlusionScale; // strength multiplier for occlusion + LLColor4 emissiveColor; // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent) + std::string alphaMode; // "OPAQUE", "MASK" or "BLEND" + double alphaMask; // alpha cut-off + + // textures + U32 baseColorTexIdx; // always sRGB encoded + U32 metalRoughTexIdx; // always linear, roughness in G channel, metalness in B channel + U32 normalTexIdx; // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0) + U32 occlusionTexIdx; // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded + U32 emissiveTexIdx; // always stored as sRGB, in nits (candela / meter^2) + + // texture coordinates + U32 baseColorTexCoords; + U32 metalRoughTexCoords; + U32 normalTexCoords; + U32 occlusionTexCoords; + U32 emissiveTexCoords; + + // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry?? + + bool hasPBR; + bool hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex; + + // This field is populated after upload + LLUUID material_uuid = LLUUID::null; + +}; + +class gltf_mesh +{ +public: + std::string name; + + // TODO add mesh import DJH 2022-04 + +}; + +class LLGLTFLoader : public LLModelLoader +{ + public: + typedef std::map<std::string, LLImportMaterial> material_map; + + 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,std::less<>> &jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit); //, + //bool preprocess ); + virtual ~LLGLTFLoader(); + + virtual bool OpenFile(const std::string &filename); + +protected: + tinygltf::Model mGltfModel; + bool mGltfLoaded; + bool mMeshesLoaded; + bool mMaterialsLoaded; + + std::vector<gltf_mesh> mMeshes; + std::vector<gltf_render_material> mMaterials; + + std::vector<gltf_texture> mTextures; + std::vector<gltf_image> mImages; + std::vector<gltf_sampler> mSamplers; + +private: + bool parseMeshes(); + void uploadMeshes(); + bool parseMaterials(); + void uploadMaterials(); + bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh); + LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + + // 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/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 3d31cfbb7f..4e3e49ec9f 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -334,162 +334,6 @@ void LLModel::normalizeVolumeFaces() } } -void LLModel::normalizeVolumeFacesAndWeights() -{ - if (!mVolumeFaces.empty()) - { - LLVector4a min, max; - - // For all of the volume faces - // in the model, loop over - // them and see what the extents - // of the volume along each axis. - min = mVolumeFaces[0].mExtents[0]; - max = mVolumeFaces[0].mExtents[1]; - - for (U32 i = 1; i < mVolumeFaces.size(); ++i) - { - LLVolumeFace& face = mVolumeFaces[i]; - - update_min_max(min, max, face.mExtents[0]); - update_min_max(min, max, face.mExtents[1]); - - if (face.mTexCoords) - { - LLVector2& min_tc = face.mTexCoordExtents[0]; - LLVector2& max_tc = face.mTexCoordExtents[1]; - - min_tc = face.mTexCoords[0]; - max_tc = face.mTexCoords[0]; - - for (S32 j = 1; j < face.mNumVertices; ++j) - { - update_min_max(min_tc, max_tc, face.mTexCoords[j]); - } - } - else - { - face.mTexCoordExtents[0].set(0, 0); - face.mTexCoordExtents[1].set(1, 1); - } - } - - // Now that we have the extents of the model - // we can compute the offset needed to center - // the model at the origin. - - // Compute center of the model - // and make it negative to get translation - // needed to center at origin. - LLVector4a trans; - trans.setAdd(min, max); - trans.mul(-0.5f); - - // Compute the total size along all - // axes of the model. - LLVector4a size; - size.setSub(max, min); - - // Prevent division by zero. - F32 x = size[0]; - F32 y = size[1]; - F32 z = size[2]; - F32 w = size[3]; - if (fabs(x) < F_APPROXIMATELY_ZERO) - { - x = 1.0; - } - if (fabs(y) < F_APPROXIMATELY_ZERO) - { - y = 1.0; - } - if (fabs(z) < F_APPROXIMATELY_ZERO) - { - z = 1.0; - } - size.set(x, y, z, w); - - // Compute scale as reciprocal of size - LLVector4a scale; - scale.splat(1.f); - scale.div(size); - - LLVector4a inv_scale(1.f); - inv_scale.div(scale); - - for (U32 i = 0; i < mVolumeFaces.size(); ++i) - { - LLVolumeFace& face = mVolumeFaces[i]; - - // We shrink the extents so - // that they fall within - // the unit cube. - // VFExtents change - face.mExtents[0].add(trans); - face.mExtents[0].mul(scale); - - face.mExtents[1].add(trans); - face.mExtents[1].mul(scale); - - // For all the positions, we scale - // the positions to fit within the unit cube. - LLVector4a* pos = (LLVector4a*)face.mPositions; - LLVector4a* norm = (LLVector4a*)face.mNormals; - LLVector4a* t = (LLVector4a*)face.mTangents; - - for (S32 j = 0; j < face.mNumVertices; ++j) - { - pos[j].add(trans); - pos[j].mul(scale); - if (norm && !norm[j].equals3(LLVector4a::getZero())) - { - norm[j].mul(inv_scale); - norm[j].normalize3(); - } - - if (t) - { - F32 w = t[j].getF32ptr()[3]; - t[j].mul(inv_scale); - t[j].normalize3(); - t[j].getF32ptr()[3] = w; - } - } - } - - weight_map old_weights = mSkinWeights; - mSkinWeights.clear(); - mPosition.clear(); - - for (auto& weights : old_weights) - { - LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]); - pos.add(trans); - pos.mul(scale); - LLVector3 scaled_pos(pos.getF32ptr()); - mPosition.push_back(scaled_pos); - mSkinWeights[scaled_pos] = weights.second; - } - - // mNormalizedScale is the scale at which - // we would need to multiply the model - // by to get the original size of the - // model instead of the normalized size. - LLVector4a normalized_scale; - normalized_scale.splat(1.f); - normalized_scale.div(scale); - mNormalizedScale.set(normalized_scale.getF32ptr()); - mNormalizedTranslation.set(trans.getF32ptr()); - mNormalizedTranslation *= -1.f; - - // remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation) - for (auto& face : mVolumeFaces) - { - face.mNormalizedScale = mNormalizedScale; - } - } -} - void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const { scale_out = mNormalizedScale; @@ -1717,21 +1561,11 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi { ret["joint_names"][i] = mJointNames[i]; - // For model to work at all there must be a matching bind matrix, - // so supply an indentity one if it isn't true - // Note: can build an actual bind matrix from joints - const LLMatrix4a& inv_bind = mInvBindMatrix.size() > i ? mInvBindMatrix[i] : LLMatrix4a::identity(); - if (i >= mInvBindMatrix.size()) - { - LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size " - << mInvBindMatrix.size() << LL_ENDL; - } - for (U32 j = 0; j < 4; j++) { for (U32 k = 0; k < 4; k++) { - ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k]; + ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; } } } @@ -1744,25 +1578,15 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } - // optional 'joint overrides' - if (include_joints && mAlternateBindMatrix.size() > 0) + if ( include_joints && mAlternateBindMatrix.size() > 0 ) { for (U32 i = 0; i < mJointNames.size(); ++i) { - // If there is not enough to match mJointNames, - // either supply no alternate matrixes at all or supply - // replacements - const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity(); - if (i >= mAlternateBindMatrix.size()) - { - LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " - << mAlternateBindMatrix.size() << LL_ENDL; - } for (U32 j = 0; j < 4; j++) { for (U32 k = 0; k < 4; k++) { - ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k]; + ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; } } } diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 5c6d0a55d2..fe28926720 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -202,7 +202,6 @@ public: void sortVolumeFacesByMaterialName(); void normalizeVolumeFaces(); - void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); void optimizeVolumeFaces(); diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index f97ac16a83..7facd53a72 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -150,8 +150,6 @@ void LLModelLoader::run() { mWarningsArray.clear(); doLoadModel(); - // todo: we are inside of a thread, push this into main thread worker, - // not into doOnIdleOneTime that laks tread safety doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this)); } @@ -468,58 +466,6 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector<std:: return true; } -void LLModelLoader::dumpDebugData() -{ - std::string log_file = mFilename + "_importer.txt"; - LLStringUtil::toLower(log_file); - llofstream file; - file.open(log_file.c_str()); - if (!file) - { - LL_WARNS() << "dumpDebugData failed to open file " << log_file << LL_ENDL; - return; - } - file << "Importing: " << mFilename << "\n"; - - std::map<std::string, LLMatrix4a> inv_bind; - std::map<std::string, LLMatrix4a> alt_bind; - for (LLPointer<LLModel>& mdl : mModelList) - { - - file << "Model name: " << mdl->mLabel << "\n"; - const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; - file << "Shape Bind matrix: " << skin_info.mBindShapeMatrix << "\n"; - file << "Skin Weights count: " << (S32)mdl->mSkinWeights.size() << "\n"; - - // some objects might have individual bind matrices, - // but for now it isn't accounted for - size_t joint_count = skin_info.mJointNames.size(); - for (size_t i = 0; i< joint_count;i++) - { - const std::string& joint = skin_info.mJointNames[i]; - if (skin_info.mInvBindMatrix.size() > i) - { - inv_bind[joint] = skin_info.mInvBindMatrix[i]; - } - if (skin_info.mAlternateBindMatrix.size() > i) - { - alt_bind[joint] = skin_info.mAlternateBindMatrix[i]; - } - } - } - - file << "Inv Bind matrices.\n"; - for (auto& bind : inv_bind) - { - file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; - } - - file << "Alt Bind matrices.\n"; - for (auto& bind : alt_bind) - { - file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; - } -} //called in the main thread void LLModelLoader::loadTextures() diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 7c808dcae0..aece922111 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -36,7 +36,7 @@ class LLJoint; typedef std::map<std::string, LLMatrix4> JointTransformMap; typedef std::map<std::string, LLMatrix4>::iterator JointTransformMapIt; -typedef std::map<std::string, std::string> JointMap; +typedef std::map<std::string, std::string, std::less<>> JointMap; typedef std::deque<std::string> JointNameSet; const S32 SLM_SUPPORTED_VERSION = 3; @@ -111,7 +111,6 @@ public: bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info) model_list mModelList; - // The scene is pretty much what ends up getting loaded for upload. Basically assign things to this guy if you want something uploaded. scene mScene; typedef std::queue<LLPointer<LLModel> > model_queue; @@ -120,14 +119,9 @@ public: model_queue mPhysicsQ; //map of avatar joints as named in COLLADA assets to internal joint names - // Do not use this for anything other than looking up the name of a joint. This is populated elsewhere. JointMap mJointMap; - - // The joint list is what you want to use to actually setup the specific joint transformations. JointTransformMap& mJointList; JointNameSet& mJointsFromNode; - - U32 mMaxJointsPerMesh; LLModelLoader( @@ -198,7 +192,6 @@ public: const LLSD logOut() const { return mWarningsArray; } void clearLog() { mWarningsArray.clear(); } - void dumpDebugData(); protected: diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index dee3d5ed59..98151e2f4d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -76,7 +76,6 @@ set(viewer_SOURCE_FILES gltf/accessor.cpp gltf/primitive.cpp gltf/animation.cpp - gltf/llgltfloader.cpp groupchatlistener.cpp llaccountingcostmanager.cpp llaisapi.cpp @@ -747,7 +746,6 @@ 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 8c9f77686a..c210b9c61d 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -50,10 +50,6 @@ 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") @@ -476,14 +472,11 @@ void Asset::update() for (auto& image : mImages) { - 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); - } + 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); } } } @@ -493,23 +486,18 @@ void Asset::update() bool Asset::prep() { LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; - // check required extensions + // check required extensions and fail if not supported + bool unsupported = false; for (auto& extension : mExtensionsRequired) { if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) { - if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end()) - { - LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; - mUnsupportedExtensions.push_back(extension); - } - else - { - mIgnoredExtensions.push_back(extension); - } + LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; + unsupported = true; } } - if (mUnsupportedExtensions.size() > 0) + + if (unsupported) { return false; } @@ -525,7 +513,7 @@ bool Asset::prep() for (auto& image : mImages) { - if (!image.prep(*this, mLoadIntoVRAM)) + if (!image.prep(*this)) { return false; } @@ -554,106 +542,102 @@ bool Asset::prep() return false; } } - if (mLoadIntoVRAM) - { - // prepare vertex buffers - // material count is number of materials + 1 for default material - U32 mat_count = (U32) mMaterials.size() + 1; + // 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(); + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); + } + + 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); } - for (S32 double_sided = 0; double_sided < 2; ++double_sided) + // for each material + for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) { - RenderData& rd = mRenderData[double_sided]; - for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) + // 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) { - rd.mBatches[i].resize(mat_count); + continue; } - // for each material - for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) + for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) { - // 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) + U32 attribute_mask = 0; + // for each mesh + for (auto& mesh : mMeshes) { - continue; - } - - for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) - { - U32 attribute_mask = 0; - // for each mesh - for (auto& mesh : mMeshes) + // for each primitive + for (auto& primitive : mesh.mPrimitives) { - // for each primitive - for (auto& primitive : mesh.mPrimitives) + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) { - if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) - { - // accumulate vertex and index counts - primitive.mVertexOffset = vertex_count[variant]; - primitive.mIndexOffset = index_count[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; - #if 0 - LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); + // allocate vertex buffer and pack it + if (vertex_count[variant] > 0) + { + U32 mat_idx = mat_id + 1; + 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& mesh : mMeshes) + { + for (auto& primitive : mesh.mPrimitives) { - for (auto& primitive : mesh.mPrimitives) + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) { - if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) - { - primitive.upload(vb); - } + primitive.upload(vb); } } + } - vb->unmapBuffer(); + vb->unmapBuffer(); - vb->unbind(); - #endif - } + vb->unbind(); } } } + } - // sanity check that all primitives have a vertex buffer - for (auto& mesh : mMeshes) + // sanity check that all primitives have a vertex buffer + for (auto& mesh : mMeshes) + { + for (auto& primitive : mesh.mPrimitives) { - for (auto& primitive : mesh.mPrimitives) - { - //llassert(primitive.mVertexBuffer.notNull()); - } + llassert(primitive.mVertexBuffer.notNull()); } } - #if 0 + // build render batches for (S32 node_id = 0; node_id < mNodes.size(); ++node_id) { @@ -680,7 +664,6 @@ bool Asset::prep() } } } - #endif return true; } @@ -689,10 +672,9 @@ Asset::Asset(const Value& src) *this = src; } -bool Asset::load(std::string_view filename, bool loadIntoVRAM) +bool Asset::load(std::string_view filename) { LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; - mLoadIntoVRAM = loadIntoVRAM; mFilename = filename; std::string ext = gDirUtilp->getExtension(mFilename); @@ -710,7 +692,7 @@ bool Asset::load(std::string_view filename, bool loadIntoVRAM) } else if (ext == "glb") { - return loadBinary(str, mLoadIntoVRAM); + return loadBinary(str); } else { @@ -727,9 +709,8 @@ bool Asset::load(std::string_view filename, bool loadIntoVRAM) return false; } -bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM) +bool Asset::loadBinary(const std::string& data) { - mLoadIntoVRAM = loadIntoVRAM; // load from binary gltf const U8* ptr = (const U8*)data.data(); const U8* end = ptr + data.size(); @@ -954,9 +935,8 @@ void Asset::eraseBufferView(S32 bufferView) LLViewerFetchedTexture* fetch_texture(const LLUUID& id); -bool Image::prep(Asset& asset, bool loadIntoVRAM) +bool Image::prep(Asset& asset) { - 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 @@ -971,12 +951,12 @@ bool Image::prep(Asset& asset, bool loadIntoVRAM) { // embedded in a buffer, load the texture from the buffer BufferView& bufferView = asset.mBufferViews[mBufferView]; Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; - if (mLoadIntoTexturePipe) - { - U8* data = buffer.mData.data() + bufferView.mByteOffset; - mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); - } - else if (mTexture.isNull() && mLoadIntoTexturePipe) + + U8* data = buffer.mData.data() + bufferView.mByteOffset; + + mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); + + if (mTexture.isNull()) { LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -991,12 +971,12 @@ bool Image::prep(Asset& asset, bool loadIntoVRAM) std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri; LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file); - if (tracking_id.notNull() && mLoadIntoTexturePipe) + if (tracking_id.notNull()) { LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); mTexture = LLViewerTextureManager::getFetchedTexture(world_id); } - else if (mLoadIntoTexturePipe) + else { LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -1011,7 +991,7 @@ bool Image::prep(Asset& asset, bool loadIntoVRAM) return false; } - if (!asset.mFilename.empty() && mLoadIntoTexturePipe) + if (!asset.mFilename.empty()) { // 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 b9554d753c..27821659db 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -286,7 +286,6 @@ 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: @@ -302,8 +301,6 @@ namespace LL S32 mBits = -1; S32 mPixelType = -1; - bool mLoadIntoTexturePipe = false; - LLPointer<LLViewerFetchedTexture> mTexture; const Image& operator=(const Value& src); @@ -319,7 +316,7 @@ namespace LL // preserve only uri and name void clearData(Asset& asset); - bool prep(Asset& asset, bool loadIntoVRAM); + bool prep(Asset& asset); }; // Render Batch -- vertex buffer and list of primitives to render using @@ -394,10 +391,6 @@ 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(); @@ -435,12 +428,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 loadIntoVRAM); + bool load(std::string_view filename); // load .glb contents from memory // data - binary contents of .glb file // returns result of prep() on success - bool loadBinary(const std::string& data, bool loadIntoVRAM); + bool loadBinary(const std::string& data); 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 be36c5e90b..ef9bba8128 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -159,12 +159,6 @@ 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); @@ -375,11 +369,6 @@ 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 deleted file mode 100644 index 7ae255e054..0000000000 --- a/indra/newview/gltf/llgltfloader.cpp +++ /dev/null @@ -1,1710 +0,0 @@ -/** - * @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 deleted file mode 100644 index 048f7df7f5..0000000000 --- a/indra/newview/gltf/llgltfloader.h +++ /dev/null @@ -1,203 +0,0 @@ -/** - * @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 3cb5e9a0d7..9faead9533 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, true)) + if (asset->load(filename)) { gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions asset->updateTransforms(); diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 41e954b7fa..716e6cd9e3 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, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0" +#define MODEL_FILTER L"Model files (*.dae)\0*.dae\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,8 +217,6 @@ bool LLFilePicker::setupFilter(ELoadFilter filter) break; case FFLOAD_MODEL: mOFN.lpstrFilter = MODEL_FILTER \ - COLLADA_FILTER \ - MATERIAL_FILTER \ L"\0"; break; case FFLOAD_MATERIAL: @@ -673,8 +671,6 @@ 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/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index b94c31ec04..5ee93be061 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -179,7 +179,7 @@ void LLFloaterBvhPreview::setAnimCallbacks() getChild<LLUICtrl>("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1)); } -std::map <std::string, std::string> LLFloaterBvhPreview::getJointAliases() +std::map<std::string, std::string, std::less<>> LLFloaterBvhPreview::getJointAliases() { LLPointer<LLVOAvatar> av = (LLVOAvatar*)mAnimPreview->getDummyAvatar(); return av->getJointAliases(); @@ -252,7 +252,7 @@ bool LLFloaterBvhPreview::postBuild() ELoadStatus load_status = E_ST_OK; S32 line_number = 0; - std::map<std::string, std::string> joint_alias_map = getJointAliases(); + auto joint_alias_map = getJointAliases(); loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map); std::string status = getString(STATUS[load_status]); diff --git a/indra/newview/llfloaterbvhpreview.h b/indra/newview/llfloaterbvhpreview.h index ae64521492..bb69ab65ef 100644 --- a/indra/newview/llfloaterbvhpreview.h +++ b/indra/newview/llfloaterbvhpreview.h @@ -108,7 +108,7 @@ public: S32 status, LLExtStat ext_status); private: void setAnimCallbacks() ; - std::map <std::string, std::string> getJointAliases(); + std::map<std::string, std::string, std::less<>> getJointAliases(); protected: diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 84b9cb18f8..47471edb92 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -64,7 +64,6 @@ #include "llcallbacklist.h" #include "llviewertexteditor.h" #include "llviewernetwork.h" -#include "llmaterialeditor.h" //static @@ -620,9 +619,11 @@ 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) @@ -756,7 +757,7 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) mModelPreview->onLODMeshOptimizerParamCommit(lod, enforce_tri_limit, mode); break; default: - LL_ERRS() << "Only supposed to be called to generate models" << LL_ENDL; + LL_ERRS() << "Only supposed to be called to generate models, val: " << mode << LL_ENDL; break; } @@ -1487,7 +1488,7 @@ void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides) { // Populate table - std::map<std::string, std::string> joint_alias_map; + std::map<std::string, std::string, std::less<>> joint_alias_map; mModelPreview->getJointAliases(joint_alias_map); S32 conflicts = 0; diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 378f5fdf91..28160177f6 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -137,8 +137,7 @@ LLFloaterComboOptions* LLFloaterComboOptions::showUI( { combo_picker->mComboOptions->addSimpleElement(*iter); } - // select 'Bulk Upload All' option - combo_picker->mComboOptions->selectNthItem((S32)options.size() - 1); + combo_picker->mComboOptions->selectFirstItem(); combo_picker->openFloater(LLSD(title)); combo_picker->setFocus(true); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index fc0a3ec58f..49c0006f66 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -30,7 +30,7 @@ #include "llmodelloader.h" #include "lldaeloader.h" -#include "gltf/llgltfloader.h" +#include "llgltfloader.h" #include "llfloatermodelpreview.h" #include "llagent.h" @@ -40,7 +40,6 @@ #include "lldrawable.h" #include "llface.h" #include "lliconctrl.h" -#include "lljointdata.h" #include "llmatrix4a.h" #include "llmeshrepository.h" #include "llmeshoptimizer.h" @@ -781,7 +780,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mLODFile[lod] = filename; - std::map<std::string, std::string> joint_alias_map; + std::map<std::string, std::string, std::less<>> joint_alias_map; getJointAliases(joint_alias_map); LLHandle<LLModelPreview> preview_handle = getHandle(); @@ -811,9 +810,6 @@ 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, @@ -826,8 +822,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mJointsFromNode, joint_alias_map, LLSkinningUtil::getMaxJointCount(), - gSavedSettings.getU32("ImporterModelLimit"), - viewer_skeleton); + gSavedSettings.getU32("ImporterModelLimit")); } if (force_disable_slm) @@ -3095,48 +3090,25 @@ 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); - - // 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) + std::string::size_type i = lod_filename_lower.rfind(ext); + if (i != std::string::npos) { - std::string::size_type i = lod_filename_lower.rfind(ext); - if (i != std::string::npos) - { - ext_pos = i; - found_ext = ext; - break; - } + lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext); } - - if (ext_pos != std::string::npos) + if (gDirUtilp->fileExists(lod_filename)) { - // 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 + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (fmp) { - lookupLODModelFiles(next_lod); + fmp->setCtrlLoadFromFile(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 47f58afa00..cee43f3cff 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -135,12 +135,6 @@ 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) @@ -360,8 +354,7 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a { rig_info_tab[joint_num].setIsRiggedTo(true); - size_t bind_poses_size = skin->mBindPoseMatrix.size(); - const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity(); + const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index]; LLVector4a pos_joint_space; mat.affineTransform(pos, pos_joint_space); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index d9a3ec3004..dcba891f9f 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -6306,13 +6306,13 @@ const LLUUID& LLVOAvatar::getID() const // getJoint() //----------------------------------------------------------------------------- // RN: avatar joints are multi-rooted to include screen-based attachments -LLJoint *LLVOAvatar::getJoint( const std::string &name ) +LLJoint* LLVOAvatar::getJoint(std::string_view name) { joint_map_t::iterator iter = mJointMap.find(name); - LLJoint* jointp = NULL; + LLJoint* jointp = nullptr; - if (iter == mJointMap.end() || iter->second == NULL) + if (iter == mJointMap.end() || iter->second == nullptr) { //search for joint and cache found joint in lookup table if (mJointAliasMap.empty()) { @@ -6329,7 +6329,7 @@ LLJoint *LLVOAvatar::getJoint( const std::string &name ) canonical_name = name; } jointp = mRoot->findJoint(canonical_name); - mJointMap[name] = jointp; + mJointMap[std::string(name)] = jointp; } else { //return cached pointer diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index ab27c5752d..9eb8d3f880 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -202,7 +202,7 @@ public: void startDefaultMotions(); void dumpAnimationState(); - virtual LLJoint* getJoint(const std::string &name); + virtual LLJoint* getJoint(std::string_view name); LLJoint* getJoint(S32 num); void initAllJoints(); diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 90ff4067f2..ebba9ba291 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -697,17 +697,17 @@ void LLVOAvatarSelf::idleUpdate(LLAgent &agent, const F64 &time) } // virtual -LLJoint *LLVOAvatarSelf::getJoint(const std::string &name) +LLJoint* LLVOAvatarSelf::getJoint(std::string_view name) { std::lock_guard lock(mJointMapMutex); - LLJoint *jointp = NULL; + LLJoint* jointp = nullptr; jointp = LLVOAvatar::getJoint(name); if (!jointp && mScreenp) { jointp = mScreenp->findJoint(name); if (jointp) { - mJointMap[name] = jointp; + mJointMap[std::string(name)] = jointp; } } if (jointp && jointp != mScreenp && jointp != mRoot) diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index f9bea41b1d..f7cd974ab0 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -90,7 +90,7 @@ public: /*virtual*/ bool hasMotionFromSource(const LLUUID& source_id); /*virtual*/ void stopMotionFromSource(const LLUUID& source_id); /*virtual*/ void requestStopMotion(LLMotion* motion); - /*virtual*/ LLJoint* getJoint(const std::string &name); + /*virtual*/ LLJoint* getJoint(std::string_view name); /*virtual*/ void renderJoints(); 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 f8543ba1aa..90223fcda8 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: Model parsing issue - see log for details.</string> + <string name="status_parse_error">Error: Dae 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,7 +45,6 @@ <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> @@ -62,22 +61,6 @@ <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" @@ -1723,6 +1706,7 @@ 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 82e77119ab..a51feeb7ab 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -9453,11 +9453,8 @@ Unable to upload texture: '[NAME]' icon="alertmodal.tga" name="CannotUploadMaterial" type="alertmodal"> -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. +There was a problem uploading the file <tag>fail</tag> - <usetemplate - name="okbutton" - yestext="OK"/> </notification> <notification |