diff options
author | Erik Kundiman <erik@megapahit.org> | 2025-06-24 17:26:03 +0800 |
---|---|---|
committer | Erik Kundiman <erik@megapahit.org> | 2025-06-24 17:26:03 +0800 |
commit | b024b356c75528b4d2688016d49a11e1270fb48d (patch) | |
tree | 7913efa6bdcc38510f5e3743887c295a213f77c0 | |
parent | 7f48cc4f84a6e5b525570b7e1ccfad52187b6420 (diff) | |
parent | 1a6e3286110f9e54611715b44c5f8b47d08dc310 (diff) |
Merge tag 'Second_Life_Project#1a6e3286-GLTF_Mesh_Import' into gltf_mesh_import
-rw-r--r-- | indra/llappearance/CMakeLists.txt | 1 | ||||
-rw-r--r-- | indra/llappearance/llavatarappearance.cpp | 52 | ||||
-rw-r--r-- | indra/llappearance/llavatarappearance.h | 6 | ||||
-rw-r--r-- | indra/llappearance/lljointdata.h | 44 | ||||
-rw-r--r-- | indra/llprimitive/llmodel.cpp | 26 | ||||
-rw-r--r-- | indra/newview/gltf/llgltfloader.cpp | 1855 | ||||
-rw-r--r-- | indra/newview/gltf/llgltfloader.h | 160 | ||||
-rw-r--r-- | indra/newview/llfloatermodelpreview.cpp | 5 | ||||
-rw-r--r-- | indra/newview/llmodelpreview.cpp | 7 | ||||
-rw-r--r-- | indra/newview/llskinningutil.cpp | 3 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/floater_model_preview.xml | 16 |
11 files changed, 1251 insertions, 924 deletions
diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index f3f822f61c..b382e21b3c 100644 --- a/indra/llappearance/CMakeLists.txt +++ b/indra/llappearance/CMakeLists.txt @@ -14,6 +14,7 @@ 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 3d66809ed6..13bea1e5ea 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -29,16 +29,17 @@ #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; @@ -71,6 +72,7 @@ public: mChildren.clear(); } bool parseXml(LLXmlTreeNode* node); + glm::mat4 getJointMatrix(); private: std::string mName; @@ -106,10 +108,16 @@ 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; }; @@ -1623,6 +1631,21 @@ 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() //----------------------------------------------------------------------------- @@ -1653,6 +1676,21 @@ 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.mRestMatrix = parent_mat * data.mJointMatrix; + 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) { @@ -1714,6 +1752,16 @@ 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 13e504e639..2748da9a1d 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -34,6 +34,7 @@ #include "lltexlayer.h" #include "llviewervisualparam.h" #include "llxmltree.h" +#include "v4math.h" class LLTexLayerSet; class LLTexGlobalColor; @@ -41,6 +42,7 @@ class LLTexGlobalColorInfo; class LLWearableData; class LLAvatarBoneInfo; class LLAvatarSkeletonInfo; +class LLJointData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // LLAvatarAppearance @@ -153,7 +155,9 @@ public: const avatar_joint_list_t& getSkeleton() { return mSkeleton; } typedef std::map<std::string, std::string> 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 new file mode 100644 index 0000000000..549f4af041 --- /dev/null +++ b/indra/llappearance/lljointdata.h @@ -0,0 +1,44 @@ +/** + * @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; + glm::mat4 mJointMatrix; + glm::mat4 mRestMatrix; + + typedef std::vector<LLJointData> bones_t; + bones_t mChildren; +}; + +#endif //LL_LLJOINTDATA_H diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 0bc7846023..9ecc2c2a7d 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1717,11 +1717,21 @@ 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] = mInvBindMatrix[i].mMatrix[j][k]; + ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k]; } } } @@ -1734,15 +1744,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } - if ( include_joints && mAlternateBindMatrix.size() > 0 ) + // optional 'joint overrides' + 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] = mAlternateBindMatrix[i].mMatrix[j][k]; + ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k]; } } } diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5cdd7f09e0..7a1ebf0610 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -52,11 +52,14 @@ #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] = { @@ -76,6 +79,13 @@ static const glm::mat4 coord_system_rotation( ); +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, @@ -87,7 +97,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, JointNameSet & jointsFromNodes, std::map<std::string, std::string> &jointAliasMap, U32 maxJointsPerMesh, - U32 modelLimit) //, + U32 modelLimit, + std::vector<LLJointData> viewer_skeleton) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -99,10 +110,9 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ), - //mPreprocessGLTF(preprocess), - mMeshesLoaded(false), - mMaterialsLoaded(false) + maxJointsPerMesh ) + , mGeneratedModelLimit(modelLimit) + , mViewerJointData(viewer_skeleton) { } @@ -124,15 +134,109 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) notifyUnsupportedExtension(false); - mMeshesLoaded = parseMeshes(); - if (mMeshesLoaded) uploadMeshes(); - - mMaterialsLoaded = parseMaterials(); - if (mMaterialsLoaded) uploadMaterials(); + bool meshesLoaded = parseMeshes(); setLoadState(DONE); - return (mMeshesLoaded); + 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() @@ -151,103 +255,179 @@ bool LLGLTFLoader::parseMeshes() node.makeMatrixValid(); } + if (mGLTFAsset.mSkins.size() > 0) + { + checkForXYrotation(mGLTFAsset.mSkins[0]); + } + // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. - for (auto& skin : mGLTFAsset.mSkins) + for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) { - populateJointFromSkin(skin); + populateJointFromSkin(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; - // Process each node - for (auto& node : mGLTFAsset.mNodes) + // Check if we have scenes defined + if (!mGLTFAsset.mScenes.empty()) { - LLMatrix4 transformation; - material_map mats; - auto meshidx = node.mMesh; + // Process the default scene (or first scene if no default) + S32 scene_idx = mGLTFAsset.mScene >= 0 ? mGLTFAsset.mScene : 0; - if (meshidx >= 0) + if (scene_idx < mGLTFAsset.mScenes.size()) { - if (mGLTFAsset.mMeshes.size() > meshidx) - { - LLModel* pModel = new LLModel(volume_params, 0.f); - auto mesh = mGLTFAsset.mMeshes[meshidx]; + 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; - // Get base mesh name and track usage - std::string base_name = mesh.mName; - if (base_name.empty()) + // 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())) { - base_name = "mesh_" + std::to_string(meshidx); + processNodeHierarchy(root_idx, mesh_name_counts, submodel_limit, volume_params); } + } + } + } + else + { + LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; - S32 instance_count = mesh_name_counts[base_name]++; + LLSD args; + args["Message"] = "NoScenesFound"; + mWarningsArray.append(args); + return false; + } - if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && - (LLModel::NO_ERRORS == pModel->getStatus()) && - validate_model(pModel)) - { - mModelList.push_back(pModel); + // 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; - mTransform.setIdentity(); - transformation = mTransform; + LLSD args; + args["Message"] = "TooManyMeshParts"; + args["PART_COUNT"] = static_cast<S32>(total_models); + args["LIMIT"] = static_cast<S32>(mGeneratedModelLimit); + mWarningsArray.append(args); + return false; + } - // adjust the transformation to compensate for mesh normalization - LLVector3 mesh_scale_vector; - LLVector3 mesh_translation_vector; - pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + return true; +} - LLMatrix4 mesh_translation; - mesh_translation.setTranslation(mesh_translation_vector); - mesh_translation *= transformation; - transformation = mesh_translation; +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; - LLMatrix4 mesh_scale; - mesh_scale.initScale(mesh_scale_vector); - mesh_scale *= transformation; - transformation = mesh_scale; + auto& node = mGLTFAsset.mNodes[node_idx]; - 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; - } + LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" + << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") + << " - children: " << node.mChildren.size() << 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); - } + // Process this node's mesh if it has one + if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) + { + LLMatrix4 transformation; + material_map mats; - mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); - stretch_extents(pModel, transformation); - } - else - { - setLoadState(ERROR_MODEL + pModel->getStatus()); - delete pModel; - return false; - } + 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; - return true; + 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 @@ -293,6 +473,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& 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); @@ -312,7 +494,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform); // Combine transforms: coordinate rotation applied to hierarchy transform - const glm::mat4 final_transform = coord_system_rotation * 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; @@ -322,13 +508,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // 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_count; + 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_count.resize(jointCnt); + gltf_joint_index_use.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) @@ -340,317 +526,430 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) == mJointMap.end()) { - gltf_joint_index_use_count[i] = -1; // mark as unsupported + // This might need to hold a substitute index + gltf_joint_index_use[i] = -1; // mark as unsupported } } } - auto prims = mesh.mPrimitives; - for (auto prim : prims) + for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) { - // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. - if (prim.getVertexCount() < USHRT_MAX) - { - // 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; - std::vector<U16> indices; + const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; - LLImportMaterial impMat; - impMat.mDiffuseColor = LLColor4::white; // Default color + // 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; - // Process material if available - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + 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) { - 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 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; - if (texIndex < mGLTFAsset.mTextures.size()) + S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; + if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.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()) { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + // URI might be a remote URL or a local path + std::string filename = image.mUri; - // Use URI as texture file name - if (!image.mUri.empty()) + // Extract just the filename from the URI + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) { - // 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 << LL_ENDL; - - // 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 - { - // Let the model preview know we need to load this texture - mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL; - } + filename = filename.substr(pos + 1); } - else if (image.mTexture.notNull()) + + // 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()) { - // 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; + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; } - else if (image.mBufferView >= 0) + else { - // For embedded textures (no URI but has buffer data) - // Create a pseudo filename for the embedded texture - std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; - impMat.mDiffuseMapFilename = pseudo_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; - - // Mark for loading - mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + // 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; } } } } } + } - // 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; + 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); + 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); - - 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["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); } - - if (prim.getIndexCount() % 3 != 0) + else { - LL_WARNS("GLTF_IMPORT") << "Invalid primitive: index count " << prim.getIndexCount() - << " is not divisible by 3. GLTF files must contain triangulated geometry." << LL_ENDL; - LLSD args; - args["Message"] = "InvalidGeometryNonTriangulated"; - mWarningsArray.append(args); - continue; // Skip this primitive + // 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; } - // When processing indices, flip winding order if needed - for (U32 i = 0; i < prim.getIndexCount(); i += 3) + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + + if (skinIdx >= 0) { - if (hasNegativeScale) + 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) { - // 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]); + 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 { - indices.push_back(prim.mIndexArray[i]); - indices.push_back(prim.mIndexArray[i + 1]); - indices.push_back(prim.mIndexArray[i + 2]); + 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()) + // 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 { - LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL; - continue; // Skip this primitive + 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); } - std::vector<LLVolumeFace::VertexData> faceVertices; - glm::vec3 min = glm::vec3(FLT_MAX); - glm::vec3 max = glm::vec3(-FLT_MAX); + 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); - for (U32 i = 0; i < vertices.size(); i++) + if (skinIdx >= 0) { - LLVolumeFace::VertexData vert; - - // Update min/max bounds - if (i == 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) { - min = max = vertices[i].position; + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + gltf_joint_index_use[vertices[i].joints.x]++; } - else + if (gltf_joint_index_use[vertices[i].joints.y] >= 0 + && vertices[i].weights.y > 0.f) { - 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); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + gltf_joint_index_use[vertices[i].joints.y]++; } - - 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) + if (gltf_joint_index_use[vertices[i].joints.z] >= 0 + && vertices[i].weights.z > 0.f) { - // 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_count[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_count[vertices[i].joints.x]++; - } - if (gltf_joint_index_use_count[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_count[vertices[i].joints.y]++; - } - if (gltf_joint_index_use_count[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_count[vertices[i].joints.z]++; - } - if (gltf_joint_index_use_count[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_count[vertices[i].joints.w]++; - } + 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::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - std::vector<LLModel::JointWeight> wght; - F32 total = 0.f; + 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; - } + 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 (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; - } + if (wght.size() > 0) + { + pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } } + } - 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); + // 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; - pModel->getVolumeFaces().push_back(face); + if (materialName.empty()) + { + materialName = "mat" + std::to_string(prim.mMaterial); + } + } + else + { + materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + } + mats[materialName] = impMat; - // Create a unique material name for this primitive - std::string materialName; - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + // 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) { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - materialName = material->mName; + // 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]); + } + } - if (materialName.empty()) + // 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) { - materialName = "mat" + std::to_string(prim.mMaterial); + // 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); } } - else + if (indices_16.size() > 0 && face_verts.size() > 0) { - materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + 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); - mats[materialName] = impMat; - } - else { - LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; - LLSD args; - args["Message"] = "ParsingErrorBadElement"; - mWarningsArray.append(args); - return false; } } @@ -662,27 +961,21 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; - - size_t jointCnt = gltf_skin.mJoints.size(); - if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size()) - { - LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; - LLSD args; - args["Message"] = "InvBindCountMismatch"; - mWarningsArray.append(args); - } + S32 valid_joints_count = mValidJointsCount[skinIdx]; std::vector<S32> gltfindex_to_joitindex_map; + size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; + S32 stripped_valid_joints = 0; for (size_t i = 0; i < jointCnt; ++i) { // Process joint name and idnex S32 joint = gltf_skin.mJoints[i]; - if (gltf_joint_index_use_count[i] <= 0) + if (gltf_joint_index_use[i] < 0) { - // Unused (0) or unsupported (-1) joint, drop it + // unsupported (-1) joint, drop it continue; } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; @@ -694,52 +987,47 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } else { - llassert(false); // should have been stopped by gltf_joint_index_use_count[i] == -1 + llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 continue; } - gltfindex_to_joitindex_map[i] = replacement_index++; - - skin_info.mJointNames.push_back(legal_name); - skin_info.mJointNums.push_back(-1); - if (i < gltf_skin.mInverseBindMatricesData.size()) + if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT + && gltf_joint_index_use[i] == 0 + && legal_name != "mPelvis") { - // Use pre-computed coord_system_rotation instead of recreating it - LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; + // Unused (0) joint + // 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. + // But if model is over limid, drop extras sans pelvis. + // Keeping 'pelvis' is a workaround to keep preview whole. + // Todo: consider improving this, either take as much as possible or + // ensure common parents/roots are included + continue; + } - glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); - glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); + gltfindex_to_joitindex_map[i] = replacement_index++; - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); - skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); + skin_info.mJointNames.push_back(legal_name); + skin_info.mJointNums.push_back(-1); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + // In scope of same skin multiple meshes reuse same bind matrices + skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[skinIdx][i]); - // For alternate bind matrix, use the ORIGINAL joint transform (before rotation) - // Get the original joint node and use its matrix directly - S32 joint = gltf_skin.mJoints[i]; - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJoint(joint, gltf_skin); // skeleton can have multiple real roots - if (root_joint == joint) - { - // This is very likely incomplete in some way. - // Root shouldn't be the only one to need full coordinate fix - joint_mat = coord_system_rotation * joint_mat; - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); + } - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); - } - else - { - // For gltf mInverseBindMatrices are optional, but not for viewer - // todo: get a model that triggers this - skin_info.mInvBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); // might need to be an 'identity' - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); - } + 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 @@ -755,62 +1043,195 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& return true; } -void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) +void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { - LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL; + 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); + 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; - for (auto joint : skin.mJoints) + 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) { - auto jointNode = mGLTFAsset.mNodes[joint]; + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, 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; + } + + // 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 { - // ignore unrecognized joint - LL_DEBUGS("GLTF") << "Ignoring joint: " << legal_name << LL_ENDL; - continue; + // 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)); } - // Debug: Log original joint matrix - glm::mat4 gltf_joint_matrix = jointNode.mMatrix; - LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' original matrix:" << LL_ENDL; - for(int i = 0; i < 4; i++) + // 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) { - LL_INFOS("GLTF_DEBUG") << " [" << gltf_joint_matrix[i][0] << ", " << gltf_joint_matrix[i][1] - << ", " << gltf_joint_matrix[i][2] << ", " << gltf_joint_matrix[i][3] << "]" << LL_ENDL; + // 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); } + } +} - // Apply coordinate system rotation to joint transform - glm::mat4 rotated_joint_matrix = coord_system_rotation * gltf_joint_matrix; - // Debug: Log rotated joint matrix - LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' rotated matrix:" << LL_ENDL; - for(int i = 0; i < 4; i++) +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++) { - LL_INFOS("GLTF_DEBUG") << " [" << rotated_joint_matrix[i][0] << ", " << rotated_joint_matrix[i][1] - << ", " << rotated_joint_matrix[i][2] << ", " << rotated_joint_matrix[i][3] << "]" << LL_ENDL; + 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); - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_joint_matrix)); - mJointList[legal_name] = gltf_transform; - mJointsFromNode.push_front(legal_name); - - LL_INFOS("GLTF_DEBUG") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; - } + return -1; } -S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const +S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const { - S32 root_joint = 0; - S32 found_joint = source_joint; + S32 root_node = 0; + S32 found_node = source_joint_node; S32 size = (S32)gltf_skin.mJoints.size(); do { - root_joint = found_joint; + root_node = found_node; for (S32 i = 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; @@ -818,514 +1239,328 @@ S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& glt if (mJointMap.find(jointNode.mName) != mJointMap.end()) { - std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); if (it != jointNode.mChildren.end()) { - found_joint = joint; + // Found node's parent + found_node = joint; break; } } } - } while (root_joint != found_joint); + } while (root_node != found_node); - return root_joint; + return root_node; } -S32 LLGLTFLoader::findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const +S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const { - S32 root_joint = 0; - S32 found_joint = 0; + S32 root_node = 0; + S32 found_node = 0; S32 size = (S32)gltf_skin.mJoints.size(); do { - root_joint = found_joint; + 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_joint); + std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); if (it != jointNode.mChildren.end()) { - found_joint = joint; + // Found node's parent + found_node = joint; break; } } - } while (root_joint != found_joint); + } while (root_node != found_node); LL_INFOS("GLTF_DEBUG") << "mJointList name: "; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_joint]; - LL_CONT << jointNode.mName << " index: " << root_joint << LL_ENDL; - return root_joint; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node]; + LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL; + return root_node; } -bool LLGLTFLoader::parseMaterials() +S32 LLGLTFLoader::findParentNode(S32 node) const { - if (!mGltfLoaded) return false; - - // fill local texture data structures - mSamplers.clear(); - for (auto& in_sampler : mGLTFAsset.mSamplers) + S32 size = (S32)mGLTFAsset.mNodes.size(); + for (S32 i = 0; i < size; i++) { - gltf_sampler sampler; - sampler.magFilter = in_sampler.mMagFilter > 0 ? in_sampler.mMagFilter : GL_LINEAR; - sampler.minFilter = in_sampler.mMinFilter > 0 ? in_sampler.mMinFilter : GL_LINEAR; - sampler.wrapS = in_sampler.mWrapS; - sampler.wrapT = in_sampler.mWrapT; - sampler.name = in_sampler.mName; - mSamplers.push_back(sampler); - } - - mImages.clear(); - for (auto& in_image : mGLTFAsset.mImages) - { - gltf_image image; - image.numChannels = in_image.mComponent; - image.bytesPerChannel = in_image.mBits >> 3; // Convert bits to bytes - image.pixelType = in_image.mPixelType; - image.size = 0; // We'll calculate this below if we have valid dimensions - - // Get dimensions from the texture if available - if (in_image.mTexture && in_image.mTexture->getDiscardLevel() >= 0) + 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()) { - image.height = in_image.mTexture->getHeight(); - image.width = in_image.mTexture->getWidth(); - // Since we don't have direct access to the raw data, we'll use the dimensions to calculate size - if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) - { - image.size = static_cast<U32>(image.height * image.width * image.numChannels * image.bytesPerChannel); - } - } - else - { - // Fallback to provided dimensions - image.height = in_image.mHeight; - image.width = in_image.mWidth; - if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) - { - image.size = static_cast<U32>(image.height * image.width * image.numChannels * image.bytesPerChannel); - } + return i; } - - // If we couldn't determine the size, skip this image - if (image.size == 0) - { - LL_WARNS("GLTF_IMPORT") << "Image size could not be determined" << LL_ENDL; - continue; - } - - // We don't have direct access to the image data, so data pointer remains nullptr - image.data = nullptr; - mImages.push_back(image); } + return -1; +} - mTextures.clear(); - for (auto& in_tex : mGLTFAsset.mTextures) +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) 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()) { - gltf_texture tex; - tex.imageIdx = in_tex.mSource; - tex.samplerIdx = in_tex.mSampler; - tex.imageUuid.setNull(); + S32 gltf_node_idx = found_node->second; + JointNodeData& node = gltf_nodes[gltf_node_idx]; + node.mIsOverrideValid = true; - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) + glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; + if (mApplyXYRotation) { - LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; - return false; + gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; } - - mTextures.push_back(tex); + node.mOverrideMatrix = glm::inverse(parent_rest) * gltf_joint_rest_pose; + + glm::vec3 override; + glm::vec3 skew; + glm::vec3 scale; + glm::vec4 perspective; + glm::quat rotation; + glm::decompose(node.mOverrideMatrix, scale, rotation, override, skew, perspective); + glm::vec3 translate; + glm::decompose(viewer_data.mJointMatrix, scale, rotation, translate, skew, perspective); + glm::mat4 viewer_joint = glm::recompose(scale, rotation, override, skew, perspective); + + node.mOverrideMatrix = viewer_joint; + rest = parent_rest * node.mOverrideMatrix; + node.mOverrideRestMatrix = rest; } - - // parse each material - mMaterials.clear(); - for (const auto& gltf_material : mGLTFAsset.mMaterials) + else { - gltf_render_material mat; - mat.name = gltf_material.mName; - - // PBR Metallic Roughness properties - mat.hasPBR = true; - - // Base color factor - mat.baseColor = LLColor4( - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[0], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[1], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[2], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[3] - ); - - // Base color texture - mat.hasBaseTex = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0; - mat.baseColorTexIdx = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex; - mat.baseColorTexCoords = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mTexCoord; - - // Metalness and roughness - mat.metalness = gltf_material.mPbrMetallicRoughness.mMetallicFactor; - mat.roughness = gltf_material.mPbrMetallicRoughness.mRoughnessFactor; - - // Metallic-roughness texture - mat.hasMRTex = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex >= 0; - mat.metalRoughTexIdx = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex; - mat.metalRoughTexCoords = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTexCoord; - - // Normal texture - mat.normalScale = gltf_material.mNormalTexture.mScale; - mat.hasNormalTex = gltf_material.mNormalTexture.mIndex >= 0; - mat.normalTexIdx = gltf_material.mNormalTexture.mIndex; - mat.normalTexCoords = gltf_material.mNormalTexture.mTexCoord; - - // Occlusion texture - mat.occlusionScale = gltf_material.mOcclusionTexture.mStrength; - mat.hasOcclusionTex = gltf_material.mOcclusionTexture.mIndex >= 0; - mat.occlusionTexIdx = gltf_material.mOcclusionTexture.mIndex; - mat.occlusionTexCoords = gltf_material.mOcclusionTexture.mTexCoord; - - // Emissive texture and color - mat.emissiveColor = LLColor4( - gltf_material.mEmissiveFactor[0], - gltf_material.mEmissiveFactor[1], - gltf_material.mEmissiveFactor[2], - 1.0f - ); - mat.hasEmissiveTex = gltf_material.mEmissiveTexture.mIndex >= 0; - mat.emissiveTexIdx = gltf_material.mEmissiveTexture.mIndex; - mat.emissiveTexCoords = gltf_material.mEmissiveTexture.mTexCoord; - - // Convert AlphaMode enum to string - switch (gltf_material.mAlphaMode) - { - case LL::GLTF::Material::AlphaMode::OPAQUE: - mat.alphaMode = "OPAQUE"; - break; - case LL::GLTF::Material::AlphaMode::MASK: - mat.alphaMode = "MASK"; - break; - case LL::GLTF::Material::AlphaMode::BLEND: - mat.alphaMode = "BLEND"; - break; - default: - mat.alphaMode = "OPAQUE"; - break; - } - - mat.alphaMask = gltf_material.mAlphaCutoff; - - // Verify that all referenced textures are valid - 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; - } - - // Verify texture coordinate sets are valid (mesh can have up to 3 sets of UV) - if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || - (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); + // No override for this joint + rest = parent_rest * viewer_data.mJointMatrix; + } + for (LLJointData& child_data : viewer_data.mChildren) + { + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest); } - - 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() +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const { - LL_INFOS("GLTF_IMPORT") << "Uploading materials, count: " << mMaterials.size() << LL_ENDL; + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? - for (gltf_render_material& mat : mMaterials) + if (joint_node_index < 0 || joint_node_index >= static_cast<S32>(mGLTFAsset.mNodes.size())) { - LL_INFOS("GLTF_IMPORT") << "Processing material: " << mat.name << LL_ENDL; + return glm::mat4(1.0f); + } - // Process base color texture - if (mat.hasBaseTex && mat.baseColorTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; - if (gtex.imageUuid.isNull()) - { - LL_INFOS("GLTF_IMPORT") << "Loading base color texture for material " << mat.name << LL_ENDL; - gtex.imageUuid = imageBufferToTextureUUID(gtex); + const auto& node = mGLTFAsset.mNodes[joint_node_index]; - if (gtex.imageUuid.notNull()) - { - LL_INFOS("GLTF_IMPORT") << "Base color texture loaded, ID: " << gtex.imageUuid.asString() << LL_ENDL; - } - else - { - LL_WARNS("GLTF_IMPORT") << "Failed to load base color texture for material " << mat.name << LL_ENDL; - } - } - } - - // Process other textures similarly - if (mat.hasMRTex && mat.metalRoughTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } + // 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 (mat.hasNormalTex && mat.normalTexIdx < mTextures.size()) + if (it != potential_parent.mChildren.end()) { - gltf_texture& gtex = mTextures[mat.normalTexIdx]; - if (gtex.imageUuid.isNull()) + // Found parent + if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end()) { - gtex.imageUuid = imageBufferToTextureUUID(gtex); + // 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; +} - if (mat.hasOcclusionTex && mat.occlusionTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } +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 (mat.hasEmissiveTex && mat.emissiveTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } + if (joint_node_index < 0 || joint_node_index >= static_cast<S32>(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); } - // Update material map for all model instances to ensure textures are properly associated - // mScene is a std::map<LLMatrix4, model_instance_list>, not an array, so we need to iterate through it correctly - for (auto& scene_entry : mScene) - { - for (LLModelInstance& instance : scene_entry.second) - { - LLModel* model = instance.mModel; + auto& data = joint_data.at(joint_node_index); - if (model) - { - for (size_t i = 0; i < model->getMaterialList().size(); ++i) - { - const std::string& matName = model->getMaterialList()[i]; - if (!matName.empty()) - { - // Ensure this material exists in the instance's material map - if (instance.mMaterial.find(matName) == instance.mMaterial.end()) - { - // Find material in our render materials - for (const auto& renderMat : mMaterials) - { - if (renderMat.name == matName) - { - // Create an import material from the render material - LLImportMaterial impMat; - impMat.mDiffuseColor = renderMat.baseColor; - - // Set diffuse texture if available - if (renderMat.hasBaseTex && renderMat.baseColorTexIdx < mTextures.size()) - { - const gltf_texture& gtex = mTextures[renderMat.baseColorTexIdx]; - if (!gtex.imageUuid.isNull()) - { - impMat.setDiffuseMap(gtex.imageUuid); - LL_INFOS("GLTF_IMPORT") << "Setting texture " << gtex.imageUuid.asString() << " for material " << matName << LL_ENDL; - } - } - - // Add material to instance's material map - instance.mMaterial[matName] = impMat; - LL_INFOS("GLTF_IMPORT") << "Added material " << matName << " to instance" << LL_ENDL; - break; - } - } - } - } - } - } - } + if (data.mParentNodeIdx >=0) + { + return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix; } + // Should we return armature or stop earlier? + return data.mGltfMatrix; } -LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) +// 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 { - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) + const JointNodeData& node_data = joints_data_map.at(gltf_node_index); + if (!node_data.mIsOverrideValid) { - LL_WARNS("GLTF_IMPORT") << "Invalid texture indices in imageBufferToTextureUUID" << LL_ENDL; - return LLUUID::null; + // For now assume they are identical and return an identity (for ease of debuging) + return glm::mat4(1.0f); } - gltf_image& image = mImages[tex.imageIdx]; - gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + // 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; - S32 sourceIndex = tex.imageIdx; - if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) - { - LL_WARNS("GLTF_IMPORT") << "Invalid image index: " << sourceIndex << LL_ENDL; - return LLUUID::null; - } + LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; - LL::GLTF::Image& source_image = mGLTFAsset.mImages[sourceIndex]; + LLMatrix4 transform(glm::value_ptr(rest_pose)); - // If the image already has a texture loaded, use it - if (source_image.mTexture.notNull()) - { - LL_INFOS("GLTF_IMPORT") << "Using already loaded texture ID: " << source_image.mTexture->getID().asString() << LL_ENDL; - return source_image.mTexture->getID(); - } + 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); +} - // Create an import material to pass to the texture load function - LLImportMaterial material; +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"; - // Try to get the texture filename from the URI - if (!source_image.mUri.empty()) + S32 size = (S32)gltf_skin.mJoints.size(); + S32 joints_found = 0; + for (S32 i= 0; i < size; i++) { - std::string filename = source_image.mUri; + S32 joint = gltf_skin.mJoints[i]; + auto joint_node = mGLTFAsset.mNodes[joint]; - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) + // 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()) { - filename = filename.substr(pos + 1); + // unsupported joint + continue; + } + if (found->second == right_shoulder_str || found->second == left_shoulder_str) + { + if (checkForXYrotation(gltf_skin, joint, i)) + { + joints_found++; + } + else + { + return; + } } - - material.mDiffuseMapFilename = filename; - material.mDiffuseMapLabel = filename; - } - else if (source_image.mBufferView >= 0) - { - // For embedded textures, create a pseudo-filename - std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; - material.mDiffuseMapFilename = pseudo_filename; - material.mDiffuseMapLabel = pseudo_filename; } - else + + if (joints_found == 2) { - LL_WARNS("GLTF_IMPORT") << "No URI or buffer data for image" << LL_ENDL; - return LLUUID::null; + // Both joints in a weird position/rotation, assume rotated model + mApplyXYRotation = true; } +} - // Create LLSD container with image and sampler data for texture upload - LLSD texture_data = LLSD::emptyMap(); +std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) +{ + if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) + return ""; - // Image data - texture_data["width"] = LLSD::Integer(image.width); - texture_data["height"] = LLSD::Integer(image.height); - texture_data["components"] = LLSD::Integer(image.numChannels); - texture_data["bytes_per_component"] = LLSD::Integer(image.bytesPerChannel); - texture_data["pixel_type"] = LLSD::Integer(image.pixelType); + S32 sourceIndex = mGLTFAsset.mTextures[textureIndex].mSource; + if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) + return ""; - // Sampler data - texture_data["min_filter"] = LLSD::Integer(sampler.minFilter); - texture_data["mag_filter"] = LLSD::Integer(sampler.magFilter); - texture_data["wrap_s"] = LLSD::Integer(sampler.wrapS); - texture_data["wrap_t"] = LLSD::Integer(sampler.wrapT); + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - // Add URI for reference - if (!source_image.mUri.empty()) + // Handle URI-based textures + if (!image.mUri.empty()) { - texture_data["uri"] = source_image.mUri; + return image.mUri; // Return URI directly } - // Check if we have a buffer view for embedded data - if (source_image.mBufferView >= 0) + // Handle embedded textures + if (image.mBufferView >= 0) { - texture_data["has_embedded_data"] = LLSD::Boolean(true); - texture_data["buffer_view"] = LLSD::Integer(source_image.mBufferView); - - // Extract embedded data for texture loading - if (source_image.mBufferView < mGLTFAsset.mBufferViews.size()) + if (image.mBufferView < mGLTFAsset.mBufferViews.size()) { - const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[source_image.mBufferView]; + 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()) { - // Add embedded data reference to texture_data - texture_data["buffer_index"] = LLSD::Integer(buffer_view.mBuffer); - texture_data["byte_offset"] = LLSD::Integer(buffer_view.mByteOffset); - texture_data["byte_length"] = LLSD::Integer(buffer_view.mByteLength); + // Extract image data + const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; + U32 data_size = buffer_view.mByteLength; - LL_INFOS("GLTF_IMPORT") << "Found embedded texture data: offset=" << buffer_view.mByteOffset - << " length=" << buffer_view.mByteLength << LL_ENDL; - } - } - } - } - - // Store the texture metadata in the binding field - std::ostringstream ostr; - LLSDSerialize::toXML(texture_data, ostr); - material.mBinding = ostr.str(); - - LL_INFOS("GLTF_IMPORT") << "Loading texture: " << material.mDiffuseMapFilename << LL_ENDL; - - // Flag to track if texture was loaded immediately - bool texture_loaded = false; - - // Call texture loading function with our import material - if (mTextureLoadFunc) - { - // Increment textures to fetch counter BEFORE calling load function - mNumOfFetchingTextures++; + // 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 + } - U32 result = mTextureLoadFunc(material, mOpaqueData); + // 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; - // If result is 0, texture is being loaded asynchronously - // If result is >0, texture was loaded immediately - if (result > 0) - { - // Texture was loaded immediately, so decrement counter - mNumOfFetchingTextures--; - texture_loaded = true; + // 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(); - if (material.getDiffuseMap().notNull()) - { - LL_INFOS("GLTF_IMPORT") << "Texture loaded successfully, ID: " << material.getDiffuseMap().asString() << LL_ENDL; + 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; - // Store the texture in the source image for future reference - if (source_image.mTexture.isNull()) - { - // Create and store a texture object using the UUID - source_image.mTexture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap()); + LLSD args; + args["Message"] = "FailedToCreateTempFile"; + args["TEXTURE_INDEX"] = sourceIndex; + args["TEXTURE_TYPE"] = texture_type; + args["TEMP_FILE"] = temp_filename; + mWarningsArray.append(args); + } } - - return material.getDiffuseMap(); } } - else if (result == 0) - { - LL_INFOS("GLTF_IMPORT") << "Texture loading queued asynchronously for " << material.mDiffuseMapFilename << LL_ENDL; - } - else // result < 0, indicating error - { - // Texture loading failed, decrement counter - mNumOfFetchingTextures--; - LL_WARNS("GLTF_IMPORT") << "Texture loading failed for " << material.mDiffuseMapFilename << LL_ENDL; - } - } - else - { - LL_WARNS("GLTF_IMPORT") << "No texture loading function available" << LL_ENDL; } - return LLUUID::null; + return ""; } void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 6e0fe2b32c..19a029d6d4 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -32,95 +32,44 @@ #include "asset.h" #include "llglheaders.h" +#include "lljointdata.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; + 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, @@ -133,7 +82,8 @@ class LLGLTFLoader : public LLModelLoader JointNameSet & jointsFromNodes, std::map<std::string, std::string> &jointAliasMap, U32 maxJointsPerMesh, - U32 modelLimit); //, + U32 modelLimit, + std::vector<LLJointData> viewer_skeleton); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -152,27 +102,41 @@ protected: LL::GLTF::Asset mGLTFAsset; tinygltf::Model mGltfModel; bool mGltfLoaded; - bool mMeshesLoaded; - bool mMaterialsLoaded; + 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; - std::vector<gltf_mesh> mMeshes; - std::vector<gltf_render_material> mMaterials; + // vector of vectors because of a posibility of having more than one skin + typedef std::vector<LLMeshSkinInfo::matrix_list_t> bind_matrices_t; + bind_matrices_t mInverseBindMatrices; + bind_matrices_t mAlternateBindMatrices; - std::vector<gltf_texture> mTextures; - std::vector<gltf_image> mImages; - std::vector<gltf_sampler> mSamplers; + // per skin joint count, needs to be tracked for the sake of limits check. + std::vector<S32> mValidJointsCount; private: bool parseMeshes(); - void uploadMeshes(); - bool parseMaterials(); - void uploadMaterials(); 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 populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); - void populateJointFromSkin(const LL::GLTF::Skin& skin); - S32 findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; - S32 findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint - LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + void populateJointFromSkin(S32 skin_idx); + 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 buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_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); diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index f962217480..84b9cb18f8 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -114,11 +114,6 @@ void LLMeshFilePicker::notify(const std::vector<std::string>& filenames) if (filenames.size() > 0) { mMP->loadModel(filenames[0], mLOD); - - if (filenames[0].substr(filenames[0].length() - 4) == ".glb" || filenames[0].substr(filenames[0].length() - 5) == ".gltf") - { - LLMaterialEditor::loadMaterialFromFile(filenames[0], -1); - } } else { diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index c7cb2b68e0..d8373ed0cb 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -40,6 +40,7 @@ #include "lldrawable.h" #include "llface.h" #include "lliconctrl.h" +#include "lljointdata.h" #include "llmatrix4a.h" #include "llmeshrepository.h" #include "llmeshoptimizer.h" @@ -810,6 +811,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable } else { + LLVOAvatar* av = getPreviewAvatar(); + std::vector<LLJointData> viewer_skeleton; + av->getJointMatricesAndHierarhy(viewer_skeleton); mModelLoader = new LLGLTFLoader( filename, lod, @@ -822,7 +826,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mJointsFromNode, joint_alias_map, LLSkinningUtil::getMaxJointCount(), - gSavedSettings.getU32("ImporterModelLimit")); + gSavedSettings.getU32("ImporterModelLimit"), + viewer_skeleton); } if (force_disable_slm) diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index 43836a420b..47f58afa00 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -360,7 +360,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a { rig_info_tab[joint_num].setIsRiggedTo(true); - const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index]; + size_t bind_poses_size = skin->mBindPoseMatrix.size(); + const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity(); LLVector4a pos_joint_space; mat.affineTransform(pos, pos_joint_space); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 6231abf9a5..80b6427738 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -61,9 +61,19 @@ <string name="ParsingErrorNoRoot">Document has no root</string> <string name="ParsingErrorNoScene">Document has no visual_scene</string> <string name="ParsingErrorPositionInvalidModel">Unable to process mesh without position data. Invalid model.</string> - <string name="InvalidGeometryNonTriangulated">Invalid geometry: GLTF files must contain triangulated meshes only.</string> - <string name="IgnoredExtension">Model uses unsupported extension: [EXT], related material properties are ignored.</string> - <string name="UnsupportedExtension">Unable to load a model, unsupported extension: [EXT]</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="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> <panel follows="top|left" |