diff options
author | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2025-06-13 18:33:49 +0300 |
---|---|---|
committer | Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> | 2025-06-13 21:49:02 +0300 |
commit | 54660c8931593ceb465605acf872d5227e1d2d63 (patch) | |
tree | 6460f98d9e12a5640d93516a39ccc78aabdcf76b | |
parent | 8322a9a61e951275278fbf80b0a46880f5318107 (diff) |
#4147 Joint Overrides #3
Remande skeleton translation from default skeleton to overriden skeleton
-rw-r--r-- | indra/llappearance/CMakeLists.txt | 1 | ||||
-rw-r--r-- | indra/llappearance/llavatarappearance.cpp | 36 | ||||
-rw-r--r-- | indra/llappearance/llavatarappearance.h | 3 | ||||
-rw-r--r-- | indra/llappearance/lljointdata.h | 44 | ||||
-rw-r--r-- | indra/newview/gltf/llgltfloader.cpp | 162 | ||||
-rw-r--r-- | indra/newview/gltf/llgltfloader.h | 39 | ||||
-rw-r--r-- | indra/newview/llmodelpreview.cpp | 9 |
7 files changed, 223 insertions, 71 deletions
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<LLAvatarBoneInfo*> 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<LLJointData> &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<std::string, std::string> joint_parent_map_t; // matrix plus parent typedef std::map<std::string, glm::mat4> joint_rest_map_t; - void getJointRestMatrices(joint_rest_map_t& rest_map, joint_parent_map_t& parent_map) const; + void getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const; protected: static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/llappearance/lljointdata.h b/indra/llappearance/lljointdata.h new file mode 100644 index 0000000000..549f4af041 --- /dev/null +++ b/indra/llappearance/lljointdata.h @@ -0,0 +1,44 @@ +/** + * @file lljointdata.h + * @brief LLJointData class for holding individual joint data and skeleton + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLJOINTDATA_H +#define LL_LLJOINTDATA_H + +#include "v4math.h" + +// may be just move LLAvatarBoneInfo +class LLJointData +{ +public: + std::string mName; + glm::mat4 mJointMatrix; + glm::mat4 mRestMatrix; + + typedef std::vector<LLJointData> bones_t; + bones_t mChildren; +}; + +#endif //LL_LLJOINTDATA_H diff --git a/indra/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<std::string, std::string> &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_viewer_rest_map_t jointRestMatrices, - joint_viewer_parent_map_t jointParentMap) //, + std::vector<LLJointData> 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<S32>(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + auto& data = joint_data.at(joint_node_index); + + if (data.mParentNodeIdx >=0) + { + return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix; + } + // Should we return armature or stop earlier? + return data.mGltfMatrix; +} + // This function computes the transformation matrix needed to convert from GLTF skeleton space // to viewer skeleton space for a specific joint -glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const 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<std::string, glm::mat4> joint_viewer_rest_map_t; typedef std::map<S32, glm::mat4> joint_node_mat4_map_t; + struct JointNodeData + { + JointNodeData() + : mJointListIdx(-1) + , mNodeIdx(-1) + , mParentNodeIdx(-1) + , mIsValidViewerJoint(false) + , mIsParentValidViewerJoint(false) + , mIsOverrideValid(false) + { + + } + S32 mJointListIdx; + S32 mNodeIdx; + S32 mParentNodeIdx; + glm::mat4 mGltfRestMatrix; + glm::mat4 mViewerRestMatrix; + glm::mat4 mOverrideRestMatrix; + glm::mat4 mGltfMatrix; + glm::mat4 mOverrideMatrix; + std::string mName; + bool mIsValidViewerJoint; + bool mIsParentValidViewerJoint; + bool mIsOverrideValid; + }; + typedef std::map <S32, JointNodeData> joints_data_map_t; + typedef std::map <std::string, S32> joints_name_to_node_map_t; + LLGLTFLoader(std::string filename, S32 lod, LLModelLoader::load_callback_t load_cb, @@ -137,8 +166,7 @@ class LLGLTFLoader : public LLModelLoader std::map<std::string, std::string> &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_viewer_rest_map_t jointRestMatrices, - joint_viewer_parent_map_t jointParentPap); //, + std::vector<LLJointData> 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<LLJointData> mViewerJointData; // vector of vectors because of a posibility of having more than one skin typedef std::vector<LLMeshSkinInfo::matrix_list_t> bind_matrices_t; @@ -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<LLJointData> 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) |