From 4ae9b7b24564a5d50360781d2b63f73a6aa2141c Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 2 Jun 2025 19:43:48 +0300 Subject: #4203 show valid log info about 16 bit limit --- indra/newview/gltf/llgltfloader.cpp | 2 +- indra/newview/skins/default/xui/en/floater_model_preview.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5cdd7f09e0..800b986b59 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -648,7 +648,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else { LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; LLSD args; - args["Message"] = "ParsingErrorBadElement"; + args["Message"] = "ErrorIndexLimit"; mWarningsArray.append(args); return false; } 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..1c4a857dab 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -64,6 +64,7 @@ Invalid geometry: GLTF files must contain triangulated meshes only. Model uses unsupported extension: [EXT], related material properties are ignored. Unable to load a model, unsupported extension: [EXT] + Unable to process mesh due to 16-bit index limits Date: Tue, 3 Jun 2025 13:25:02 +0300 Subject: #4097 Fix crash in LLGLTFLoader::populateModelFromMesh() --- indra/newview/gltf/llgltfloader.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 800b986b59..4d705279a5 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -450,9 +450,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& GLTFVertex vert; vert.position = glm::vec3(transformed_pos); - // 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); + if (!prim.mNormals.empty()) + { + // Use pre-computed normal_transform + glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.normal = glm::normalize(normal_transform * normal_vec); + } + else + { + // Use default normal (pointing up in model space) + vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); + LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; + } vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); -- cgit v1.2.3 From acd4de66589e960da24cd39fe44083f80a293b05 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 3 Jun 2025 20:27:25 +0300 Subject: #4080 Rigged mesh support #7 --- indra/newview/gltf/llgltfloader.cpp | 210 ++++++++++++++++++++++++------------ indra/newview/gltf/llgltfloader.h | 14 ++- 2 files changed, 151 insertions(+), 73 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 4d705279a5..c99d7c2993 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -153,9 +153,9 @@ bool LLGLTFLoader::parseMeshes() // 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 @@ -293,6 +293,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); @@ -672,16 +674,8 @@ 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); - } - std::vector gltfindex_to_joitindex_map; + size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; @@ -711,44 +705,24 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); - if (i < gltf_skin.mInverseBindMatricesData.size()) - { - // Use pre-computed coord_system_rotation instead of recreating it - LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - - 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); - - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); - skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); - - 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)); - - 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 alternate bind matrix, use the ORIGINAL joint transform (before rotation) + // Get the original joint node and use its matrix directly + // Todo: this seems blatantly wrong, it should have been rotated + glm::mat4 joint_mat = jointNode.mMatrix; + S32 root_joint = findValidRootJointNode(joint, gltf_skin); // skeleton can have multiple real roots + if (root_joint == joint) { - // 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])); + // 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)); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } // Remap indices for pModel->mSkinWeights @@ -764,23 +738,71 @@ 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]; - for (auto joint : skin.mJoints) + 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()) { - auto jointNode = mGLTFAsset.mNodes[joint]; + 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); + } + + 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; + } + + if (!legal_joint) + { + // add placeholder to not break index + LLMatrix4 gltf_transform; + gltf_transform.setIdentity(); + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + else if (inverse_count > i) + { + LL::GLTF::mat4 gltf_mat = skin.mInverseBindMatricesData[i]; + + // Todo: there should be a simpler way + 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); + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); + + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " 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; + LLMatrix4 gltf_transform; + gltf_transform.setIdentity(); + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + // todo: prepare mAlternateBindMatrix here + + if (!legal_joint) + { + LL_DEBUGS("GLTF") << "Ignoring unrecognized joint: " << legal_name << LL_ENDL; continue; } @@ -812,14 +834,45 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) } } -S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const + +S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const { - S32 root_joint = 0; - S32 found_joint = source_joint; + 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_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::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); + if (it != jointNode.mChildren.end()) + { + // Found node's parent + found_node = joint; + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + return i; + } + break; + } + } + } while (root_node != found_node); + + return -1; +} + +S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_node = 0; + S32 found_node = source_joint_node; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_node = found_node; for (S32 i = 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; @@ -827,44 +880,61 @@ S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& glt if (mJointMap.find(jointNode.mName) != mJointMap.end()) { - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + std::vector::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::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + std::vector::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; +} + +S32 LLGLTFLoader::findParentNode(S32 node) const +{ + S32 size = (S32)mGLTFAsset.mNodes.size(); + for (S32 i = 0; i < size; i++) + { + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node); + if (it != jointNode.mChildren.end()) + { + return i; + } + } + return -1; } bool LLGLTFLoader::parseMaterials() diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 6e0fe2b32c..a9572a5bfc 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -121,6 +121,7 @@ class LLGLTFLoader : public LLModelLoader { public: typedef std::map material_map; + typedef std::map joint_pos_map_t; LLGLTFLoader(std::string filename, S32 lod, @@ -162,6 +163,11 @@ protected: std::vector mImages; std::vector mSamplers; + + // vector of vectors because of a posibility of having more than one skin + typedef std::vector bind_matrices_t; + bind_matrices_t mInverseBindMatrices; + private: bool parseMeshes(); void uploadMeshes(); @@ -169,9 +175,11 @@ private: void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; 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 + void populateJointFromSkin(S32 skin_idx); + 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; LLUUID imageBufferToTextureUUID(const gltf_texture& tex); void notifyUnsupportedExtension(bool unsupported); -- cgit v1.2.3 From 11ece6840b034973a33626e98bd2412f0577ac5a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 18:00:14 +0300 Subject: #4214 Support mesh splitting for meshes with more than 8 materials --- indra/newview/gltf/llgltfloader.cpp | 107 +++++++++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 6 ++ 2 files changed, 105 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c99d7c2993..e330c1d611 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -102,7 +102,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, maxJointsPerMesh ), //mPreprocessGLTF(preprocess), mMeshesLoaded(false), - mMaterialsLoaded(false) + mMaterialsLoaded(false), + mGeneratedModelLimit(modelLimit) { } @@ -135,6 +136,99 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return (mMeshesLoaded); } +void LLGLTFLoader::addModelToScene( + LLModel* pModel, + U32 submodel_limit, + const LLMatrix4& transformation, + const LLVolumeParams& volume_params) +{ + 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 ready_models; + LLModel* current_model = pModel; + do + { + current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); + + // remove unused/redundant vertices after normalizing + current_model->remapVolumeFaces(); + + volume_faces = static_cast(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 + current_model->remapVolumeFaces(); + // Todo: go over skin weights, joints, matrices and remove unused ones + + mModelList.push_back(model); + + std::map materials; + for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) + { + materials[model->mMaterialList[i]] = LLImportMaterial(); + } + mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); + stretch_extents(model, transformation); + } +} + bool LLGLTFLoader::parseMeshes() { if (!mGltfLoaded) return false; @@ -160,6 +254,7 @@ bool LLGLTFLoader::parseMeshes() // Track how many times each mesh name has been used std::map mesh_name_counts; + U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; // Process each node for (auto& node : mGLTFAsset.mNodes) @@ -188,8 +283,6 @@ bool LLGLTFLoader::parseMeshes() (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { - mModelList.push_back(pModel); - mTransform.setIdentity(); transformation = mTransform; @@ -234,8 +327,7 @@ bool LLGLTFLoader::parseMeshes() mWarningsArray.append(args); } - mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); - stretch_extents(pModel, transformation); + addModelToScene(pModel, submodel_limit, transformation, volume_params); } else { @@ -347,8 +439,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - auto prims = mesh.mPrimitives; - for (auto prim : prims) + for (const LL::GLTF::Primitive& prim : mesh.mPrimitives) { // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. if (prim.getVertexCount() < USHRT_MAX) @@ -471,7 +562,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { vert.weights = glm::vec4(prim.mWeights[i]); - auto accessorIdx = prim.mAttributes["JOINTS_0"]; + auto accessorIdx = prim.mAttributes.at("JOINTS_0"); LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; if (accessorIdx >= 0) { diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index a9572a5bfc..a3ee8d91df 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -155,6 +155,7 @@ protected: bool mGltfLoaded; bool mMeshesLoaded; bool mMaterialsLoaded; + U32 mGeneratedModelLimit; std::vector mMeshes; std::vector mMaterials; @@ -176,6 +177,11 @@ private: void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointFromSkin(S32 skin_idx); + void addModelToScene( + LLModel* pModel, + U32 submodel_limit, + const LLMatrix4& transformation, + const LLVolumeParams& volume_params); 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 -- cgit v1.2.3 From 08f6f5c697fce4ccbfba357ab9ce5af915dd0574 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 20:57:10 +0300 Subject: #4214 Weights and Joints remap --- indra/llprimitive/llmodel.cpp | 87 +++++++++++++++++++++++++++++++++++++ indra/llprimitive/llmodel.h | 1 + indra/newview/gltf/llgltfloader.cpp | 6 +-- 3 files changed, 90 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index c67dac2733..bc9f62b6c0 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -112,6 +112,93 @@ void LLModel::remapVolumeFaces() } } +void LLModel::remapSkinWeightsAndJoints() +{ + if (mSkinWeights.empty()) + { + return; + } + + mPosition.clear(); + + // Make a list of positions + std::set positions; + for (S32 i = 0; i < getNumVolumeFaces(); ++i) + { + for (S32 j = 0; j < mVolumeFaces[i].mNumVertices; ++j) + { + positions.emplace(mVolumeFaces[i].mPositions[j].getF32ptr()); + } + } + + // Build new list of weights and record used joints + weight_map replacement_weights; + std::vector joint_index_use_count; + size_t joint_count = mSkinInfo.mJointNames.size(); + joint_index_use_count.resize(joint_count, 0); + for (const LLVector3& pos : positions) + { + mPosition.push_back(pos); + auto found = mSkinWeights.find(pos); + if (found != mSkinWeights.end()) + { + replacement_weights[pos] = found->second; + + for (auto& weight : found->second) + { + if (joint_count > weight.mJointIdx) + { + joint_index_use_count[weight.mJointIdx]++; + } + } + } + } + + // go over joint data and remap joints + // prepare joint map + std::vector replacement_joint_names; + std::vector replacement_joint_nums; + LLMeshSkinInfo::matrix_list_t replacement_inv_bind; + LLMeshSkinInfo::matrix_list_t replacement_alt_bind; + std::vector index_map; + index_map.resize(joint_count); + S32 replacement_index = 0; + + for (S32 i = 0; i < joint_count; i++) + { + if (joint_index_use_count[i] > 0) + { + replacement_joint_names.push_back(mSkinInfo.mJointNames[i]); + replacement_joint_nums.push_back(mSkinInfo.mJointNums[i]); + replacement_inv_bind.push_back(mSkinInfo.mInvBindMatrix[i]); + replacement_alt_bind.push_back(mSkinInfo.mAlternateBindMatrix[i]); + index_map[i] = replacement_index++; + } + } + + // Apply new data + mSkinInfo.mJointNames.clear(); + mSkinInfo.mJointNames = replacement_joint_names; + mSkinInfo.mJointNums.clear(); + mSkinInfo.mJointNums = replacement_joint_nums; + mSkinInfo.mInvBindMatrix.clear(); + mSkinInfo.mInvBindMatrix = replacement_inv_bind; + mSkinInfo.mAlternateBindMatrix.clear(); + mSkinInfo.mAlternateBindMatrix = replacement_alt_bind; + + // remap weights + for (auto& weights : replacement_weights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = index_map[weight.mJointIdx]; + } + } + + mSkinWeights.clear(); + mSkinWeights = replacement_weights; +} + void LLModel::optimizeVolumeFaces() { for (S32 i = 0; i < getNumVolumeFaces(); ++i) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 5c6d0a55d2..7fa3a00ee2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -205,6 +205,7 @@ public: void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); + void remapSkinWeightsAndJoints(); void optimizeVolumeFaces(); void offsetMesh( const LLVector3& pivotPoint ); void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index e330c1d611..60c6832058 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -165,9 +165,6 @@ void LLGLTFLoader::addModelToScene( { current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); - // remove unused/redundant vertices after normalizing - current_model->remapVolumeFaces(); - volume_faces = static_cast(remainder.size()); // Don't add to scene yet because weights and materials aren't ready. @@ -215,7 +212,8 @@ void LLGLTFLoader::addModelToScene( { // remove unused/redundant vertices current_model->remapVolumeFaces(); - // Todo: go over skin weights, joints, matrices and remove unused ones + // remove unused/redundant weights and joints + current_model->remapSkinWeightsAndJoints(); mModelList.push_back(model); -- cgit v1.2.3 From 74d990872c8d8f59083b868a7ae8df5e90df62c2 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 5 Jun 2025 21:07:19 +0300 Subject: #4214 Fix material upload --- indra/newview/gltf/llgltfloader.cpp | 20 +++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 3 ++- 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 60c6832058..1d7b44e566 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -140,7 +140,8 @@ void LLGLTFLoader::addModelToScene( LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, - const LLVolumeParams& volume_params) + const LLVolumeParams& volume_params, + const material_map& mats) { U32 volume_faces = pModel->getNumVolumeFaces(); @@ -220,7 +221,15 @@ void LLGLTFLoader::addModelToScene( std::map materials; for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) { - materials[model->mMaterialList[i]] = LLImportMaterial(); + 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); @@ -325,7 +334,8 @@ bool LLGLTFLoader::parseMeshes() mWarningsArray.append(args); } - addModelToScene(pModel, submodel_limit, transformation, volume_params); + addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + mats.clear(); } else { @@ -815,6 +825,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } // Remap indices for pModel->mSkinWeights + // Todo: this is now partially redundant due to + // remapSkinWeightsAndJoints being called later. + // Consider storing all joints now as is and let it + // ramap later due to missing weights. for (auto& weights : pModel->mSkinWeights) { for (auto& weight : weights.second) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index a3ee8d91df..5b9eb78c63 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -181,7 +181,8 @@ private: LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, - const LLVolumeParams& volume_params); + 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 -- cgit v1.2.3 From 35398524e560f2c89e59672cefd5c9ff60c9aa37 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 15:32:42 +0300 Subject: Fix split model vertex/joint remapping to use correct model variable --- indra/newview/gltf/llgltfloader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1d7b44e566..528b932dd3 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -212,9 +212,9 @@ void LLGLTFLoader::addModelToScene( for (auto model : ready_models) { // remove unused/redundant vertices - current_model->remapVolumeFaces(); + model->remapVolumeFaces(); // remove unused/redundant weights and joints - current_model->remapSkinWeightsAndJoints(); + model->remapSkinWeightsAndJoints(); mModelList.push_back(model); -- cgit v1.2.3 From 689b829b67204c3097d1ac6abf69916c45526b43 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 16:10:55 +0300 Subject: #4170 Fix GLTF import missing mesh parts from transform tools Process entire node hierarchy instead of only nodes with meshes. GLTF transform tools often create intermediate transform nodes without meshes that were being skipped, causing child meshes to be omitted from import. --- indra/newview/gltf/llgltfloader.cpp | 186 +++++++++++++++++++++++------------- indra/newview/gltf/llgltfloader.h | 8 +- 2 files changed, 120 insertions(+), 74 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 528b932dd3..edcc716298 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -263,91 +263,141 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Process each node - for (auto& node : mGLTFAsset.mNodes) - { - LLMatrix4 transformation; - material_map mats; - auto meshidx = node.mMesh; + // Mark which nodes have been processed to avoid duplicates + std::vector node_processed(mGLTFAsset.mNodes.size(), false); - if (meshidx >= 0) + // First, find root nodes (nodes without parents) and process their hierarchies + for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + { + if (!node_processed[node_idx]) { - if (mGLTFAsset.mMeshes.size() > meshidx) + // Check if this node has a parent + bool has_parent = false; + for (const auto& potential_parent : mGLTFAsset.mNodes) { - LLModel* pModel = new LLModel(volume_params, 0.f); - auto mesh = mGLTFAsset.mMeshes[meshidx]; - - // Get base mesh name and track usage - std::string base_name = mesh.mName; - if (base_name.empty()) + if (std::find(potential_parent.mChildren.begin(), + potential_parent.mChildren.end(), + static_cast(node_idx)) != potential_parent.mChildren.end()) { - base_name = "mesh_" + std::to_string(meshidx); + has_parent = true; + break; } + } - S32 instance_count = mesh_name_counts[base_name]++; + // If no parent, this is a root node - process its hierarchy + if (!has_parent) + { + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + } + } + } - if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && - (LLModel::NO_ERRORS == pModel->getStatus()) && - validate_model(pModel)) - { - mTransform.setIdentity(); - transformation = mTransform; + // Process any remaining unprocessed nodes (disconnected nodes) + for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + { + if (!node_processed[node_idx]) + { + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + } + } - // 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& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed) +{ + if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) + return; - LLMatrix4 mesh_scale; - mesh_scale.initScale(mesh_scale_vector); - mesh_scale *= transformation; - transformation = mesh_scale; + if (node_processed[node_idx]) + return; - 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; - } + node_processed[node_idx] = true; - 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); - } + auto& node = mGLTFAsset.mNodes[node_idx]; - addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); - mats.clear(); - } - else - { - setLoadState(ERROR_MODEL + pModel->getStatus()); - delete pModel; - return false; - } + // Process this node's mesh if it has one + if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) + { + LLMatrix4 transformation; + material_map mats; + + LLModel* pModel = new LLModel(volume_params, 0.f); + auto& mesh = mGLTFAsset.mMeshes[node.mMesh]; + + // Get base mesh name and track usage + std::string base_name = mesh.mName; + if (base_name.empty()) + { + base_name = "mesh_" + std::to_string(node.mMesh); + } + + S32 instance_count = mesh_name_counts[base_name]++; + + if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && + (LLModel::NO_ERRORS == pModel->getStatus()) && + validate_model(pModel)) + { + mTransform.setIdentity(); + transformation = mTransform; + + // adjust the transformation to compensate for mesh normalization + LLVector3 mesh_scale_vector; + LLVector3 mesh_translation_vector; + pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transformation; + transformation = mesh_translation; + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transformation; + transformation = mesh_scale; + + if (node.mSkin >= 0) + { + // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh + // into the coordinate space of the joints. + // In GLTF, this matrix is omitted, and it is assumed that this transform is either + // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. + // + // TODO: There appears to be missing rotation when joints rotate the model + // or inverted bind matrices are missing inherited rotation + // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly + // prior to skinning) + + pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); + LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; } + + if (transformation.determinant() < 0) + { // negative scales are not supported + LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + << pModel->mLabel << LL_ENDL; + LLSD args; + args["Message"] = "NegativeScaleNormTrans"; + args["LABEL"] = pModel->mLabel; + mWarningsArray.append(args); + } + + addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + mats.clear(); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete pModel; + return; } } - return true; + // Process all children + for (S32 child_idx : node.mChildren) + { + processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params, node_processed); + } } void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 5b9eb78c63..4802c8e093 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -175,14 +175,10 @@ private: 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& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointFromSkin(S32 skin_idx); - void addModelToScene( - LLModel* pModel, - U32 submodel_limit, - const LLMatrix4& transformation, - const LLVolumeParams& volume_params, - const material_map& mats); + 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 -- cgit v1.2.3 From 6924862d2ef1ef6af6a04c013aebeecbb5717bde Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 17:22:02 +0300 Subject: #4170 Follow-up: Optimize GLTF node hierarchy traversal --- indra/newview/gltf/llgltfloader.cpp | 48 +++++++++++++------------------------ indra/newview/gltf/llgltfloader.h | 2 +- 2 files changed, 17 insertions(+), 33 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index edcc716298..17de6eb829 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -263,57 +263,41 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Mark which nodes have been processed to avoid duplicates - std::vector node_processed(mGLTFAsset.mNodes.size(), false); + // Build parent mapping for efficient traversal + std::vector node_parents(mGLTFAsset.mNodes.size(), -1); + std::vector is_root(mGLTFAsset.mNodes.size(), true); - // First, find root nodes (nodes without parents) and process their hierarchies - for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + // Build parent relationships + for (size_t parent_idx = 0; parent_idx < mGLTFAsset.mNodes.size(); parent_idx++) { - if (!node_processed[node_idx]) + const auto& parent_node = mGLTFAsset.mNodes[parent_idx]; + for (S32 child_idx : parent_node.mChildren) { - // Check if this node has a parent - bool has_parent = false; - for (const auto& potential_parent : mGLTFAsset.mNodes) - { - if (std::find(potential_parent.mChildren.begin(), - potential_parent.mChildren.end(), - static_cast(node_idx)) != potential_parent.mChildren.end()) - { - has_parent = true; - break; - } - } - - // If no parent, this is a root node - process its hierarchy - if (!has_parent) + if (child_idx >= 0 && child_idx < static_cast(mGLTFAsset.mNodes.size())) { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + node_parents[child_idx] = static_cast(parent_idx); + is_root[child_idx] = false; } } } - // Process any remaining unprocessed nodes (disconnected nodes) + // Process all root nodes and their hierarchies for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) { - if (!node_processed[node_idx]) + if (is_root[node_idx]) { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params); } } return true; } -void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed) +void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params) { if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) return; - if (node_processed[node_idx]) - return; - - node_processed[node_idx] = true; - auto& node = mGLTFAsset.mNodes[node_idx]; // Process this node's mesh if it has one @@ -393,10 +377,10 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map } } - // Process all children + // Process all children recursively for (S32 child_idx : node.mChildren) { - processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params, node_processed); + processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params); } } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 4802c8e093..19337c24aa 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -175,7 +175,7 @@ private: 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& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed); + void processNodeHierarchy(S32 node_idx, std::map& 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(S32 skin_idx); void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); -- cgit v1.2.3 From b20d10c0cc96cfcd93468b8e31e47ab1977a9555 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 16:39:51 +0300 Subject: #4148 Skeleton Translation --- indra/llappearance/llavatarappearance.cpp | 47 ++++++++++++++++- indra/llappearance/llavatarappearance.h | 4 +- indra/newview/gltf/llgltfloader.cpp | 87 +++++++++++++++++++++++++++---- indra/newview/gltf/llgltfloader.h | 11 +++- indra/newview/llmodelpreview.cpp | 5 +- 5 files changed, 139 insertions(+), 15 deletions(-) (limited to 'indra') diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 3d66809ed6..34d6b4db83 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -34,11 +34,11 @@ #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 +71,7 @@ public: mChildren.clear(); } bool parseXml(LLXmlTreeNode* node); + glm::mat4 getJointMatrix(); private: std::string mName; @@ -105,11 +106,14 @@ public: S32 getNumBones() const { return mNumBones; } S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; } +private: + typedef std::vector bone_info_list_t; + static void getJointRestMatrices(const bone_info_list_t& bone_list, LLAvatarAppearance::joint_rest_map_t& result, const glm::mat4 &parent_mat); + private: S32 mNumBones; S32 mNumCollisionVolumes; LLAvatarAppearance::joint_alias_map_t mJointAliasMap; - typedef std::vector bone_info_list_t; bone_info_list_t mBoneInfoList; }; @@ -1623,6 +1627,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 +1672,22 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node) return true; } +void LLAvatarSkeletonInfo::getJointRestMatrices( + const bone_info_list_t& bone_list, + LLAvatarAppearance::joint_rest_map_t& result, + const glm::mat4& parent_mat) +{ + for (LLAvatarBoneInfo* bone_info : bone_list) + { + if (bone_info->mIsJoint) + { + glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); + result[bone_info->mName] = rest_mat; + getJointRestMatrices(bone_info->mChildren, result, rest_mat); + } + } +} + //Make aliases for joint and push to map. void LLAvatarAppearance::makeJointAliases(LLAvatarBoneInfo *bone_info) { @@ -1714,6 +1749,14 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases return mJointAliasMap; } +LLAvatarAppearance::joint_rest_map_t LLAvatarAppearance:: getJointRestMatrices() const +{ + LLAvatarAppearance::joint_rest_map_t result; + glm::mat4 identity(1.f); + LLAvatarSkeletonInfo::getJointRestMatrices(sAvatarSkeletonInfo->mBoneInfoList, result, identity); + return result; +} + //----------------------------------------------------------------------------- // parseXmlSkeletonNode(): parses nodes from XML tree diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 13e504e639..cea2a837cd 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; @@ -153,7 +154,8 @@ public: const avatar_joint_list_t& getSkeleton() { return mSkeleton; } typedef std::map joint_alias_map_t; const joint_alias_map_t& getJointAliases(); - + typedef std::map joint_rest_map_t; + joint_rest_map_t getJointRestMatrices() const; protected: static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 17de6eb829..7cf26941ad 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -87,7 +87,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, JointNameSet & jointsFromNodes, std::map &jointAliasMap, U32 maxJointsPerMesh, - U32 modelLimit) //, + U32 modelLimit, + joint_rest_map_t jointRestMatrices) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -103,7 +104,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, //mPreprocessGLTF(preprocess), mMeshesLoaded(false), mMaterialsLoaded(false), - mGeneratedModelLimit(modelLimit) + mGeneratedModelLimit(modelLimit), + mJointRestMatrices(jointRestMatrices) { } @@ -917,20 +919,24 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } else if (inverse_count > i) { - LL::GLTF::mat4 gltf_mat = skin.mInverseBindMatricesData[i]; - - // Todo: there should be a simpler way - glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); + glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, 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 << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { - LLMatrix4 gltf_transform; + glm::mat4 inv_bind(1.0f); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + inv_bind = glm::inverse(skeleton_transform * inv_bind); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); gltf_transform.setIdentity(); LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); @@ -1074,6 +1080,69 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const +{ + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? + + if (joint_node_index < 0 || joint_node_index >= static_cast(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + const auto& node = mGLTFAsset.mNodes[joint_node_index]; + + // Find and apply parent transform if it exists + for (size_t i = 0; i < mGLTFAsset.mNodes.size(); ++i) + { + const auto& potential_parent = mGLTFAsset.mNodes[i]; + auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), joint_node_index); + + if (it != potential_parent.mChildren.end()) + { + // Found parent + if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end()) + { + // parent is a joint - recursively combine transform + // assumes that matrix is already valid + return buildGltfRestMatrix(static_cast(i), gltf_skin) * node.mMatrix; + } + } + } + // Should we return armature or stop earlier? + return node.mMatrix; +} + +// 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 LL::GLTF::Skin& gltf_skin, S32 gltf_joint_index, const std::string& joint_name) const +{ + joint_rest_map_t::const_iterator found = mJointRestMatrices.find(joint_name); + if (found == mJointRestMatrices.end()) + { + // For now assume they are identical and return an identity (for ease of debuging) + // But there should be no joints viewer isn't aware about + // Warn or assert about missing joints + return glm::mat4(1.0f); + } + glm::mat4 viewer_joint_rest_pose = found->second; + + // Get the GLTF joint's rest pose (in GLTF coordinate system) + S32 joint_node_index = gltf_skin.mJoints[gltf_joint_index]; + glm::mat4 gltf_joint_rest_pose = buildGltfRestMatrix(joint_node_index, gltf_skin); + gltf_joint_rest_pose = coord_system_rotation * gltf_joint_rest_pose; + + LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; + + LLMatrix4 transform(glm::value_ptr(gltf_joint_rest_pose)); + + LL_CONT << transform << LL_ENDL; + + // Compute transformation from GLTF space to viewer space + // This assumes both skeletons are in rest pose initially + return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 19337c24aa..41e6a2dd6d 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -121,7 +121,7 @@ class LLGLTFLoader : public LLModelLoader { public: typedef std::map material_map; - typedef std::map joint_pos_map_t; + typedef std::map joint_rest_map_t; LLGLTFLoader(std::string filename, S32 lod, @@ -134,7 +134,8 @@ class LLGLTFLoader : public LLModelLoader JointNameSet & jointsFromNodes, std::map &jointAliasMap, U32 maxJointsPerMesh, - U32 modelLimit); //, + U32 modelLimit, + joint_rest_map_t jointRestMatrices); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -164,6 +165,10 @@ protected: std::vector mImages; std::vector mSamplers; + // 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 + joint_rest_map_t mJointRestMatrices; // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; @@ -183,6 +188,8 @@ private: 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; + glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; + glm::mat4 computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 joint_index, const std::string& joint_name) const; LLUUID imageBufferToTextureUUID(const gltf_texture& tex); void notifyUnsupportedExtension(bool unsupported); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 2d568ecb8b..40b6d186b8 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -810,6 +810,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable } else { + LLVOAvatar* av = getPreviewAvatar(); + LLAvatarAppearance::joint_rest_map_t rest_pose = av->getJointRestMatrices(); mModelLoader = new LLGLTFLoader( filename, lod, @@ -822,7 +824,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mJointsFromNode, joint_alias_map, LLSkinningUtil::getMaxJointCount(), - gSavedSettings.getU32("ImporterModelLimit")); + gSavedSettings.getU32("ImporterModelLimit"), + rest_pose); } if (force_disable_slm) -- cgit v1.2.3 From b4fb66c4a2173b2faf717880f0084381f2055cca Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 22:56:43 +0300 Subject: #4170 Use GLTF scene definition for node traversal Process nodes through the scene hierarchy as defined in the GLTF file instead of attempting to reconstruct parent-child relationships. This ensures proper import of models created by GLTF transform tools. --- indra/newview/gltf/llgltfloader.cpp | 47 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7cf26941ad..2dc1aa369b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -265,31 +265,32 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Build parent mapping for efficient traversal - std::vector node_parents(mGLTFAsset.mNodes.size(), -1); - std::vector is_root(mGLTFAsset.mNodes.size(), true); - - // Build parent relationships - for (size_t parent_idx = 0; parent_idx < mGLTFAsset.mNodes.size(); parent_idx++) + // Check if we have scenes defined + if (!mGLTFAsset.mScenes.empty()) { - const auto& parent_node = mGLTFAsset.mNodes[parent_idx]; - for (S32 child_idx : parent_node.mChildren) + // Process the default scene (or first scene if no default) + S32 scene_idx = mGLTFAsset.mScene >= 0 ? mGLTFAsset.mScene : 0; + + if (scene_idx < mGLTFAsset.mScenes.size()) { - if (child_idx >= 0 && child_idx < static_cast(mGLTFAsset.mNodes.size())) + const LL::GLTF::Scene& scene = mGLTFAsset.mScenes[scene_idx]; + + LL_INFOS("GLTF_IMPORT") << "Processing scene " << scene_idx << " with " << scene.mNodes.size() << " root nodes" << LL_ENDL; + + // Process all root nodes defined in the scene + for (S32 root_idx : scene.mNodes) { - node_parents[child_idx] = static_cast(parent_idx); - is_root[child_idx] = false; + if (root_idx >= 0 && root_idx < static_cast(mGLTFAsset.mNodes.size())) + { + processNodeHierarchy(root_idx, mesh_name_counts, submodel_limit, volume_params); + } } } } - - // Process all root nodes and their hierarchies - for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + else { - if (is_root[node_idx]) - { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params); - } + LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + return false; } return true; @@ -302,6 +303,10 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map auto& node = mGLTFAsset.mNodes[node_idx]; + LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" + << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") + << " - children: " << node.mChildren.size() << LL_ENDL; + // Process this node's mesh if it has one if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) { @@ -378,6 +383,12 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map return; } } + else if (node.mMesh >= 0) + { + // Log invalid mesh reference + LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " references invalid mesh " << node.mMesh + << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + } // Process all children recursively for (S32 child_idx : node.mChildren) -- cgit v1.2.3 From 4bbd6319c7db61d12a2a14c5f6f4bc6d6b5e0645 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 10 Jun 2025 14:28:07 +0300 Subject: #4148 Skeleton Translation #2 --- indra/newview/gltf/llgltfloader.cpp | 86 ++++++++++++++++++++++++++++++++++++- indra/newview/gltf/llgltfloader.h | 3 ++ 2 files changed, 88 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2dc1aa369b..8fe5a8f2fb 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -76,6 +76,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, @@ -254,6 +261,11 @@ 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 (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) @@ -461,7 +473,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; @@ -864,6 +880,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // 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; + if (mApplyXYRotation) + { + joint_mat = coord_system_rotationxy * joint_mat; + } } LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); @@ -1151,9 +1171,73 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially + if (mApplyXYRotation) + { + return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); + } return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); } +bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) +{ + glm::mat4 gltf_joint_rest = buildGltfRestMatrix(joint_idx, gltf_skin); + glm::mat4 test_mat = glm::inverse(gltf_joint_rest) * gltf_skin.mInverseBindMatricesData[bind_indx]; + // Normally for shoulders it should be something close to + // {1,0,0,0;0,-1,0,0;0,0,-1,0;0,0,0,1} + // rotated one will look like + // {0,0,0,-1;1,0,0,0;0,-1,0,0;0,0,0,1} + // Todo: This is a cheap hack, + // figure out how rotation is supposed to work + return abs(test_mat[0][0]) < 0.5 && abs(test_mat[1][1]) < 0.5 && abs(test_mat[2][2]) < 0.5; +} + +void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) +{ + // HACK: figure out model's rotation from shoulders' matrix. + // This is wrong on many levels: + // Too limited (only models that have shoulders), + // Will not work well with things that emulate 3 hands in some manner + // Only supports xy 90 degree rotation + // Todo: figure out how to find skeleton's orientation Correctly + // when model is rotated at a triangle level + constexpr char right_shoulder_str[] = "mShoulderRight"; + constexpr char left_shoulder_str[] = "mShoulderLeft"; + + S32 size = (S32)gltf_skin.mJoints.size(); + S32 joints_found = 0; + for (S32 i= 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + auto joint_node = mGLTFAsset.mNodes[joint]; + + // todo: we are doing this search thing everywhere, + // just pre-translate every joint + JointMap::iterator found = mJointMap.find(joint_node.mName); + if (found == mJointMap.end()) + { + // unsupported joint + continue; + } + if (found->second == right_shoulder_str || found->second == left_shoulder_str) + { + if (checkForXYrotation(gltf_skin, joint, i)) + { + joints_found++; + } + else + { + return; + } + } + } + + if (joints_found == 2) + { + // Both joints in a weird position/rotation, assume rotated model + mApplyXYRotation = true; + } +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 41e6a2dd6d..50a63c1cd8 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -156,6 +156,7 @@ protected: bool mGltfLoaded; bool mMeshesLoaded; bool mMaterialsLoaded; + bool mApplyXYRotation = false; U32 mGeneratedModelLimit; std::vector mMeshes; @@ -190,6 +191,8 @@ private: S32 findParentNode(S32 node) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; glm::mat4 computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 joint_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); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); void notifyUnsupportedExtension(bool unsupported); -- cgit v1.2.3 From b3fd05fa97ee90b68fb6003db68afcafeb2c2b95 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 10 Jun 2025 19:36:17 +0300 Subject: #4114 Improve GLTF mesh uploader log --- indra/newview/gltf/llgltfloader.cpp | 46 ++++++++++++++++++---- .../skins/default/xui/en/floater_model_preview.xml | 13 ++++-- 2 files changed, 48 insertions(+), 11 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 8fe5a8f2fb..d0a0a0b541 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -302,6 +302,10 @@ bool LLGLTFLoader::parseMeshes() else { LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + + LLSD args; + args["Message"] = "NoScenesFound"; + mWarningsArray.append(args); return false; } @@ -398,8 +402,16 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map else if (node.mMesh >= 0) { // Log invalid mesh reference - LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " references invalid mesh " << node.mMesh + LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " (" << node.mName + << ") references invalid mesh " << node.mMesh << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + + LLSD args; + args["Message"] = "InvalidMeshReference"; + args["NODE_NAME"] = node.mName; + args["MESH_INDEX"] = node.mMesh; + args["TOTAL_MESHES"] = static_cast(mGLTFAsset.mMeshes.size()); + mWarningsArray.append(args); } // Process all children recursively @@ -510,8 +522,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - for (const LL::GLTF::Primitive& prim : mesh.mPrimitives) + for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) { + const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. if (prim.getVertexCount() < USHRT_MAX) { @@ -566,7 +579,14 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& impMat.mDiffuseMapFilename = filename; impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename << LL_ENDL; + 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()) @@ -663,10 +683,15 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (prim.getIndexCount() % 3 != 0) { - LL_WARNS("GLTF_IMPORT") << "Invalid primitive: index count " << prim.getIndexCount() - << " is not divisible by 3. GLTF files must contain triangulated geometry." << LL_ENDL; + 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(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); mWarningsArray.append(args); continue; // Skip this primitive } @@ -818,10 +843,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& 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; + else + { + LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName + << "' primitive " << prim_idx + << " due to 16-bit index limits. Vertex count: " + << prim.getVertexCount() << " exceeds limit: " << USHRT_MAX << LL_ENDL; LLSD args; args["Message"] = "ErrorIndexLimit"; + args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; + args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); + args["LIMIT"] = USHRT_MAX; mWarningsArray.append(args); return false; } 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 1c4a857dab..d9a344ba41 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -61,10 +61,15 @@ Document has no root Document has no visual_scene Unable to process mesh without position data. Invalid model. - Invalid geometry: GLTF files must contain triangulated meshes only. - Model uses unsupported extension: [EXT], related material properties are ignored. - Unable to load a model, unsupported extension: [EXT] - Unable to process mesh due to 16-bit index limits + + + No scenes defined in GLTF file + Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES]) + Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated) + Mesh [MESH_NAME]: Vertex count [VERTEX_COUNT] exceeds 16-bit limit of [LIMIT] + Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME] + Model uses unsupported extension: [EXT], related material properties are ignored + Unable to load model, unsupported extension: [EXT] Date: Tue, 10 Jun 2025 22:23:25 +0300 Subject: #4214 Weights and Joints remap #2 --- indra/newview/gltf/llgltfloader.cpp | 52 ++++++++----------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index d0a0a0b541..3273cce70b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -499,13 +499,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 gltf_joint_index_use_count; + std::vector gltf_joint_index_valid; 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_valid.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) @@ -517,7 +517,7 @@ 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 + gltf_joint_index_valid[i] = -1; // mark as unsupported } } } @@ -760,29 +760,25 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // 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 + if (gltf_joint_index_valid[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 + if (gltf_joint_index_valid[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 + if (gltf_joint_index_valid[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 + if (gltf_joint_index_valid[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]++; } std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); @@ -868,20 +864,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; - std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); - gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 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) - { - // Unused (0) or unsupported (-1) joint, drop it - continue; - } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); @@ -889,12 +878,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - else - { - llassert(false); // should have been stopped by gltf_joint_index_use_count[i] == -1 - continue; - } - gltfindex_to_joitindex_map[i] = replacement_index++; + // else thanks to gltf_joint_index_valid any illegal + // joint should have zero uses. + // Add them anyway to preserve order, remapSkinWeightsAndJoints + // will sort them out later skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); @@ -922,19 +909,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } - - // Remap indices for pModel->mSkinWeights - // Todo: this is now partially redundant due to - // remapSkinWeightsAndJoints being called later. - // Consider storing all joints now as is and let it - // ramap later due to missing weights. - for (auto& weights : pModel->mSkinWeights) - { - for (auto& weight : weights.second) - { - weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; - } - } } return true; @@ -1203,10 +1177,6 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - if (mApplyXYRotation) - { - return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); - } return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); } -- cgit v1.2.3 From db957313918d1716aa5087f283fd51d7fe49a357 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 10 Jun 2025 23:49:52 +0300 Subject: #4148 Skeleton Translation #3 'non joints' also need adjustments --- indra/llappearance/llavatarappearance.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 34d6b4db83..a3032325c9 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -1679,12 +1679,9 @@ void LLAvatarSkeletonInfo::getJointRestMatrices( { for (LLAvatarBoneInfo* bone_info : bone_list) { - if (bone_info->mIsJoint) - { - glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); - result[bone_info->mName] = rest_mat; - getJointRestMatrices(bone_info->mChildren, result, rest_mat); - } + glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); + result[bone_info->mName] = rest_mat; + getJointRestMatrices(bone_info->mChildren, result, rest_mat); } } -- cgit v1.2.3 From 98abff90a74536c9fa3a7c6d5078fc4b037c9c67 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 10 Jun 2025 23:14:25 +0300 Subject: #4248 Add safety checks to LLMeshSkinInfo::asLLSD() --- indra/llprimitive/llmodel.cpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index bc9f62b6c0..6e45e9b1bf 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1804,13 +1804,21 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi { ret["joint_names"][i] = mJointNames[i]; - for (U32 j = 0; j < 4; j++) + if (i < mInvBindMatrix.size()) { - for (U32 k = 0; k < 4; k++) + for (U32 j = 0; j < 4; j++) { - ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; + for (U32 k = 0; k < 4; k++) + { + ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; + } } } + else + { + LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size " + << mInvBindMatrix.size() << LL_ENDL; + } } for (U32 i = 0; i < 4; i++) @@ -1821,17 +1829,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } - if ( include_joints && mAlternateBindMatrix.size() > 0 ) + if (include_joints && mAlternateBindMatrix.size() > 0) { for (U32 i = 0; i < mJointNames.size(); ++i) { - for (U32 j = 0; j < 4; j++) + if (i < mAlternateBindMatrix.size()) { - for (U32 k = 0; k < 4; k++) + for (U32 j = 0; j < 4; j++) { - ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; + for (U32 k = 0; k < 4; k++) + { + ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; + } } } + else + { + LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " + << mAlternateBindMatrix.size() << LL_ENDL; + } } if (lock_scale_if_joint_position) -- cgit v1.2.3 From 48eb8a2efecb3c308092ce60dcb0c9975a0c53e1 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 11 Jun 2025 01:21:45 +0300 Subject: #4147 Joint override --- indra/newview/gltf/llgltfloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3273cce70b..3e7b14919b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -898,7 +898,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { // 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; + joint_mat = coord_system_rotation; if (mApplyXYRotation) { joint_mat = coord_system_rotationxy * joint_mat; -- cgit v1.2.3 From 1132b19c06aa0b154991256e5063c78ad423bdc3 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 11 Jun 2025 12:31:47 +0300 Subject: #4114 Improve GLTF mesh uploader log 2 --- indra/newview/gltf/llgltfloader.cpp | 20 +++++++++++++++++--- .../skins/default/xui/en/floater_model_preview.xml | 3 ++- 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3e7b14919b..ffe574f4b1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -309,6 +309,21 @@ bool LLGLTFLoader::parseMeshes() return false; } + // Check total model count against limit + U32 total_models = static_cast(mModelList.size()); + if (total_models > mGeneratedModelLimit) + { + LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models + << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL; + + LLSD args; + args["Message"] = "TooManyMeshParts"; + args["PART_COUNT"] = static_cast(total_models); + args["LIMIT"] = static_cast(mGeneratedModelLimit); + mWarningsArray.append(args); + return false; + } + return true; } @@ -843,13 +858,12 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName << "' primitive " << prim_idx - << " due to 16-bit index limits. Vertex count: " - << prim.getVertexCount() << " exceeds limit: " << USHRT_MAX << LL_ENDL; + << " due to 65,534 vertex limit. Vertex count: " + << prim.getVertexCount() << LL_ENDL; LLSD args; args["Message"] = "ErrorIndexLimit"; args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); - args["LIMIT"] = USHRT_MAX; mWarningsArray.append(args); return false; } 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 d9a344ba41..759952b162 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -66,10 +66,11 @@ No scenes defined in GLTF file Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES]) Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated) - Mesh [MESH_NAME]: Vertex count [VERTEX_COUNT] exceeds 16-bit limit of [LIMIT] + Unable to process mesh [MESH_NAME] due to 65,534 vertex limit. Vertex count: [VERTEX_COUNT] Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME] Model uses unsupported extension: [EXT], related material properties are ignored Unable to load model, unsupported extension: [EXT] + Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] Date: Wed, 11 Jun 2025 18:52:41 +0300 Subject: #4147 Move mAlternateBindMatrices For reduced log spam and calculutions and to make further modifications easier. --- indra/newview/gltf/llgltfloader.cpp | 38 ++++++++++++++++++------------------- indra/newview/gltf/llgltfloader.h | 1 + 2 files changed, 19 insertions(+), 20 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index ffe574f4b1..40b9334e63 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -903,25 +903,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // 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 - // Todo: this seems blatantly wrong, it should have been rotated - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJointNode(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; - if (mApplyXYRotation) - { - joint_mat = coord_system_rotationxy * joint_mat; - } - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); - - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); + skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); } } @@ -992,7 +974,23 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } - // todo: prepare mAlternateBindMatrix here + + // Todo: this seems blatantly wrong + glm::mat4 joint_mat = jointNode.mMatrix; + S32 root_joint = findValidRootJointNode(joint, skin); // skeleton can have multiple real roots and one gltf root to group them + if (root_joint == joint) + { + // This is very likely incomplete in some way. + joint_mat = coord_system_rotation; + if (mApplyXYRotation) + { + joint_mat = coord_system_rotationxy * joint_mat; + } + } + LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; + mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); if (!legal_joint) { diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 50a63c1cd8..6009a8e0c7 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -174,6 +174,7 @@ protected: // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; bind_matrices_t mInverseBindMatrices; + bind_matrices_t mAlternateBindMatrices; private: bool parseMeshes(); -- cgit v1.2.3 From 8322a9a61e951275278fbf80b0a46880f5318107 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 12 Jun 2025 14:21:40 +0300 Subject: #4147 Joint Overrides #2 --- indra/llappearance/llavatarappearance.cpp | 22 ++++-- indra/llappearance/llavatarappearance.h | 3 +- indra/newview/gltf/llgltfloader.cpp | 126 ++++++++++++++++-------------- indra/newview/gltf/llgltfloader.h | 12 ++- indra/newview/llmodelpreview.cpp | 7 +- 5 files changed, 97 insertions(+), 73 deletions(-) (limited to 'indra') diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index a3032325c9..f3e474dba7 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -108,7 +108,12 @@ public: private: typedef std::vector bone_info_list_t; - static void getJointRestMatrices(const bone_info_list_t& bone_list, LLAvatarAppearance::joint_rest_map_t& result, const glm::mat4 &parent_mat); + static void getJointRestMatrices( + const bone_info_list_t& bone_list, + const std::string &parent_name, + LLAvatarAppearance::joint_rest_map_t& rest, + LLAvatarAppearance::joint_parent_map_t& parent_map, + const glm::mat4& parent_mat); private: S32 mNumBones; @@ -1674,14 +1679,17 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node) void LLAvatarSkeletonInfo::getJointRestMatrices( const bone_info_list_t& bone_list, - LLAvatarAppearance::joint_rest_map_t& result, + const std::string& parent_name, + LLAvatarAppearance::joint_rest_map_t& rest, + LLAvatarAppearance::joint_parent_map_t& parent_map, const glm::mat4& parent_mat) { for (LLAvatarBoneInfo* bone_info : bone_list) { glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); - result[bone_info->mName] = rest_mat; - getJointRestMatrices(bone_info->mChildren, result, rest_mat); + rest[bone_info->mName] = rest_mat; + parent_map[bone_info->mName] = parent_name; + getJointRestMatrices(bone_info->mChildren, bone_info->mName, rest, parent_map, rest_mat); } } @@ -1746,12 +1754,10 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases return mJointAliasMap; } -LLAvatarAppearance::joint_rest_map_t LLAvatarAppearance:: getJointRestMatrices() const +void LLAvatarAppearance:: getJointRestMatrices(LLAvatarAppearance::joint_rest_map_t& rest_map, LLAvatarAppearance::joint_parent_map_t& parent_map) const { - LLAvatarAppearance::joint_rest_map_t result; glm::mat4 identity(1.f); - LLAvatarSkeletonInfo::getJointRestMatrices(sAvatarSkeletonInfo->mBoneInfoList, result, identity); - return result; + LLAvatarSkeletonInfo::getJointRestMatrices(sAvatarSkeletonInfo->mBoneInfoList, std::string(), rest_map, parent_map, identity); } diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index cea2a837cd..e48d80b8ce 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -154,8 +154,9 @@ public: const avatar_joint_list_t& getSkeleton() { return mSkeleton; } typedef std::map joint_alias_map_t; const joint_alias_map_t& getJointAliases(); + typedef std::map joint_parent_map_t; // matrix plus parent typedef std::map joint_rest_map_t; - joint_rest_map_t getJointRestMatrices() const; + void getJointRestMatrices(joint_rest_map_t& rest_map, joint_parent_map_t& parent_map) const; protected: static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 40b9334e63..292fd09035 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -95,7 +95,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_rest_map_t jointRestMatrices) //, + joint_viewer_rest_map_t jointRestMatrices, + joint_viewer_parent_map_t jointParentMap) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -107,12 +108,13 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ), + maxJointsPerMesh ) //mPreprocessGLTF(preprocess), - mMeshesLoaded(false), - mMaterialsLoaded(false), - mGeneratedModelLimit(modelLimit), - mJointRestMatrices(jointRestMatrices) + , mMeshesLoaded(false) + , mMaterialsLoaded(false) + , mGeneratedModelLimit(modelLimit) + , mJointViewerRestMatrices(jointRestMatrices) + , mJointViewerParentMap(jointParentMap) { } @@ -929,6 +931,16 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) if (mInverseBindMatrices.size() <= skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); + mAlternateBindMatrices.resize(skin_idx + 1); + } + + // Build gltf rest matrices + // This is very inefficienct, todo: run from root to children, not from children to root + joint_node_mat4_map_t gltf_rest_map; + for (S32 i = 0; i < joint_count; i++) + { + S32 joint = skin.mJoints[i]; + gltf_rest_map[joint] = buildGltfRestMatrix(joint, skin); } for (S32 i = 0; i < joint_count; i++) @@ -943,86 +955,84 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) legal_joint = true; } + // Compute bind matrices + if (!legal_joint) { - // add placeholder to not break index + // 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(skin, i, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, 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 << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { + // If bind matrices aren't present (they are optional in gltf), + // assume an identy matrix + // todo: find a model with this, might need to use rotated matrix glm::mat4 inv_bind(1.0f); - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); inv_bind = glm::inverse(skeleton_transform * inv_bind); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); - gltf_transform.setIdentity(); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } - // Todo: this seems blatantly wrong - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJointNode(joint, skin); // skeleton can have multiple real roots and one gltf root to group them - if (root_joint == joint) + // Compute Alternative matrices also known as overrides + + glm::mat4 joint_mat = glm::mat4(1.f); + if (legal_joint) { - // This is very likely incomplete in some way. - joint_mat = coord_system_rotation; - if (mApplyXYRotation) + joint_viewer_parent_map_t::const_iterator found = mJointViewerParentMap.find(legal_name); + if (found != mJointViewerParentMap.end() && !found->second.empty()) { - joint_mat = coord_system_rotationxy * joint_mat; + glm::mat4 gltf_joint_rest_pose = coord_system_rotation * buildGltfRestMatrix(joint, skin); + if (mApplyXYRotation) + { + gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; + } + // Compute viewer's override by moving joint to viewer's base + // This might be overused or somewhat incorect for regular cases + // But this logic should be solid for cases where model lacks + // parent joints that viewer has. + // ex: like boots that have only knees and feet, but no pelvis, + // or anything else, in which case we take viewer's pelvis + glm::mat4 viewer_rest = mJointViewerRestMatrices[found->second]; + joint_mat = glm::inverse(viewer_rest) * gltf_joint_rest_pose; } } LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + // 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: " << original_joint_transform << LL_ENDL; mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); - if (!legal_joint) - { - LL_DEBUGS("GLTF") << "Ignoring unrecognized joint: " << legal_name << LL_ENDL; - continue; - } - - // 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++) - { - 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; - } - - // 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++) + if (legal_joint) { - 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; + // 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); } - - 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; } } @@ -1164,10 +1174,11 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF // 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 LL::GLTF::Skin& gltf_skin, S32 gltf_joint_index, const std::string& joint_name) const + +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t& gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) const { - joint_rest_map_t::const_iterator found = mJointRestMatrices.find(joint_name); - if (found == mJointRestMatrices.end()) + joint_viewer_rest_map_t::const_iterator found = mJointViewerRestMatrices.find(joint_name); + if (found == mJointViewerRestMatrices.end()) { // For now assume they are identical and return an identity (for ease of debuging) // But there should be no joints viewer isn't aware about @@ -1177,19 +1188,18 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski glm::mat4 viewer_joint_rest_pose = found->second; // Get the GLTF joint's rest pose (in GLTF coordinate system) - S32 joint_node_index = gltf_skin.mJoints[gltf_joint_index]; - glm::mat4 gltf_joint_rest_pose = buildGltfRestMatrix(joint_node_index, gltf_skin); - gltf_joint_rest_pose = coord_system_rotation * gltf_joint_rest_pose; + const glm::mat4 &gltf_joint_rest_pose = gltf_rest_map.at(gltf_node_index); + glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; - LLMatrix4 transform(glm::value_ptr(gltf_joint_rest_pose)); + LLMatrix4 transform(glm::value_ptr(rest_pose)); LL_CONT << transform << LL_ENDL; // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); + return viewer_joint_rest_pose * glm::inverse(rest_pose); } bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 6009a8e0c7..bf5f830d47 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -121,7 +121,9 @@ class LLGLTFLoader : public LLModelLoader { public: typedef std::map material_map; - typedef std::map joint_rest_map_t; + typedef std::map joint_viewer_parent_map_t; + typedef std::map joint_viewer_rest_map_t; + typedef std::map joint_node_mat4_map_t; LLGLTFLoader(std::string filename, S32 lod, @@ -135,7 +137,8 @@ class LLGLTFLoader : public LLModelLoader std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_rest_map_t jointRestMatrices); //, + joint_viewer_rest_map_t jointRestMatrices, + joint_viewer_parent_map_t jointParentPap); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -169,7 +172,8 @@ protected: // 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 - joint_rest_map_t mJointRestMatrices; + joint_viewer_rest_map_t mJointViewerRestMatrices; + joint_viewer_parent_map_t mJointViewerParentMap; // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; @@ -191,7 +195,7 @@ private: 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; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; - glm::mat4 computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 joint_index, const std::string& joint_name) const; + glm::mat4 computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t &gltf_rest_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); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 40b6d186b8..d6b438a6e2 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -811,7 +811,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable else { LLVOAvatar* av = getPreviewAvatar(); - LLAvatarAppearance::joint_rest_map_t rest_pose = av->getJointRestMatrices(); + LLAvatarAppearance::joint_rest_map_t rest_pose; + LLAvatarAppearance::joint_parent_map_t rest_parent_map; + av->getJointRestMatrices(rest_pose, rest_parent_map); mModelLoader = new LLGLTFLoader( filename, lod, @@ -825,7 +827,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), - rest_pose); + rest_pose, + rest_parent_map); } if (force_disable_slm) -- cgit v1.2.3 From 54660c8931593ceb465605acf872d5227e1d2d63 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 13 Jun 2025 18:33:49 +0300 Subject: #4147 Joint Overrides #3 Remande skeleton translation from default skeleton to overriden skeleton --- indra/llappearance/CMakeLists.txt | 1 + indra/llappearance/llavatarappearance.cpp | 36 +++---- indra/llappearance/llavatarappearance.h | 3 +- indra/llappearance/lljointdata.h | 44 ++++++++ indra/newview/gltf/llgltfloader.cpp | 162 ++++++++++++++++++++++-------- indra/newview/gltf/llgltfloader.h | 39 ++++++- indra/newview/llmodelpreview.cpp | 9 +- 7 files changed, 223 insertions(+), 71 deletions(-) create mode 100644 indra/llappearance/lljointdata.h (limited to 'indra') diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index c3be8bc78e..6744c8d8a4 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 f3e474dba7..13bea1e5ea 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -29,6 +29,7 @@ #include "llavatarappearance.h" #include "llavatarappearancedefines.h" #include "llavatarjointmesh.h" +#include "lljointdata.h" #include "llstl.h" #include "lldir.h" #include "llpolymorph.h" @@ -108,11 +109,9 @@ public: private: typedef std::vector bone_info_list_t; - static void getJointRestMatrices( - const bone_info_list_t& bone_list, - const std::string &parent_name, - LLAvatarAppearance::joint_rest_map_t& rest, - LLAvatarAppearance::joint_parent_map_t& parent_map, + static void getJointMatricesAndHierarhy( + LLAvatarBoneInfo* bone_info, + LLJointData& data, const glm::mat4& parent_mat); private: @@ -1677,19 +1676,18 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node) return true; } -void LLAvatarSkeletonInfo::getJointRestMatrices( - const bone_info_list_t& bone_list, - const std::string& parent_name, - LLAvatarAppearance::joint_rest_map_t& rest, - LLAvatarAppearance::joint_parent_map_t& parent_map, +void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy( + LLAvatarBoneInfo* bone_info, + LLJointData& data, const glm::mat4& parent_mat) { - for (LLAvatarBoneInfo* bone_info : bone_list) + data.mName = bone_info->mName; + data.mJointMatrix = bone_info->getJointMatrix(); + data.mRestMatrix = parent_mat * data.mJointMatrix; + for (LLAvatarBoneInfo* child_info : bone_info->mChildren) { - glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); - rest[bone_info->mName] = rest_mat; - parent_map[bone_info->mName] = parent_name; - getJointRestMatrices(bone_info->mChildren, bone_info->mName, rest, parent_map, rest_mat); + LLJointData& child_data = data.mChildren.emplace_back(); + getJointMatricesAndHierarhy(child_info, child_data, data.mRestMatrix); } } @@ -1754,10 +1752,14 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases return mJointAliasMap; } -void LLAvatarAppearance:: getJointRestMatrices(LLAvatarAppearance::joint_rest_map_t& rest_map, LLAvatarAppearance::joint_parent_map_t& parent_map) const +void LLAvatarAppearance::getJointMatricesAndHierarhy(std::vector &data) const { glm::mat4 identity(1.f); - LLAvatarSkeletonInfo::getJointRestMatrices(sAvatarSkeletonInfo->mBoneInfoList, std::string(), rest_map, parent_map, identity); + for (LLAvatarBoneInfo* bone_info : sAvatarSkeletonInfo->mBoneInfoList) + { + LLJointData& child_data = data.emplace_back(); + LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(bone_info, child_data, identity); + } } diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index e48d80b8ce..2748da9a1d 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -42,6 +42,7 @@ class LLTexGlobalColorInfo; class LLWearableData; class LLAvatarBoneInfo; class LLAvatarSkeletonInfo; +class LLJointData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // LLAvatarAppearance @@ -156,7 +157,7 @@ public: const joint_alias_map_t& getJointAliases(); typedef std::map joint_parent_map_t; // matrix plus parent typedef std::map joint_rest_map_t; - void getJointRestMatrices(joint_rest_map_t& rest_map, joint_parent_map_t& parent_map) const; + void getJointMatricesAndHierarhy(std::vector &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 bones_t; + bones_t mChildren; +}; + +#endif //LL_LLJOINTDATA_H diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 292fd09035..c6d2bec238 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -95,8 +95,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_viewer_rest_map_t jointRestMatrices, - joint_viewer_parent_map_t jointParentMap) //, + std::vector viewer_skeleton) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -113,8 +112,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, , mMeshesLoaded(false) , mMaterialsLoaded(false) , mGeneratedModelLimit(modelLimit) - , mJointViewerRestMatrices(jointRestMatrices) - , mJointViewerParentMap(jointParentMap) + , mViewerJointData(viewer_skeleton) { } @@ -934,13 +932,45 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) mAlternateBindMatrices.resize(skin_idx + 1); } - // Build gltf rest matrices - // This is very inefficienct, todo: run from root to children, not from children to root - joint_node_mat4_map_t gltf_rest_map; + // 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]; - gltf_rest_map[joint] = buildGltfRestMatrix(joint, skin); + 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; + } + 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; + } + } + + // Go over viewer joints and build overrides + glm::mat4 ident(1.0); + for (auto &viewer_data : mViewerJointData) + { + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); } for (S32 i = 0; i < joint_count; i++) @@ -971,7 +1001,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) // 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(gltf_rest_map, joint, legal_name); + 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); @@ -985,7 +1015,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) // 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(gltf_rest_map, joint, legal_name); + 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)); @@ -994,37 +1024,15 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } // Compute Alternative matrices also known as overrides - - glm::mat4 joint_mat = glm::mat4(1.f); - if (legal_joint) - { - joint_viewer_parent_map_t::const_iterator found = mJointViewerParentMap.find(legal_name); - if (found != mJointViewerParentMap.end() && !found->second.empty()) - { - glm::mat4 gltf_joint_rest_pose = coord_system_rotation * buildGltfRestMatrix(joint, skin); - if (mApplyXYRotation) - { - gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; - } - // Compute viewer's override by moving joint to viewer's base - // This might be overused or somewhat incorect for regular cases - // But this logic should be solid for cases where model lacks - // parent joints that viewer has. - // ex: like boots that have only knees and feet, but no pelvis, - // or anything else, in which case we take viewer's pelvis - glm::mat4 viewer_rest = mJointViewerRestMatrices[found->second]; - joint_mat = glm::inverse(viewer_rest) * gltf_joint_rest_pose; - } - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + 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: " << original_joint_transform << LL_ENDL; - mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; + mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); if (legal_joint) { @@ -1139,6 +1147,57 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& leftover) const +{ + glm::mat4 new_lefover(1.f); + glm::mat4 rest(1.f); + joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName); + if (found_node != names_to_nodes.end()) + { + S32 gltf_node_idx = found_node->second; + JointNodeData& node = gltf_nodes[gltf_node_idx]; + node.mIsOverrideValid = true; + + glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; + if (mApplyXYRotation) + { + gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; + } + 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 * viewer_joint; + node.mOverrideRestMatrix = rest; + if (viewer_data.mName == "mPelvis") + { + // Todo: This is wrong, but this is a temporary + // solution for parts staying behind. + // Something is still missing with override mechanics + node.mOverrideMatrix = glm::mat4(1.f); + } + } + else + { + // No override for this joint + new_lefover = leftover * viewer_data.mJointMatrix; + rest = parent_rest * viewer_data.mJointMatrix; + } + for (LLJointData& child_data : viewer_data.mChildren) + { + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, new_lefover); + } +} + glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const { // This is inefficient since we are recalculating some joints multiple times over @@ -1172,23 +1231,40 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF return node.mMatrix; } +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const +{ + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? + + if (joint_node_index < 0 || joint_node_index >= static_cast(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + auto& data = joint_data.at(joint_node_index); + + if (data.mParentNodeIdx >=0) + { + return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix; + } + // Should we return armature or stop earlier? + return data.mGltfMatrix; +} + // This function computes the transformation matrix needed to convert from GLTF skeleton space // to viewer skeleton space for a specific joint -glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t& gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) const +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const { - joint_viewer_rest_map_t::const_iterator found = mJointViewerRestMatrices.find(joint_name); - if (found == mJointViewerRestMatrices.end()) + const JointNodeData& node_data = joints_data_map.at(gltf_node_index); + if (!node_data.mIsOverrideValid) { // For now assume they are identical and return an identity (for ease of debuging) - // But there should be no joints viewer isn't aware about - // Warn or assert about missing joints return glm::mat4(1.0f); } - glm::mat4 viewer_joint_rest_pose = found->second; // Get the GLTF joint's rest pose (in GLTF coordinate system) - const glm::mat4 &gltf_joint_rest_pose = gltf_rest_map.at(gltf_node_index); + const glm::mat4 &gltf_joint_rest_pose = node_data.mGltfRestMatrix; glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; @@ -1199,7 +1275,7 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_ma // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - return viewer_joint_rest_pose * glm::inverse(rest_pose); + return node_data.mOverrideRestMatrix * glm::inverse(rest_pose); } bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index bf5f830d47..408f9243c7 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -32,6 +32,7 @@ #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 @@ -125,6 +126,34 @@ class LLGLTFLoader : public LLModelLoader typedef std::map joint_viewer_rest_map_t; typedef std::map 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 joints_data_map_t; + typedef std::map joints_name_to_node_map_t; + LLGLTFLoader(std::string filename, S32 lod, LLModelLoader::load_callback_t load_cb, @@ -137,8 +166,7 @@ class LLGLTFLoader : public LLModelLoader std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_viewer_rest_map_t jointRestMatrices, - joint_viewer_parent_map_t jointParentPap); //, + std::vector viewer_skeleton); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -172,8 +200,7 @@ protected: // 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 - joint_viewer_rest_map_t mJointViewerRestMatrices; - joint_viewer_parent_map_t mJointViewerParentMap; + std::vector mViewerJointData; // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; @@ -194,8 +221,10 @@ private: 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, glm::mat4& leftover) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; - glm::mat4 computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t &gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) 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); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index d6b438a6e2..fc0a3ec58f 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" @@ -811,9 +812,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable else { LLVOAvatar* av = getPreviewAvatar(); - LLAvatarAppearance::joint_rest_map_t rest_pose; - LLAvatarAppearance::joint_parent_map_t rest_parent_map; - av->getJointRestMatrices(rest_pose, rest_parent_map); + std::vector viewer_skeleton; + av->getJointMatricesAndHierarhy(viewer_skeleton); mModelLoader = new LLGLTFLoader( filename, lod, @@ -827,8 +827,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), - rest_pose, - rest_parent_map); + viewer_skeleton); } if (force_disable_slm) -- cgit v1.2.3 From 46aeaf48034fead1dd69060f07494d53ade1ae80 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 17 Jun 2025 22:39:58 +0300 Subject: #4242 Support splitting of gltf faces that are over 16bit limit --- indra/newview/gltf/llgltfloader.cpp | 623 ++++++++++++++++++++---------------- indra/newview/gltf/llgltfloader.h | 2 +- 2 files changed, 353 insertions(+), 272 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c6d2bec238..33639cf93c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -514,13 +514,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 gltf_joint_index_valid; + std::vector 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_valid.resize(jointCnt); + gltf_joint_index_use.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) @@ -532,7 +532,8 @@ 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_valid[i] = -1; // mark as unsupported + // This might need to hold a substitute index + gltf_joint_index_use[i] = -1; // mark as unsupported } } } @@ -540,332 +541,420 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) { const LL::GLTF::Primitive& prim = mesh.mPrimitives[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 vertices; - std::vector indices; - 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 vertices; + + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color - // Process material if available - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + // 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); - } + filename = filename.substr(pos + 1); + } - // Store the texture filename - impMat.mDiffuseMapFilename = filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; + // 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; + 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); + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; + args["MATERIAL_NAME"] = material->mName; + mWarningsArray.append(args); - // If the image has a texture loaded already, use it - if (image.mTexture.notNull()) - { - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; - } - else - { - // 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; - } - } - else if (image.mTexture.notNull()) + // 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 + // Let the model preview know we need to load this texture mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << 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) + // 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; + } } } } + } - // Apply the global scale and center offset to all vertices - for (U32 i = 0; i < prim.getVertexCount(); i++) - { - // Use pre-computed final_transform - glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); - glm::vec4 transformed_pos = final_transform * pos; - - GLTFVertex vert; - vert.position = glm::vec3(transformed_pos); - - if (!prim.mNormals.empty()) - { - // Use pre-computed normal_transform - glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.normal = glm::normalize(normal_transform * normal_vec); - } - else - { - // Use default normal (pointing up in model space) - vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); - LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; - } + 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; - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + LLSD args; + args["Message"] = "InvalidGeometryNonTriangulated"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); + mWarningsArray.append(args); + return false; // Skip this primitive + } - if (skinIdx >= 0) - { - vert.weights = glm::vec4(prim.mWeights[i]); + // 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; - auto accessorIdx = prim.mAttributes.at("JOINTS_0"); - LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; - if (accessorIdx >= 0) - { - auto accessor = mGLTFAsset.mAccessors[accessorIdx]; - componentType = accessor.mComponentType; - } + GLTFVertex vert; + vert.position = glm::vec3(transformed_pos); - // 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(); - vert.weights = glm::zero(); - } - } - vertices.push_back(vert); + 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); } - - if (prim.getIndexCount() % 3 != 0) + else { - 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(prim_idx); - args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); - 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(); + vert.weights = glm::zero(); } } + 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(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); + mWarningsArray.append(args); + return false; // Skip this primitive + } + + std::vector 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) { - LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL; - continue; // Skip this primitive + min = max = vertices[i].position; + } + else + { + min.x = std::min(min.x, vertices[i].position.x); + min.y = std::min(min.y, vertices[i].position.y); + min.z = std::min(min.z, vertices[i].position.z); + max.x = std::max(max.x, vertices[i].position.x); + max.y = std::max(max.y, vertices[i].position.y); + max.z = std::max(max.z, vertices[i].position.z); } - std::vector 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)); } - 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)); } - - 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_valid[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)); - } - if (gltf_joint_index_valid[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)); - } - if (gltf_joint_index_valid[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)); - } - if (gltf_joint_index_valid[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)); - } + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.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)); + } - std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - std::vector wght; - F32 total = 0.f; + std::vector 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; + + // 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 indices_32; + for (U32 i = 0; i < prim.getIndexCount(); i += 3) + { + // When processing indices, flip winding order if needed + if (hasNegativeScale) + { + // Flip winding order for negative scale + indices_32.push_back(prim.mIndexArray[i]); + indices_32.push_back(prim.mIndexArray[i + 2]); // Swap these two + indices_32.push_back(prim.mIndexArray[i + 1]); + } + else + { + indices_32.push_back(prim.mIndexArray[i]); + indices_32.push_back(prim.mIndexArray[i + 1]); + indices_32.push_back(prim.mIndexArray[i + 2]); + } + } - // Create a unique material name for this primitive - std::string materialName; - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + // remap 32 bit into multiple 16 bit ones + std::vector indices_16; + std::vector vertices_remap; // should it be a point map? + vertices_remap.resize(faceVertices.size(), -1); + std::vector face_verts; + min = glm::vec3(FLT_MAX); + max = glm::vec3(-FLT_MAX); + for (size_t idx = 0; idx < indices_32.size(); idx++) { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - materialName = material->mName; + size_t vert_index = indices_32[idx]; + if (vertices_remap[vert_index] == -1) + { + // First encounter, add it + size_t new_vert_idx = face_verts.size(); + vertices_remap[vert_index] = (S64)new_vert_idx; + face_verts.push_back(faceVertices[vert_index]); + vert_index = new_vert_idx; + + // Update min/max bounds + const LLVector4a& vec = face_verts[new_vert_idx].getPosition(); + if (new_vert_idx == 0) + { + min.x = vec[0]; + min.y = vec[1]; + min.z = vec[2]; + max = min; + } + else + { + min.x = std::min(min.x, vec[0]); + min.y = std::min(min.y, vec[1]); + min.z = std::min(min.z, vec[2]); + max.x = std::max(max.x, vec[0]); + max.y = std::max(max.y, vec[1]); + max.z = std::max(max.z, vec[2]); + } + } + else + { + // already in vector, get position + vert_index = (size_t)vertices_remap[vert_index]; + } + indices_16.push_back((U16)vert_index); - if (materialName.empty()) + if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532) { - materialName = "mat" + std::to_string(prim.mMaterial); + 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); } - - pModel->getMaterialList().push_back(materialName); - mats[materialName] = impMat; } else { - LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName - << "' primitive " << prim_idx - << " due to 65,534 vertex limit. Vertex count: " - << prim.getVertexCount() << LL_ENDL; - LLSD args; - args["Message"] = "ErrorIndexLimit"; - args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; - args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); - mWarningsArray.append(args); - return false; + // can use indices directly + std::vector 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); } } @@ -892,7 +981,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - // else thanks to gltf_joint_index_valid any illegal + // else thanks to gltf_joint_index_usage any illegal // joint should have zero uses. // Add them anyway to preserve order, remapSkinWeightsAndJoints // will sort them out later @@ -970,7 +1059,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) { - buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident); } for (S32 i = 0; i < joint_count; i++) @@ -1147,7 +1236,7 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } -void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& leftover) const +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); @@ -1176,25 +1265,17 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map glm::mat4 viewer_joint = glm::recompose(scale, rotation, override, skew, perspective); node.mOverrideMatrix = viewer_joint; - rest = parent_rest * viewer_joint; + rest = parent_rest * node.mOverrideMatrix; node.mOverrideRestMatrix = rest; - if (viewer_data.mName == "mPelvis") - { - // Todo: This is wrong, but this is a temporary - // solution for parts staying behind. - // Something is still missing with override mechanics - node.mOverrideMatrix = glm::mat4(1.f); - } } else { // No override for this joint - new_lefover = leftover * viewer_data.mJointMatrix; rest = parent_rest * viewer_data.mJointMatrix; } for (LLJointData& child_data : viewer_data.mChildren) { - buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, new_lefover); + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest); } } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 408f9243c7..aa77c39030 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -221,7 +221,7 @@ private: 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, glm::mat4& leftover) 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; -- cgit v1.2.3 From bb45bfae0d5f3a7c4d588a2727a86172b12e7c40 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 18 Jun 2025 15:57:11 +0300 Subject: #4204 Fix GLTF texture loading to match DAE loader behavior --- indra/newview/gltf/llgltfloader.cpp | 72 +++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 11 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 33639cf93c..1f65435728 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 #include +#include static const std::string lod_suffix[LLModel::NUM_LODS] = { @@ -609,9 +612,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } 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; + // 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()) @@ -623,14 +625,62 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else if (image.mBufferView >= 0) { // 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; + // Extract the texture to a temporary file immediately + if (image.mBufferView < mGLTFAsset.mBufferViews.size()) + { + const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; + if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) + { + const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + + if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) + { + // Extract image data + const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; + U32 data_size = buffer_view.mByteLength; + + // Determine the file extension + std::string extension = ".png"; // Default + if (!image.mMimeType.empty()) + { + if (image.mMimeType == "image/jpeg") + extension = ".jpg"; + else if (image.mMimeType == "image/png") + extension = ".png"; + } + else if (data_size >= 4) + { + if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) + extension = ".jpg"; // JPEG magic bytes + else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) + extension = ".png"; // PNG magic bytes + } + + // Create a temporary file + std::string temp_dir = gDirUtilp->getTempDir(); + std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + + "gltf_embedded_" + std::to_string(sourceIndex) + extension; + + // Write the image data to the temporary file + std::ofstream temp_file(temp_filename, std::ios::binary); + if (temp_file.is_open()) + { + temp_file.write(reinterpret_cast(data_ptr), data_size); + temp_file.close(); + + // Use the temp file as the texture filename + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; + + LL_INFOS("GLTF_IMPORT") << "Extracted embedded texture to: " << temp_filename << LL_ENDL; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; + } + } + } + } } } } -- cgit v1.2.3 From 68dc0919b3645724f70ca6e609a3f893cba49a69 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 18 Jun 2025 16:50:17 +0300 Subject: #4204 Don't show material editor with model loader --- indra/newview/llfloatermodelpreview.cpp | 5 ----- 1 file changed, 5 deletions(-) (limited to 'indra') 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& 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 { -- cgit v1.2.3 From 5099401a5364ebfe4ec908fc0481ed3568778651 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 18 Jun 2025 17:18:48 +0300 Subject: #4204 Log embedded texture extraction failure --- indra/newview/gltf/llgltfloader.cpp | 6 ++++++ indra/newview/skins/default/xui/en/floater_model_preview.xml | 1 + 2 files changed, 7 insertions(+) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1f65435728..50ce075d65 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -677,6 +677,12 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else { LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; + + LLSD args; + args["Message"] = "FailedToCreateTempFile"; + args["TEXTURE_INDEX"] = sourceIndex; + args["TEMP_FILE"] = temp_filename; + mWarningsArray.append(args); } } } 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 759952b162..6c177e5008 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -71,6 +71,7 @@ Model uses unsupported extension: [EXT], related material properties are ignored Unable to load model, unsupported extension: [EXT] Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] + Failed to create temporary file for embedded texture [TEXTURE_INDEX]: [TEMP_FILE] Date: Wed, 18 Jun 2025 21:55:57 +0300 Subject: #4250 Crash uploading a dae model --- indra/llprimitive/llmodel.cpp | 48 ++++++++++++++++++++++------------------ indra/newview/llskinningutil.cpp | 3 ++- 2 files changed, 28 insertions(+), 23 deletions(-) (limited to 'indra') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 6e45e9b1bf..e47c0d8d07 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1804,21 +1804,23 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi { ret["joint_names"][i] = mJointNames[i]; - if (i < mInvBindMatrix.size()) - { - 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]; - } - } - } - else + // For model to work at all there must be a matching bind matrix, + // so supply an indentity one if it isn't true + // Note: can build an actual bind matrix from joints + const LLMatrix4a& inv_bind = mInvBindMatrix.size() > i ? mInvBindMatrix[i] : LLMatrix4a::identity(); + if (i >= mInvBindMatrix.size()) { LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size " << mInvBindMatrix.size() << LL_ENDL; } + + for (U32 j = 0; j < 4; j++) + { + for (U32 k = 0; k < 4; k++) + { + ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k]; + } + } } for (U32 i = 0; i < 4; i++) @@ -1829,25 +1831,27 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } + // optional 'joint overrides' if (include_joints && mAlternateBindMatrix.size() > 0) { for (U32 i = 0; i < mJointNames.size(); ++i) { - if (i < mAlternateBindMatrix.size()) - { - 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]; - } - } - } - else + // If there is not enough to match mJointNames, + // either supply no alternate matrixes at all or supply + // replacements + const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity(); + if (i >= mAlternateBindMatrix.size()) { LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " << mAlternateBindMatrix.size() << LL_ENDL; } + for (U32 j = 0; j < 4; j++) + { + for (U32 k = 0; k < 4; k++) + { + ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k]; + } + } } if (lock_scale_if_joint_position) 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); -- cgit v1.2.3 From f5320302d4b81825b1037ca0074a65ad9eac14ac Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 19 Jun 2025 19:20:35 +0300 Subject: #4214 Revert and remake "weights remap" This reverts commits fe10a83f69bb26eb581e143fb99c1250c355938b and 08f6f5c697fce4ccbfba357ab9ce5af915dd0574.. --- indra/llprimitive/llmodel.cpp | 87 ---------------------- indra/llprimitive/llmodel.h | 1 - indra/newview/gltf/llgltfloader.cpp | 79 ++++++++++++++++++-- indra/newview/gltf/llgltfloader.h | 3 + .../skins/default/xui/en/floater_model_preview.xml | 2 + 5 files changed, 78 insertions(+), 94 deletions(-) (limited to 'indra') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index e47c0d8d07..3d31cfbb7f 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -112,93 +112,6 @@ void LLModel::remapVolumeFaces() } } -void LLModel::remapSkinWeightsAndJoints() -{ - if (mSkinWeights.empty()) - { - return; - } - - mPosition.clear(); - - // Make a list of positions - std::set positions; - for (S32 i = 0; i < getNumVolumeFaces(); ++i) - { - for (S32 j = 0; j < mVolumeFaces[i].mNumVertices; ++j) - { - positions.emplace(mVolumeFaces[i].mPositions[j].getF32ptr()); - } - } - - // Build new list of weights and record used joints - weight_map replacement_weights; - std::vector joint_index_use_count; - size_t joint_count = mSkinInfo.mJointNames.size(); - joint_index_use_count.resize(joint_count, 0); - for (const LLVector3& pos : positions) - { - mPosition.push_back(pos); - auto found = mSkinWeights.find(pos); - if (found != mSkinWeights.end()) - { - replacement_weights[pos] = found->second; - - for (auto& weight : found->second) - { - if (joint_count > weight.mJointIdx) - { - joint_index_use_count[weight.mJointIdx]++; - } - } - } - } - - // go over joint data and remap joints - // prepare joint map - std::vector replacement_joint_names; - std::vector replacement_joint_nums; - LLMeshSkinInfo::matrix_list_t replacement_inv_bind; - LLMeshSkinInfo::matrix_list_t replacement_alt_bind; - std::vector index_map; - index_map.resize(joint_count); - S32 replacement_index = 0; - - for (S32 i = 0; i < joint_count; i++) - { - if (joint_index_use_count[i] > 0) - { - replacement_joint_names.push_back(mSkinInfo.mJointNames[i]); - replacement_joint_nums.push_back(mSkinInfo.mJointNums[i]); - replacement_inv_bind.push_back(mSkinInfo.mInvBindMatrix[i]); - replacement_alt_bind.push_back(mSkinInfo.mAlternateBindMatrix[i]); - index_map[i] = replacement_index++; - } - } - - // Apply new data - mSkinInfo.mJointNames.clear(); - mSkinInfo.mJointNames = replacement_joint_names; - mSkinInfo.mJointNums.clear(); - mSkinInfo.mJointNums = replacement_joint_nums; - mSkinInfo.mInvBindMatrix.clear(); - mSkinInfo.mInvBindMatrix = replacement_inv_bind; - mSkinInfo.mAlternateBindMatrix.clear(); - mSkinInfo.mAlternateBindMatrix = replacement_alt_bind; - - // remap weights - for (auto& weights : replacement_weights) - { - for (auto& weight : weights.second) - { - weight.mJointIdx = index_map[weight.mJointIdx]; - } - } - - mSkinWeights.clear(); - mSkinWeights = replacement_weights; -} - void LLModel::optimizeVolumeFaces() { for (S32 i = 0; i < getNumVolumeFaces(); ++i) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 7fa3a00ee2..5c6d0a55d2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -205,7 +205,6 @@ public: void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); - void remapSkinWeightsAndJoints(); void optimizeVolumeFaces(); void offsetMesh( const LLVector3& pivotPoint ); void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 50ce075d65..e495abfe75 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -225,8 +225,6 @@ void LLGLTFLoader::addModelToScene( { // remove unused/redundant vertices model->remapVolumeFaces(); - // remove unused/redundant weights and joints - model->remapSkinWeightsAndJoints(); mModelList.push_back(model); @@ -821,21 +819,25 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& && vertices[i].weights.x > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + gltf_joint_index_use[vertices[i].joints.x]++; } if (gltf_joint_index_use[vertices[i].joints.y] >= 0 && vertices[i].weights.y > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + gltf_joint_index_use[vertices[i].joints.y]++; } if (gltf_joint_index_use[vertices[i].joints.z] >= 0 && vertices[i].weights.z > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + gltf_joint_index_use[vertices[i].joints.z]++; } if (gltf_joint_index_use[vertices[i].joints.w] >= 0 && vertices[i].weights.w > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + gltf_joint_index_use[vertices[i].joints.w]++; } std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); @@ -1022,14 +1024,23 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; + S32 valid_joints_count = mValidJointsCount[skinIdx]; + std::vector 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[i] < 0) + { + // unsupported (-1) joint, drop it + continue; + } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); @@ -1037,10 +1048,28 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - // else thanks to gltf_joint_index_usage any illegal - // joint should have zero uses. - // Add them anyway to preserve order, remapSkinWeightsAndJoints - // will sort them out later + else + { + llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 + continue; + } + + if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT + && gltf_joint_index_use[i] == 0 + && legal_name != "mPelvis") + { + // 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; + } + + gltfindex_to_joitindex_map[i] = replacement_index++; skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); @@ -1050,6 +1079,28 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); } + + if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel + << " Count: " << (S32)skin_info.mInvBindMatrix.size() + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + LLSD args; + args["Message"] = "ModelTooManyJoint"; + args["MODEL_NAME"] = pModel->mLabel; + args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + + // Remap indices for pModel->mSkinWeights + for (auto& weights : pModel->mSkinWeights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; + } + } } return true; @@ -1075,6 +1126,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); + mValidJointsCount.resize(skin_idx + 1, 0); } // fill up joints related data @@ -1095,6 +1147,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { data.mName = mJointMap[jointNode.mName]; data.mIsValidViewerJoint = true; + mValidJointsCount[skin_idx]++; } else { @@ -1111,6 +1164,20 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } } + if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" + << " Count: " << mValidJointsCount[skin_idx] + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + + LLSD args; + args["Message"] = "SkinJointsOverLimit"; + args["SKIN_INDEX"] = (S32)skin_idx; + args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + // Go over viewer joints and build overrides glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index aa77c39030..393df8c4ee 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -207,6 +207,9 @@ protected: bind_matrices_t mInverseBindMatrices; bind_matrices_t mAlternateBindMatrices; + // per skin joint count, needs to be tracked for the sake of limits check. + std::vector mValidJointsCount; + private: bool parseMeshes(); void uploadMeshes(); 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 6c177e5008..4c60320a94 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -72,6 +72,8 @@ Unable to load model, unsupported extension: [EXT] Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] Failed to create temporary file for embedded texture [TEXTURE_INDEX]: [TEMP_FILE] + Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis. + Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail Date: Thu, 19 Jun 2025 20:57:44 +0300 Subject: #4204 Unused code cleanup (#4278) --- indra/newview/gltf/llgltfloader.cpp | 377 ++++----------------- indra/newview/gltf/llgltfloader.h | 3 +- .../skins/default/xui/en/floater_model_preview.xml | 2 +- 3 files changed, 65 insertions(+), 317 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index e495abfe75..056d3df1a6 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -623,67 +623,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else if (image.mBufferView >= 0) { // For embedded textures (no URI but has buffer data) - // Extract the texture to a temporary file immediately - if (image.mBufferView < mGLTFAsset.mBufferViews.size()) + std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); + if (!temp_filename.empty()) { - const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; - if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) - { - const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; - - if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) - { - // Extract image data - const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; - U32 data_size = buffer_view.mByteLength; - - // Determine the file extension - std::string extension = ".png"; // Default - if (!image.mMimeType.empty()) - { - if (image.mMimeType == "image/jpeg") - extension = ".jpg"; - else if (image.mMimeType == "image/png") - extension = ".png"; - } - else if (data_size >= 4) - { - if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) - extension = ".jpg"; // JPEG magic bytes - else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) - extension = ".png"; // PNG magic bytes - } - - // Create a temporary file - std::string temp_dir = gDirUtilp->getTempDir(); - std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + - "gltf_embedded_" + std::to_string(sourceIndex) + extension; - - // Write the image data to the temporary file - std::ofstream temp_file(temp_filename, std::ios::binary); - if (temp_file.is_open()) - { - temp_file.write(reinterpret_cast(data_ptr), data_size); - temp_file.close(); - - // Use the temp file as the texture filename - impMat.mDiffuseMapFilename = temp_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; - - LL_INFOS("GLTF_IMPORT") << "Extracted embedded texture to: " << temp_filename << LL_ENDL; - } - else - { - LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; - - LLSD args; - args["Message"] = "FailedToCreateTempFile"; - args["TEXTURE_INDEX"] = sourceIndex; - args["TEMP_FILE"] = temp_filename; - mWarningsArray.append(args); - } - } - } + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; } } } @@ -1730,286 +1674,89 @@ void LLGLTFLoader::uploadMeshes() // convert raw image buffers to texture UUIDs & assemble into a render material void LLGLTFLoader::uploadMaterials() { - LL_INFOS("GLTF_IMPORT") << "Uploading materials, count: " << mMaterials.size() << LL_ENDL; - - for (gltf_render_material& mat : mMaterials) - { - LL_INFOS("GLTF_IMPORT") << "Processing material: " << mat.name << LL_ENDL; - - // 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); - - 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); - } - } - - if (mat.hasNormalTex && mat.normalTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.normalTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasOcclusionTex && mat.occlusionTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasEmissiveTex && mat.emissiveTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - } - - // Update material map for all model instances to ensure textures are properly associated - // mScene is a std::map, 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; - - 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; - } - } - } - } - } - } - } - } } - -LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) +std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Invalid texture indices in imageBufferToTextureUUID" << LL_ENDL; - return LLUUID::null; - } - - gltf_image& image = mImages[tex.imageIdx]; - gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) + return ""; - S32 sourceIndex = tex.imageIdx; + S32 sourceIndex = mGLTFAsset.mTextures[textureIndex].mSource; if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) - { - LL_WARNS("GLTF_IMPORT") << "Invalid image index: " << sourceIndex << LL_ENDL; - return LLUUID::null; - } + return ""; - LL::GLTF::Image& source_image = mGLTFAsset.mImages[sourceIndex]; + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - // If the image already has a texture loaded, use it - if (source_image.mTexture.notNull()) + // Handle URI-based textures + if (!image.mUri.empty()) { - LL_INFOS("GLTF_IMPORT") << "Using already loaded texture ID: " << source_image.mTexture->getID().asString() << LL_ENDL; - return source_image.mTexture->getID(); + return image.mUri; // Return URI directly } - // Create an import material to pass to the texture load function - LLImportMaterial material; - - // Try to get the texture filename from the URI - if (!source_image.mUri.empty()) + // Handle embedded textures + if (image.mBufferView >= 0) { - std::string filename = source_image.mUri; - - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) + if (image.mBufferView < mGLTFAsset.mBufferViews.size()) { - filename = filename.substr(pos + 1); - } - - 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 - { - LL_WARNS("GLTF_IMPORT") << "No URI or buffer data for image" << LL_ENDL; - return LLUUID::null; - } - - // Create LLSD container with image and sampler data for texture upload - LLSD texture_data = LLSD::emptyMap(); - - // 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); - - // 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); - - // Add URI for reference - if (!source_image.mUri.empty()) - { - texture_data["uri"] = source_image.mUri; - } - - // Check if we have a buffer view for embedded data - if (source_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()) - { - 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++; - - U32 result = mTextureLoadFunc(material, mOpaqueData); + // 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 + } - // 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; + // 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 (material.getDiffuseMap().notNull()) - { - LL_INFOS("GLTF_IMPORT") << "Texture loaded successfully, ID: " << material.getDiffuseMap().asString() << LL_ENDL; + // 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(data_ptr), data_size); + temp_file.close(); - // 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()); + LL_INFOS("GLTF_IMPORT") << "Extracted embedded " << texture_type << " texture to: " << temp_filename << LL_ENDL; + return temp_filename; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for " << texture_type << " texture: " << temp_filename << LL_ENDL; + + LLSD args; + args["Message"] = "FailedToCreateTempFile"; + args["TEXTURE_INDEX"] = sourceIndex; + args["TEXTURE_TYPE"] = texture_type; + args["TEMP_FILE"] = temp_filename; + mWarningsArray.append(args); + } } - - return 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 393df8c4ee..e0b3664012 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -230,7 +230,8 @@ private: 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); - LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + + std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type); void notifyUnsupportedExtension(bool unsupported); 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 4c60320a94..80b6427738 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -71,7 +71,7 @@ Model uses unsupported extension: [EXT], related material properties are ignored Unable to load model, unsupported extension: [EXT] Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] - Failed to create temporary file for embedded texture [TEXTURE_INDEX]: [TEMP_FILE] + Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE] Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis. Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail -- cgit v1.2.3 From 1a6e3286110f9e54611715b44c5f8b47d08dc310 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 19 Jun 2025 22:31:38 +0300 Subject: #4204 Remove more unused code --- indra/newview/gltf/llgltfloader.cpp | 200 +----------------------------------- indra/newview/gltf/llgltfloader.h | 95 ----------------- 2 files changed, 2 insertions(+), 293 deletions(-) (limited to 'indra') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 056d3df1a6..7a1ebf0610 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -111,9 +111,6 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointsFromNodes, jointAliasMap, maxJointsPerMesh ) - //mPreprocessGLTF(preprocess), - , mMeshesLoaded(false) - , mMaterialsLoaded(false) , mGeneratedModelLimit(modelLimit) , mViewerJointData(viewer_skeleton) { @@ -137,15 +134,11 @@ 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( @@ -1486,195 +1479,6 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) } } -bool LLGLTFLoader::parseMaterials() -{ - if (!mGltfLoaded) return false; - - // fill local texture data structures - mSamplers.clear(); - for (auto& in_sampler : mGLTFAsset.mSamplers) - { - 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) - { - 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(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(image.height * image.width * image.numChannels * image.bytesPerChannel); - } - } - - // 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); - } - - mTextures.clear(); - for (auto& in_tex : mGLTFAsset.mTextures) - { - gltf_texture tex; - tex.imageIdx = in_tex.mSource; - tex.samplerIdx = in_tex.mSampler; - tex.imageUuid.setNull(); - - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; - return false; - } - - mTextures.push_back(tex); - } - - // parse each material - mMaterials.clear(); - for (const auto& gltf_material : mGLTFAsset.mMaterials) - { - 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); - } - - 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() -{ -} std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index e0b3664012..19a029d6d4 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -35,89 +35,6 @@ #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: @@ -185,18 +102,9 @@ protected: LL::GLTF::Asset mGLTFAsset; tinygltf::Model mGltfModel; bool mGltfLoaded; - bool mMeshesLoaded; - bool mMaterialsLoaded; bool mApplyXYRotation = false; U32 mGeneratedModelLimit; - std::vector mMeshes; - std::vector mMaterials; - - std::vector mTextures; - std::vector mImages; - std::vector mSamplers; - // 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 @@ -212,9 +120,6 @@ protected: 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& 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); -- cgit v1.2.3