#4147 Joint Overrides #3

Remande skeleton translation from default skeleton to overriden skeleton
master
Andrey Kleshchev 2025-06-13 18:33:49 +03:00 committed by Andrey Kleshchev
parent 8322a9a61e
commit 54660c8931
7 changed files with 223 additions and 71 deletions

View File

@ -14,6 +14,7 @@ set(llappearance_SOURCE_FILES
llavatarjoint.cpp
llavatarjointmesh.cpp
lldriverparam.cpp
lljointdata.h
lllocaltextureobject.cpp
llpolyskeletaldistortion.cpp
llpolymesh.cpp

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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)