diff options
author | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2025-06-04 16:39:51 +0300 |
---|---|---|
committer | Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> | 2025-06-09 20:06:01 +0300 |
commit | b20d10c0cc96cfcd93468b8e31e47ab1977a9555 (patch) | |
tree | c7327409c1d4321b41623b35137037998f3dd1f7 | |
parent | 6924862d2ef1ef6af6a04c013aebeecbb5717bde (diff) |
#4148 Skeleton Translation
-rw-r--r-- | indra/llappearance/llavatarappearance.cpp | 47 | ||||
-rw-r--r-- | indra/llappearance/llavatarappearance.h | 4 | ||||
-rw-r--r-- | indra/newview/gltf/llgltfloader.cpp | 87 | ||||
-rw-r--r-- | indra/newview/gltf/llgltfloader.h | 11 | ||||
-rw-r--r-- | indra/newview/llmodelpreview.cpp | 5 |
5 files changed, 139 insertions, 15 deletions
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; @@ -106,10 +107,13 @@ public: S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; } private: + typedef std::vector<LLAvatarBoneInfo*> 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<LLAvatarBoneInfo*> 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 <skeleton> 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<std::string, std::string> joint_alias_map_t; const joint_alias_map_t& getJointAliases(); - + typedef std::map<std::string, glm::mat4> 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<std::string, std::string> &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<S32>(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + const auto& node = mGLTFAsset.mNodes[joint_node_index]; + + // Find and apply parent transform if it exists + for (size_t i = 0; i < mGLTFAsset.mNodes.size(); ++i) + { + const auto& potential_parent = mGLTFAsset.mNodes[i]; + auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), joint_node_index); + + if (it != potential_parent.mChildren.end()) + { + // Found parent + if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end()) + { + // parent is a joint - recursively combine transform + // assumes that matrix is already valid + return buildGltfRestMatrix(static_cast<S32>(i), gltf_skin) * node.mMatrix; + } + } + } + // Should we return armature or stop earlier? + return node.mMatrix; +} + +// 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<std::string, LLImportMaterial> material_map; - typedef std::map<std::string, LLVector3> joint_pos_map_t; + typedef std::map<std::string, glm::mat4> joint_rest_map_t; LLGLTFLoader(std::string filename, S32 lod, @@ -134,7 +134,8 @@ class LLGLTFLoader : public LLModelLoader JointNameSet & jointsFromNodes, std::map<std::string, std::string> &jointAliasMap, U32 maxJointsPerMesh, - U32 modelLimit); //, + U32 modelLimit, + joint_rest_map_t jointRestMatrices); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -164,6 +165,10 @@ protected: std::vector<gltf_image> mImages; std::vector<gltf_sampler> 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<LLMeshSkinInfo::matrix_list_t> 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) |