summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2025-06-24 17:26:03 +0800
committerErik Kundiman <erik@megapahit.org>2025-06-24 17:26:03 +0800
commitb024b356c75528b4d2688016d49a11e1270fb48d (patch)
tree7913efa6bdcc38510f5e3743887c295a213f77c0
parent7f48cc4f84a6e5b525570b7e1ccfad52187b6420 (diff)
parent1a6e3286110f9e54611715b44c5f8b47d08dc310 (diff)
Merge tag 'Second_Life_Project#1a6e3286-GLTF_Mesh_Import' into gltf_mesh_import
-rw-r--r--indra/llappearance/CMakeLists.txt1
-rw-r--r--indra/llappearance/llavatarappearance.cpp52
-rw-r--r--indra/llappearance/llavatarappearance.h6
-rw-r--r--indra/llappearance/lljointdata.h44
-rw-r--r--indra/llprimitive/llmodel.cpp26
-rw-r--r--indra/newview/gltf/llgltfloader.cpp1855
-rw-r--r--indra/newview/gltf/llgltfloader.h160
-rw-r--r--indra/newview/llfloatermodelpreview.cpp5
-rw-r--r--indra/newview/llmodelpreview.cpp7
-rw-r--r--indra/newview/llskinningutil.cpp3
-rw-r--r--indra/newview/skins/default/xui/en/floater_model_preview.xml16
11 files changed, 1251 insertions, 924 deletions
diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt
index f3f822f61c..b382e21b3c 100644
--- a/indra/llappearance/CMakeLists.txt
+++ b/indra/llappearance/CMakeLists.txt
@@ -14,6 +14,7 @@ set(llappearance_SOURCE_FILES
llavatarjoint.cpp
llavatarjointmesh.cpp
lldriverparam.cpp
+ lljointdata.h
lllocaltextureobject.cpp
llpolyskeletaldistortion.cpp
llpolymesh.cpp
diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp
index 3d66809ed6..13bea1e5ea 100644
--- a/indra/llappearance/llavatarappearance.cpp
+++ b/indra/llappearance/llavatarappearance.cpp
@@ -29,16 +29,17 @@
#include "llavatarappearance.h"
#include "llavatarappearancedefines.h"
#include "llavatarjointmesh.h"
+#include "lljointdata.h"
#include "llstl.h"
#include "lldir.h"
#include "llpolymorph.h"
#include "llpolymesh.h"
#include "llpolyskeletaldistortion.h"
-#include "llstl.h"
#include "lltexglobalcolor.h"
#include "llwearabledata.h"
#include "boost/bind.hpp"
#include "boost/tokenizer.hpp"
+#include "v4math.h"
using namespace LLAvatarAppearanceDefines;
@@ -71,6 +72,7 @@ public:
mChildren.clear();
}
bool parseXml(LLXmlTreeNode* node);
+ glm::mat4 getJointMatrix();
private:
std::string mName;
@@ -106,10 +108,16 @@ public:
S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; }
private:
+ typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t;
+ static void getJointMatricesAndHierarhy(
+ LLAvatarBoneInfo* bone_info,
+ LLJointData& data,
+ const glm::mat4& parent_mat);
+
+private:
S32 mNumBones;
S32 mNumCollisionVolumes;
LLAvatarAppearance::joint_alias_map_t mJointAliasMap;
- typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t;
bone_info_list_t mBoneInfoList;
};
@@ -1623,6 +1631,21 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node)
return true;
}
+
+glm::mat4 LLAvatarBoneInfo::getJointMatrix()
+{
+ glm::mat4 mat(1.0f);
+ // 1. Scaling
+ mat = glm::scale(mat, glm::vec3(mScale[0], mScale[1], mScale[2]));
+ // 2. Rotation (Euler angles rad)
+ mat = glm::rotate(mat, mRot[0], glm::vec3(1, 0, 0));
+ mat = glm::rotate(mat, mRot[1], glm::vec3(0, 1, 0));
+ mat = glm::rotate(mat, mRot[2], glm::vec3(0, 0, 1));
+ // 3. Position
+ mat = glm::translate(mat, glm::vec3(mPos[0], mPos[1], mPos[2]));
+ return mat;
+}
+
//-----------------------------------------------------------------------------
// LLAvatarSkeletonInfo::parseXml()
//-----------------------------------------------------------------------------
@@ -1653,6 +1676,21 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node)
return true;
}
+void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(
+ LLAvatarBoneInfo* bone_info,
+ LLJointData& data,
+ const glm::mat4& parent_mat)
+{
+ data.mName = bone_info->mName;
+ data.mJointMatrix = bone_info->getJointMatrix();
+ data.mRestMatrix = parent_mat * data.mJointMatrix;
+ for (LLAvatarBoneInfo* child_info : bone_info->mChildren)
+ {
+ LLJointData& child_data = data.mChildren.emplace_back();
+ getJointMatricesAndHierarhy(child_info, child_data, data.mRestMatrix);
+ }
+}
+
//Make aliases for joint and push to map.
void LLAvatarAppearance::makeJointAliases(LLAvatarBoneInfo *bone_info)
{
@@ -1714,6 +1752,16 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases
return mJointAliasMap;
}
+void LLAvatarAppearance::getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const
+{
+ glm::mat4 identity(1.f);
+ for (LLAvatarBoneInfo* bone_info : sAvatarSkeletonInfo->mBoneInfoList)
+ {
+ LLJointData& child_data = data.emplace_back();
+ LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(bone_info, child_data, identity);
+ }
+}
+
//-----------------------------------------------------------------------------
// parseXmlSkeletonNode(): parses <skeleton> nodes from XML tree
diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h
index 13e504e639..2748da9a1d 100644
--- a/indra/llappearance/llavatarappearance.h
+++ b/indra/llappearance/llavatarappearance.h
@@ -34,6 +34,7 @@
#include "lltexlayer.h"
#include "llviewervisualparam.h"
#include "llxmltree.h"
+#include "v4math.h"
class LLTexLayerSet;
class LLTexGlobalColor;
@@ -41,6 +42,7 @@ class LLTexGlobalColorInfo;
class LLWearableData;
class LLAvatarBoneInfo;
class LLAvatarSkeletonInfo;
+class LLJointData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// LLAvatarAppearance
@@ -153,7 +155,9 @@ public:
const avatar_joint_list_t& getSkeleton() { return mSkeleton; }
typedef std::map<std::string, std::string> joint_alias_map_t;
const joint_alias_map_t& getJointAliases();
-
+ typedef std::map<std::string, std::string> joint_parent_map_t; // matrix plus parent
+ typedef std::map<std::string, glm::mat4> joint_rest_map_t;
+ void getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const;
protected:
static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree);
diff --git a/indra/llappearance/lljointdata.h b/indra/llappearance/lljointdata.h
new file mode 100644
index 0000000000..549f4af041
--- /dev/null
+++ b/indra/llappearance/lljointdata.h
@@ -0,0 +1,44 @@
+/**
+ * @file lljointdata.h
+ * @brief LLJointData class for holding individual joint data and skeleton
+ *
+ * $LicenseInfo:firstyear=2025&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2025, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLJOINTDATA_H
+#define LL_LLJOINTDATA_H
+
+#include "v4math.h"
+
+// may be just move LLAvatarBoneInfo
+class LLJointData
+{
+public:
+ std::string mName;
+ glm::mat4 mJointMatrix;
+ glm::mat4 mRestMatrix;
+
+ typedef std::vector<LLJointData> bones_t;
+ bones_t mChildren;
+};
+
+#endif //LL_LLJOINTDATA_H
diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp
index 0bc7846023..9ecc2c2a7d 100644
--- a/indra/llprimitive/llmodel.cpp
+++ b/indra/llprimitive/llmodel.cpp
@@ -1717,11 +1717,21 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi
{
ret["joint_names"][i] = mJointNames[i];
+ // For model to work at all there must be a matching bind matrix,
+ // so supply an indentity one if it isn't true
+ // Note: can build an actual bind matrix from joints
+ const LLMatrix4a& inv_bind = mInvBindMatrix.size() > i ? mInvBindMatrix[i] : LLMatrix4a::identity();
+ if (i >= mInvBindMatrix.size())
+ {
+ LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size "
+ << mInvBindMatrix.size() << LL_ENDL;
+ }
+
for (U32 j = 0; j < 4; j++)
{
for (U32 k = 0; k < 4; k++)
{
- ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k];
+ ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k];
}
}
}
@@ -1734,15 +1744,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi
}
}
- if ( include_joints && mAlternateBindMatrix.size() > 0 )
+ // optional 'joint overrides'
+ if (include_joints && mAlternateBindMatrix.size() > 0)
{
for (U32 i = 0; i < mJointNames.size(); ++i)
{
+ // If there is not enough to match mJointNames,
+ // either supply no alternate matrixes at all or supply
+ // replacements
+ const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity();
+ if (i >= mAlternateBindMatrix.size())
+ {
+ LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size "
+ << mAlternateBindMatrix.size() << LL_ENDL;
+ }
for (U32 j = 0; j < 4; j++)
{
for (U32 k = 0; k < 4; k++)
{
- ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k];
+ ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k];
}
}
}
diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp
index 5cdd7f09e0..7a1ebf0610 100644
--- a/indra/newview/gltf/llgltfloader.cpp
+++ b/indra/newview/gltf/llgltfloader.cpp
@@ -52,11 +52,14 @@
#include "llsdserialize.h"
#include "lljoint.h"
+#include "llbase64.h"
+#include "lldir.h"
#include "llmatrix4a.h"
#include <boost/regex.hpp>
#include <boost/algorithm/string/replace.hpp>
+#include <fstream>
static const std::string lod_suffix[LLModel::NUM_LODS] =
{
@@ -76,6 +79,13 @@ static const glm::mat4 coord_system_rotation(
);
+static const glm::mat4 coord_system_rotationxy(
+ 0.f, 1.f, 0.f, 0.f,
+ -1.f, 0.f, 0.f, 0.f,
+ 0.f, 0.f, 1.f, 0.f,
+ 0.f, 0.f, 0.f, 1.f
+);
+
LLGLTFLoader::LLGLTFLoader(std::string filename,
S32 lod,
LLModelLoader::load_callback_t load_cb,
@@ -87,7 +97,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename,
JointNameSet & jointsFromNodes,
std::map<std::string, std::string> &jointAliasMap,
U32 maxJointsPerMesh,
- U32 modelLimit) //,
+ U32 modelLimit,
+ std::vector<LLJointData> viewer_skeleton) //,
//bool preprocess)
: LLModelLoader( filename,
lod,
@@ -99,10 +110,9 @@ LLGLTFLoader::LLGLTFLoader(std::string filename,
jointTransformMap,
jointsFromNodes,
jointAliasMap,
- maxJointsPerMesh ),
- //mPreprocessGLTF(preprocess),
- mMeshesLoaded(false),
- mMaterialsLoaded(false)
+ maxJointsPerMesh )
+ , mGeneratedModelLimit(modelLimit)
+ , mViewerJointData(viewer_skeleton)
{
}
@@ -124,15 +134,109 @@ bool LLGLTFLoader::OpenFile(const std::string &filename)
notifyUnsupportedExtension(false);
- mMeshesLoaded = parseMeshes();
- if (mMeshesLoaded) uploadMeshes();
-
- mMaterialsLoaded = parseMaterials();
- if (mMaterialsLoaded) uploadMaterials();
+ bool meshesLoaded = parseMeshes();
setLoadState(DONE);
- return (mMeshesLoaded);
+ return meshesLoaded;
+}
+
+void LLGLTFLoader::addModelToScene(
+ LLModel* pModel,
+ U32 submodel_limit,
+ const LLMatrix4& transformation,
+ const LLVolumeParams& volume_params,
+ const material_map& mats)
+{
+ U32 volume_faces = pModel->getNumVolumeFaces();
+
+ // Side-steps all manner of issues when splitting models
+ // and matching lower LOD materials to base models
+ //
+ pModel->sortVolumeFacesByMaterialName();
+
+ int submodelID = 0;
+
+ // remove all faces that definitely won't fit into one model and submodel limit
+ U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES;
+ if (face_limit < volume_faces)
+ {
+ pModel->setNumVolumeFaces(face_limit);
+ }
+
+ LLVolume::face_list_t remainder;
+ std::vector<LLModel*> ready_models;
+ LLModel* current_model = pModel;
+ do
+ {
+ current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder);
+
+ volume_faces = static_cast<U32>(remainder.size());
+
+ // Don't add to scene yet because weights and materials aren't ready.
+ // Just save it
+ ready_models.push_back(current_model);
+
+ // If we have left-over volume faces, create another model
+ // to absorb them.
+ if (volume_faces)
+ {
+ LLModel* next = new LLModel(volume_params, 0.f);
+ next->ClearFacesAndMaterials();
+ next->mSubmodelID = ++submodelID;
+ next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod];
+ next->getVolumeFaces() = remainder;
+ next->mNormalizedScale = current_model->mNormalizedScale;
+ next->mNormalizedTranslation = current_model->mNormalizedTranslation;
+ next->mSkinWeights = current_model->mSkinWeights;
+ next->mPosition = current_model->mPosition;
+
+ const LLMeshSkinInfo& current_skin_info = current_model->mSkinInfo;
+ LLMeshSkinInfo& next_skin_info = next->mSkinInfo;
+ next_skin_info.mJointNames = current_skin_info.mJointNames;
+ next_skin_info.mJointNums = current_skin_info.mJointNums;
+ next_skin_info.mBindShapeMatrix = current_skin_info.mBindShapeMatrix;
+ next_skin_info.mInvBindMatrix = current_skin_info.mInvBindMatrix;
+ next_skin_info.mAlternateBindMatrix = current_skin_info.mAlternateBindMatrix;
+ next_skin_info.mPelvisOffset = current_skin_info.mPelvisOffset;
+
+
+ if (current_model->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES)
+ {
+ next->mMaterialList.assign(current_model->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, current_model->mMaterialList.end());
+ current_model->mMaterialList.resize(LL_SCULPT_MESH_MAX_FACES);
+ }
+
+ current_model = next;
+ }
+
+ remainder.clear();
+
+ } while (volume_faces);
+
+ for (auto model : ready_models)
+ {
+ // remove unused/redundant vertices
+ model->remapVolumeFaces();
+
+ mModelList.push_back(model);
+
+ std::map<std::string, LLImportMaterial> materials;
+ for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i)
+ {
+ material_map::const_iterator found = mats.find(model->mMaterialList[i]);
+ if (found != mats.end())
+ {
+ materials[model->mMaterialList[i]] = found->second;
+ }
+ else
+ {
+ materials[model->mMaterialList[i]] = LLImportMaterial();
+ }
+ }
+ mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials));
+ stretch_extents(model, transformation);
+ }
}
bool LLGLTFLoader::parseMeshes()
@@ -151,103 +255,179 @@ bool LLGLTFLoader::parseMeshes()
node.makeMatrixValid();
}
+ if (mGLTFAsset.mSkins.size() > 0)
+ {
+ checkForXYrotation(mGLTFAsset.mSkins[0]);
+ }
+
// Populate the joints from skins first.
// There's not many skins - and you can pretty easily iterate through the nodes from that.
- for (auto& skin : mGLTFAsset.mSkins)
+ for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++)
{
- populateJointFromSkin(skin);
+ populateJointFromSkin(i);
}
// Track how many times each mesh name has been used
std::map<std::string, S32> mesh_name_counts;
+ U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0;
- // Process each node
- for (auto& node : mGLTFAsset.mNodes)
+ // Check if we have scenes defined
+ if (!mGLTFAsset.mScenes.empty())
{
- LLMatrix4 transformation;
- material_map mats;
- auto meshidx = node.mMesh;
+ // Process the default scene (or first scene if no default)
+ S32 scene_idx = mGLTFAsset.mScene >= 0 ? mGLTFAsset.mScene : 0;
- if (meshidx >= 0)
+ if (scene_idx < mGLTFAsset.mScenes.size())
{
- if (mGLTFAsset.mMeshes.size() > meshidx)
- {
- LLModel* pModel = new LLModel(volume_params, 0.f);
- auto mesh = mGLTFAsset.mMeshes[meshidx];
+ const LL::GLTF::Scene& scene = mGLTFAsset.mScenes[scene_idx];
+
+ LL_INFOS("GLTF_IMPORT") << "Processing scene " << scene_idx << " with " << scene.mNodes.size() << " root nodes" << LL_ENDL;
- // Get base mesh name and track usage
- std::string base_name = mesh.mName;
- if (base_name.empty())
+ // Process all root nodes defined in the scene
+ for (S32 root_idx : scene.mNodes)
+ {
+ if (root_idx >= 0 && root_idx < static_cast<S32>(mGLTFAsset.mNodes.size()))
{
- base_name = "mesh_" + std::to_string(meshidx);
+ processNodeHierarchy(root_idx, mesh_name_counts, submodel_limit, volume_params);
}
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL;
- S32 instance_count = mesh_name_counts[base_name]++;
+ LLSD args;
+ args["Message"] = "NoScenesFound";
+ mWarningsArray.append(args);
+ return false;
+ }
- if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) &&
- (LLModel::NO_ERRORS == pModel->getStatus()) &&
- validate_model(pModel))
- {
- mModelList.push_back(pModel);
+ // Check total model count against limit
+ U32 total_models = static_cast<U32>(mModelList.size());
+ if (total_models > mGeneratedModelLimit)
+ {
+ LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models
+ << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL;
- mTransform.setIdentity();
- transformation = mTransform;
+ LLSD args;
+ args["Message"] = "TooManyMeshParts";
+ args["PART_COUNT"] = static_cast<S32>(total_models);
+ args["LIMIT"] = static_cast<S32>(mGeneratedModelLimit);
+ mWarningsArray.append(args);
+ return false;
+ }
- // adjust the transformation to compensate for mesh normalization
- LLVector3 mesh_scale_vector;
- LLVector3 mesh_translation_vector;
- pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector);
+ return true;
+}
- LLMatrix4 mesh_translation;
- mesh_translation.setTranslation(mesh_translation_vector);
- mesh_translation *= transformation;
- transformation = mesh_translation;
+void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params)
+{
+ if (node_idx < 0 || node_idx >= static_cast<S32>(mGLTFAsset.mNodes.size()))
+ return;
- LLMatrix4 mesh_scale;
- mesh_scale.initScale(mesh_scale_vector);
- mesh_scale *= transformation;
- transformation = mesh_scale;
+ auto& node = mGLTFAsset.mNodes[node_idx];
- if (node.mSkin >= 0)
- {
- // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh
- // into the coordinate space of the joints.
- // In GLTF, this matrix is omitted, and it is assumed that this transform is either
- // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices.
- //
- // TODO: There appears to be missing rotation when joints rotate the model
- // or inverted bind matrices are missing inherited rotation
- // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly
- // prior to skinning)
-
- pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale);
- LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL;
- }
+ LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")"
+ << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no")
+ << " - children: " << node.mChildren.size() << LL_ENDL;
- if (transformation.determinant() < 0)
- { // negative scales are not supported
- LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: "
- << pModel->mLabel << LL_ENDL;
- LLSD args;
- args["Message"] = "NegativeScaleNormTrans";
- args["LABEL"] = pModel->mLabel;
- mWarningsArray.append(args);
- }
+ // Process this node's mesh if it has one
+ if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size())
+ {
+ LLMatrix4 transformation;
+ material_map mats;
- mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats));
- stretch_extents(pModel, transformation);
- }
- else
- {
- setLoadState(ERROR_MODEL + pModel->getStatus());
- delete pModel;
- return false;
- }
+ LLModel* pModel = new LLModel(volume_params, 0.f);
+ auto& mesh = mGLTFAsset.mMeshes[node.mMesh];
+
+ // Get base mesh name and track usage
+ std::string base_name = mesh.mName;
+ if (base_name.empty())
+ {
+ base_name = "mesh_" + std::to_string(node.mMesh);
+ }
+
+ S32 instance_count = mesh_name_counts[base_name]++;
+
+ if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) &&
+ (LLModel::NO_ERRORS == pModel->getStatus()) &&
+ validate_model(pModel))
+ {
+ mTransform.setIdentity();
+ transformation = mTransform;
+
+ // adjust the transformation to compensate for mesh normalization
+ LLVector3 mesh_scale_vector;
+ LLVector3 mesh_translation_vector;
+ pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector);
+
+ LLMatrix4 mesh_translation;
+ mesh_translation.setTranslation(mesh_translation_vector);
+ mesh_translation *= transformation;
+ transformation = mesh_translation;
+
+ LLMatrix4 mesh_scale;
+ mesh_scale.initScale(mesh_scale_vector);
+ mesh_scale *= transformation;
+ transformation = mesh_scale;
+
+ if (node.mSkin >= 0)
+ {
+ // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh
+ // into the coordinate space of the joints.
+ // In GLTF, this matrix is omitted, and it is assumed that this transform is either
+ // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices.
+ //
+ // TODO: There appears to be missing rotation when joints rotate the model
+ // or inverted bind matrices are missing inherited rotation
+ // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly
+ // prior to skinning)
+
+ pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale);
+ LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL;
}
+
+ if (transformation.determinant() < 0)
+ { // negative scales are not supported
+ LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: "
+ << pModel->mLabel << LL_ENDL;
+ LLSD args;
+ args["Message"] = "NegativeScaleNormTrans";
+ args["LABEL"] = pModel->mLabel;
+ mWarningsArray.append(args);
+ }
+
+ addModelToScene(pModel, submodel_limit, transformation, volume_params, mats);
+ mats.clear();
+ }
+ else
+ {
+ setLoadState(ERROR_MODEL + pModel->getStatus());
+ delete pModel;
+ return;
}
}
+ else if (node.mMesh >= 0)
+ {
+ // Log invalid mesh reference
+ LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " (" << node.mName
+ << ") references invalid mesh " << node.mMesh
+ << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL;
- return true;
+ LLSD args;
+ args["Message"] = "InvalidMeshReference";
+ args["NODE_NAME"] = node.mName;
+ args["MESH_INDEX"] = node.mMesh;
+ args["TOTAL_MESHES"] = static_cast<S32>(mGLTFAsset.mMeshes.size());
+ mWarningsArray.append(args);
+ }
+
+ // Process all children recursively
+ for (S32 child_idx : node.mChildren)
+ {
+ processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params);
+ }
}
void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const
@@ -293,6 +473,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
base_name = "mesh_" + std::to_string(mesh_index);
}
+ LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL;
+
if (instance_count > 0)
{
pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count);
@@ -312,7 +494,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform);
// Combine transforms: coordinate rotation applied to hierarchy transform
- const glm::mat4 final_transform = coord_system_rotation * hierarchy_transform;
+ glm::mat4 final_transform = coord_system_rotation * hierarchy_transform;
+ if (mApplyXYRotation)
+ {
+ final_transform = coord_system_rotationxy * final_transform;
+ }
// Check if we have a negative scale (flipped coordinate system)
bool hasNegativeScale = glm::determinant(final_transform) < 0.0f;
@@ -322,13 +508,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
// Mark unsuported joints with '-1' so that they won't get added into weights
// GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones.
- std::vector<S32> gltf_joint_index_use_count;
+ std::vector<S32> gltf_joint_index_use;
if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx)
{
LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx];
size_t jointCnt = gltf_skin.mJoints.size();
- gltf_joint_index_use_count.resize(jointCnt);
+ gltf_joint_index_use.resize(jointCnt);
S32 replacement_index = 0;
for (size_t i = 0; i < jointCnt; ++i)
@@ -340,317 +526,430 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
std::string legal_name(jointNode.mName);
if (mJointMap.find(legal_name) == mJointMap.end())
{
- gltf_joint_index_use_count[i] = -1; // mark as unsupported
+ // This might need to hold a substitute index
+ gltf_joint_index_use[i] = -1; // mark as unsupported
}
}
}
- auto prims = mesh.mPrimitives;
- for (auto prim : prims)
+ for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx)
{
- // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit.
- if (prim.getVertexCount() < USHRT_MAX)
- {
- // So primitives already have all of the data we need for a given face in SL land.
- // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call
- // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07
- LLVolumeFace face;
- std::vector<GLTFVertex> vertices;
- std::vector<U16> indices;
+ const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx];
- LLImportMaterial impMat;
- impMat.mDiffuseColor = LLColor4::white; // Default color
+ // So primitives already have all of the data we need for a given face in SL land.
+ // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call
+ // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07
+ LLVolumeFace face;
+ std::vector<GLTFVertex> vertices;
- // Process material if available
- if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size())
+ LLImportMaterial impMat;
+ impMat.mDiffuseColor = LLColor4::white; // Default color
+
+ // Process material if available
+ if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size())
+ {
+ LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial];
+
+ // Set diffuse color from base color factor
+ impMat.mDiffuseColor = LLColor4(
+ material->mPbrMetallicRoughness.mBaseColorFactor[0],
+ material->mPbrMetallicRoughness.mBaseColorFactor[1],
+ material->mPbrMetallicRoughness.mBaseColorFactor[2],
+ material->mPbrMetallicRoughness.mBaseColorFactor[3]
+ );
+
+ // Process base color texture if it exists
+ if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0)
{
- LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial];
-
- // Set diffuse color from base color factor
- impMat.mDiffuseColor = LLColor4(
- material->mPbrMetallicRoughness.mBaseColorFactor[0],
- material->mPbrMetallicRoughness.mBaseColorFactor[1],
- material->mPbrMetallicRoughness.mBaseColorFactor[2],
- material->mPbrMetallicRoughness.mBaseColorFactor[3]
- );
-
- // Process base color texture if it exists
- if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0)
+ S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex;
+ if (texIndex < mGLTFAsset.mTextures.size())
{
- S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex;
- if (texIndex < mGLTFAsset.mTextures.size())
+ S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource;
+ if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size())
{
- S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource;
- if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size())
+ LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex];
+
+ // Use URI as texture file name
+ if (!image.mUri.empty())
{
- LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex];
+ // URI might be a remote URL or a local path
+ std::string filename = image.mUri;
- // Use URI as texture file name
- if (!image.mUri.empty())
+ // Extract just the filename from the URI
+ size_t pos = filename.find_last_of("/\\");
+ if (pos != std::string::npos)
{
- // URI might be a remote URL or a local path
- std::string filename = image.mUri;
-
- // Extract just the filename from the URI
- size_t pos = filename.find_last_of("/\\");
- if (pos != std::string::npos)
- {
- filename = filename.substr(pos + 1);
- }
-
- // Store the texture filename
- impMat.mDiffuseMapFilename = filename;
- impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName;
-
- LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename << LL_ENDL;
-
- // If the image has a texture loaded already, use it
- if (image.mTexture.notNull())
- {
- impMat.setDiffuseMap(image.mTexture->getID());
- LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL;
- }
- else
- {
- // Let the model preview know we need to load this texture
- mNumOfFetchingTextures++;
- LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL;
- }
+ filename = filename.substr(pos + 1);
}
- else if (image.mTexture.notNull())
+
+ // Store the texture filename
+ impMat.mDiffuseMapFilename = filename;
+ impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName;
+
+ LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename
+ << " for material: " << material->mName << LL_ENDL;
+
+ LLSD args;
+ args["Message"] = "TextureFound";
+ args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename;
+ args["MATERIAL_NAME"] = material->mName;
+ mWarningsArray.append(args);
+
+ // If the image has a texture loaded already, use it
+ if (image.mTexture.notNull())
{
- // No URI but we have a texture, use it directly
impMat.setDiffuseMap(image.mTexture->getID());
- LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL;
+ LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL;
}
- else if (image.mBufferView >= 0)
+ else
{
- // For embedded textures (no URI but has buffer data)
- // Create a pseudo filename for the embedded texture
- std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png";
- impMat.mDiffuseMapFilename = pseudo_filename;
- impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName;
-
- // Mark for loading
- mNumOfFetchingTextures++;
- LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL;
+ // Texture will be loaded later through the callback system
+ LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL;
+ }
+ }
+ else if (image.mTexture.notNull())
+ {
+ // No URI but we have a texture, use it directly
+ impMat.setDiffuseMap(image.mTexture->getID());
+ LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL;
+ }
+ else if (image.mBufferView >= 0)
+ {
+ // For embedded textures (no URI but has buffer data)
+ std::string temp_filename = extractTextureToTempFile(texIndex, "base_color");
+ if (!temp_filename.empty())
+ {
+ impMat.mDiffuseMapFilename = temp_filename;
+ impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName;
}
}
}
}
}
+ }
- // Apply the global scale and center offset to all vertices
- for (U32 i = 0; i < prim.getVertexCount(); i++)
- {
- // Use pre-computed final_transform
- glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f);
- glm::vec4 transformed_pos = final_transform * pos;
+ if (prim.getIndexCount() % 3 != 0)
+ {
+ LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx
+ << ": Invalid index count " << prim.getIndexCount()
+ << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL;
+
+ LLSD args;
+ args["Message"] = "InvalidGeometryNonTriangulated";
+ args["MESH_NAME"] = mesh.mName;
+ args["PRIMITIVE_INDEX"] = static_cast<S32>(prim_idx);
+ args["INDEX_COUNT"] = static_cast<S32>(prim.getIndexCount());
+ mWarningsArray.append(args);
+ return false; // Skip this primitive
+ }
+
+ // Apply the global scale and center offset to all vertices
+ for (U32 i = 0; i < prim.getVertexCount(); i++)
+ {
+ // Use pre-computed final_transform
+ glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f);
+ glm::vec4 transformed_pos = final_transform * pos;
- GLTFVertex vert;
- vert.position = glm::vec3(transformed_pos);
+ GLTFVertex vert;
+ vert.position = glm::vec3(transformed_pos);
+ if (!prim.mNormals.empty())
+ {
// Use pre-computed normal_transform
glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]);
vert.normal = glm::normalize(normal_transform * normal_vec);
-
- vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]);
-
- if (skinIdx >= 0)
- {
- vert.weights = glm::vec4(prim.mWeights[i]);
-
- auto accessorIdx = prim.mAttributes["JOINTS_0"];
- LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE;
- if (accessorIdx >= 0)
- {
- auto accessor = mGLTFAsset.mAccessors[accessorIdx];
- componentType = accessor.mComponentType;
- }
-
- // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short.
- // Detect and unpack accordingly.
- if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE)
- {
- auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF));
- vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w);
- }
- else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT)
- {
- vert.joints = glm::unpackUint4x16(prim.mJoints[i]);
- }
- else
- {
- vert.joints = glm::zero<glm::u16vec4>();
- vert.weights = glm::zero<glm::vec4>();
- }
- }
- vertices.push_back(vert);
}
-
- if (prim.getIndexCount() % 3 != 0)
+ else
{
- LL_WARNS("GLTF_IMPORT") << "Invalid primitive: index count " << prim.getIndexCount()
- << " is not divisible by 3. GLTF files must contain triangulated geometry." << LL_ENDL;
- LLSD args;
- args["Message"] = "InvalidGeometryNonTriangulated";
- mWarningsArray.append(args);
- continue; // Skip this primitive
+ // Use default normal (pointing up in model space)
+ vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f));
+ LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL;
}
- // When processing indices, flip winding order if needed
- for (U32 i = 0; i < prim.getIndexCount(); i += 3)
+ vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]);
+
+ if (skinIdx >= 0)
{
- if (hasNegativeScale)
+ vert.weights = glm::vec4(prim.mWeights[i]);
+
+ auto accessorIdx = prim.mAttributes.at("JOINTS_0");
+ LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE;
+ if (accessorIdx >= 0)
{
- // Flip winding order for negative scale
- indices.push_back(prim.mIndexArray[i]);
- indices.push_back(prim.mIndexArray[i + 2]); // Swap these two
- indices.push_back(prim.mIndexArray[i + 1]);
+ auto accessor = mGLTFAsset.mAccessors[accessorIdx];
+ componentType = accessor.mComponentType;
+ }
+
+ // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short.
+ // Detect and unpack accordingly.
+ if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE)
+ {
+ auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF));
+ vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w);
+ }
+ else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT)
+ {
+ vert.joints = glm::unpackUint4x16(prim.mJoints[i]);
}
else
{
- indices.push_back(prim.mIndexArray[i]);
- indices.push_back(prim.mIndexArray[i + 1]);
- indices.push_back(prim.mIndexArray[i + 2]);
+ vert.joints = glm::zero<glm::u16vec4>();
+ vert.weights = glm::zero<glm::vec4>();
}
}
+ vertices.push_back(vert);
+ }
- // Check for empty vertex array before processing
- if (vertices.empty())
+ // Check for empty vertex array before processing
+ if (vertices.empty())
+ {
+ LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive " << prim_idx << " in model " << mesh.mName << LL_ENDL;
+ LLSD args;
+ args["Message"] = "EmptyVertexArray";
+ args["MESH_NAME"] = mesh.mName;
+ args["PRIMITIVE_INDEX"] = static_cast<S32>(prim_idx);
+ args["INDEX_COUNT"] = static_cast<S32>(prim.getIndexCount());
+ mWarningsArray.append(args);
+ return false; // Skip this primitive
+ }
+
+ std::vector<LLVolumeFace::VertexData> faceVertices;
+ glm::vec3 min = glm::vec3(FLT_MAX);
+ glm::vec3 max = glm::vec3(-FLT_MAX);
+
+ for (U32 i = 0; i < vertices.size(); i++)
+ {
+ LLVolumeFace::VertexData vert;
+
+ // Update min/max bounds
+ if (i == 0)
+ {
+ min = max = vertices[i].position;
+ }
+ else
{
- LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL;
- continue; // Skip this primitive
+ min.x = std::min(min.x, vertices[i].position.x);
+ min.y = std::min(min.y, vertices[i].position.y);
+ min.z = std::min(min.z, vertices[i].position.z);
+ max.x = std::max(max.x, vertices[i].position.x);
+ max.y = std::max(max.y, vertices[i].position.y);
+ max.z = std::max(max.z, vertices[i].position.z);
}
- std::vector<LLVolumeFace::VertexData> faceVertices;
- glm::vec3 min = glm::vec3(FLT_MAX);
- glm::vec3 max = glm::vec3(-FLT_MAX);
+ LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z);
+ LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z);
+ vert.setPosition(position);
+ vert.setNormal(normal);
+ vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y);
+ faceVertices.push_back(vert);
- for (U32 i = 0; i < vertices.size(); i++)
+ if (skinIdx >= 0)
{
- LLVolumeFace::VertexData vert;
-
- // Update min/max bounds
- if (i == 0)
+ // create list of weights that influence this vertex
+ LLModel::weight_list weight_list;
+
+ // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count)
+ // don't reindex them yet, more indexes will be removed
+ // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be
+ // 'empty' ones
+ if (gltf_joint_index_use[vertices[i].joints.x] >= 0
+ && vertices[i].weights.x > 0.f)
{
- min = max = vertices[i].position;
+ weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x));
+ gltf_joint_index_use[vertices[i].joints.x]++;
}
- else
+ if (gltf_joint_index_use[vertices[i].joints.y] >= 0
+ && vertices[i].weights.y > 0.f)
{
- min.x = std::min(min.x, vertices[i].position.x);
- min.y = std::min(min.y, vertices[i].position.y);
- min.z = std::min(min.z, vertices[i].position.z);
- max.x = std::max(max.x, vertices[i].position.x);
- max.y = std::max(max.y, vertices[i].position.y);
- max.z = std::max(max.z, vertices[i].position.z);
+ weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y));
+ gltf_joint_index_use[vertices[i].joints.y]++;
}
-
- LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z);
- LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z);
- vert.setPosition(position);
- vert.setNormal(normal);
- vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y);
- faceVertices.push_back(vert);
-
- if (skinIdx >= 0)
+ if (gltf_joint_index_use[vertices[i].joints.z] >= 0
+ && vertices[i].weights.z > 0.f)
{
- // create list of weights that influence this vertex
- LLModel::weight_list weight_list;
-
- // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count)
- // don't reindex them yet, more indexes will be removed
- // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be
- // 'empty' ones
- if (gltf_joint_index_use_count[vertices[i].joints.x] >= 0
- && vertices[i].weights.x > 0.f)
- {
- weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x));
- gltf_joint_index_use_count[vertices[i].joints.x]++;
- }
- if (gltf_joint_index_use_count[vertices[i].joints.y] >= 0
- && vertices[i].weights.y > 0.f)
- {
- weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y));
- gltf_joint_index_use_count[vertices[i].joints.y]++;
- }
- if (gltf_joint_index_use_count[vertices[i].joints.z] >= 0
- && vertices[i].weights.z > 0.f)
- {
- weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z));
- gltf_joint_index_use_count[vertices[i].joints.z]++;
- }
- if (gltf_joint_index_use_count[vertices[i].joints.w] >= 0
- && vertices[i].weights.w > 0.f)
- {
- weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w));
- gltf_joint_index_use_count[vertices[i].joints.w]++;
- }
+ weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z));
+ gltf_joint_index_use[vertices[i].joints.z]++;
+ }
+ if (gltf_joint_index_use[vertices[i].joints.w] >= 0
+ && vertices[i].weights.w > 0.f)
+ {
+ weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w));
+ gltf_joint_index_use[vertices[i].joints.w]++;
+ }
- std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater());
+ std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater());
- std::vector<LLModel::JointWeight> wght;
- F32 total = 0.f;
+ std::vector<LLModel::JointWeight> wght;
+ F32 total = 0.f;
- for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j)
- {
- // take up to 4 most significant weights
- // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex.
- wght.push_back(weight_list[j]);
- total += weight_list[j].mWeight;
- }
+ for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j)
+ {
+ // take up to 4 most significant weights
+ // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex.
+ wght.push_back(weight_list[j]);
+ total += weight_list[j].mWeight;
+ }
- if (total != 0.f)
- {
- F32 scale = 1.f / total;
- if (scale != 1.f)
- { // normalize weights
- for (U32 j = 0; j < wght.size(); ++j)
- {
- wght[j].mWeight *= scale;
- }
+ if (total != 0.f)
+ {
+ F32 scale = 1.f / total;
+ if (scale != 1.f)
+ { // normalize weights
+ for (U32 j = 0; j < wght.size(); ++j)
+ {
+ wght[j].mWeight *= scale;
}
}
+ }
- if (wght.size() > 0)
- {
- pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght;
- }
+ if (wght.size() > 0)
+ {
+ pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght;
}
}
+ }
- face.fillFromLegacyData(faceVertices, indices);
- face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0);
- face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0);
+ // Create a unique material name for this primitive
+ std::string materialName;
+ if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size())
+ {
+ LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial];
+ materialName = material->mName;
- pModel->getVolumeFaces().push_back(face);
+ if (materialName.empty())
+ {
+ materialName = "mat" + std::to_string(prim.mMaterial);
+ }
+ }
+ else
+ {
+ materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1);
+ }
+ mats[materialName] = impMat;
- // Create a unique material name for this primitive
- std::string materialName;
- if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size())
+ // Indices handling
+ if (faceVertices.size() >= USHRT_MAX)
+ {
+ // Will have to remap 32 bit indices into 16 bit indices
+ // For the sake of simplicity build vector of 32 bit indices first
+ std::vector<U32> indices_32;
+ for (U32 i = 0; i < prim.getIndexCount(); i += 3)
{
- LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial];
- materialName = material->mName;
+ // When processing indices, flip winding order if needed
+ if (hasNegativeScale)
+ {
+ // Flip winding order for negative scale
+ indices_32.push_back(prim.mIndexArray[i]);
+ indices_32.push_back(prim.mIndexArray[i + 2]); // Swap these two
+ indices_32.push_back(prim.mIndexArray[i + 1]);
+ }
+ else
+ {
+ indices_32.push_back(prim.mIndexArray[i]);
+ indices_32.push_back(prim.mIndexArray[i + 1]);
+ indices_32.push_back(prim.mIndexArray[i + 2]);
+ }
+ }
- if (materialName.empty())
+ // remap 32 bit into multiple 16 bit ones
+ std::vector<U16> indices_16;
+ std::vector<S64> vertices_remap; // should it be a point map?
+ vertices_remap.resize(faceVertices.size(), -1);
+ std::vector<LLVolumeFace::VertexData> face_verts;
+ min = glm::vec3(FLT_MAX);
+ max = glm::vec3(-FLT_MAX);
+ for (size_t idx = 0; idx < indices_32.size(); idx++)
+ {
+ size_t vert_index = indices_32[idx];
+ if (vertices_remap[vert_index] == -1)
{
- materialName = "mat" + std::to_string(prim.mMaterial);
+ // First encounter, add it
+ size_t new_vert_idx = face_verts.size();
+ vertices_remap[vert_index] = (S64)new_vert_idx;
+ face_verts.push_back(faceVertices[vert_index]);
+ vert_index = new_vert_idx;
+
+ // Update min/max bounds
+ const LLVector4a& vec = face_verts[new_vert_idx].getPosition();
+ if (new_vert_idx == 0)
+ {
+ min.x = vec[0];
+ min.y = vec[1];
+ min.z = vec[2];
+ max = min;
+ }
+ else
+ {
+ min.x = std::min(min.x, vec[0]);
+ min.y = std::min(min.y, vec[1]);
+ min.z = std::min(min.z, vec[2]);
+ max.x = std::max(max.x, vec[0]);
+ max.y = std::max(max.y, vec[1]);
+ max.z = std::max(max.z, vec[2]);
+ }
+ }
+ else
+ {
+ // already in vector, get position
+ vert_index = (size_t)vertices_remap[vert_index];
+ }
+ indices_16.push_back((U16)vert_index);
+
+ if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532)
+ {
+ LLVolumeFace face;
+ face.fillFromLegacyData(face_verts, indices_16);
+ face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0);
+ face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0);
+ pModel->getVolumeFaces().push_back(face);
+ pModel->getMaterialList().push_back(materialName);
+
+ std::fill(vertices_remap.begin(), vertices_remap.end(), -1);
+ indices_16.clear();
+ face_verts.clear();
+
+ min = glm::vec3(FLT_MAX);
+ max = glm::vec3(-FLT_MAX);
}
}
- else
+ if (indices_16.size() > 0 && face_verts.size() > 0)
{
- materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1);
+ LLVolumeFace face;
+ face.fillFromLegacyData(face_verts, indices_16);
+ face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0);
+ face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0);
+ pModel->getVolumeFaces().push_back(face);
+ pModel->getMaterialList().push_back(materialName);
+ }
+ }
+ else
+ {
+ // can use indices directly
+ std::vector<U16> indices;
+ for (U32 i = 0; i < prim.getIndexCount(); i += 3)
+ {
+ // When processing indices, flip winding order if needed
+ if (hasNegativeScale)
+ {
+ // Flip winding order for negative scale
+ indices.push_back(prim.mIndexArray[i]);
+ indices.push_back(prim.mIndexArray[i + 2]); // Swap these two
+ indices.push_back(prim.mIndexArray[i + 1]);
+ }
+ else
+ {
+ indices.push_back(prim.mIndexArray[i]);
+ indices.push_back(prim.mIndexArray[i + 1]);
+ indices.push_back(prim.mIndexArray[i + 2]);
+ }
}
+ face.fillFromLegacyData(faceVertices, indices);
+ face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0);
+ face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0);
+
+ pModel->getVolumeFaces().push_back(face);
pModel->getMaterialList().push_back(materialName);
- mats[materialName] = impMat;
- }
- else {
- LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL;
- LLSD args;
- args["Message"] = "ParsingErrorBadElement";
- mWarningsArray.append(args);
- return false;
}
}
@@ -662,27 +961,21 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
{
LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx];
LLMeshSkinInfo& skin_info = pModel->mSkinInfo;
-
- size_t jointCnt = gltf_skin.mJoints.size();
- if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size())
- {
- LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL;
- LLSD args;
- args["Message"] = "InvBindCountMismatch";
- mWarningsArray.append(args);
- }
+ S32 valid_joints_count = mValidJointsCount[skinIdx];
std::vector<S32> gltfindex_to_joitindex_map;
+ size_t jointCnt = gltf_skin.mJoints.size();
gltfindex_to_joitindex_map.resize(jointCnt);
S32 replacement_index = 0;
+ S32 stripped_valid_joints = 0;
for (size_t i = 0; i < jointCnt; ++i)
{
// Process joint name and idnex
S32 joint = gltf_skin.mJoints[i];
- if (gltf_joint_index_use_count[i] <= 0)
+ if (gltf_joint_index_use[i] < 0)
{
- // Unused (0) or unsupported (-1) joint, drop it
+ // unsupported (-1) joint, drop it
continue;
}
LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint];
@@ -694,52 +987,47 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
}
else
{
- llassert(false); // should have been stopped by gltf_joint_index_use_count[i] == -1
+ llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1
continue;
}
- gltfindex_to_joitindex_map[i] = replacement_index++;
-
- skin_info.mJointNames.push_back(legal_name);
- skin_info.mJointNums.push_back(-1);
- if (i < gltf_skin.mInverseBindMatricesData.size())
+ if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT
+ && gltf_joint_index_use[i] == 0
+ && legal_name != "mPelvis")
{
- // Use pre-computed coord_system_rotation instead of recreating it
- LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i];
+ // Unused (0) joint
+ // It's perfectly valid to have more joints than is in use
+ // Ex: sandals that make your legs digitigrade despite not skining to
+ // knees or the like.
+ // But if model is over limid, drop extras sans pelvis.
+ // Keeping 'pelvis' is a workaround to keep preview whole.
+ // Todo: consider improving this, either take as much as possible or
+ // ensure common parents/roots are included
+ continue;
+ }
- glm::mat4 original_bind_matrix = glm::inverse(gltf_mat);
- glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix;
- glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original);
+ gltfindex_to_joitindex_map[i] = replacement_index++;
- LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix));
- skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform));
+ skin_info.mJointNames.push_back(legal_name);
+ skin_info.mJointNums.push_back(-1);
- LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL;
+ // In scope of same skin multiple meshes reuse same bind matrices
+ skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[skinIdx][i]);
- // For alternate bind matrix, use the ORIGINAL joint transform (before rotation)
- // Get the original joint node and use its matrix directly
- S32 joint = gltf_skin.mJoints[i];
- LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint];
- glm::mat4 joint_mat = jointNode.mMatrix;
- S32 root_joint = findValidRootJoint(joint, gltf_skin); // skeleton can have multiple real roots
- if (root_joint == joint)
- {
- // This is very likely incomplete in some way.
- // Root shouldn't be the only one to need full coordinate fix
- joint_mat = coord_system_rotation * joint_mat;
- }
- LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat));
+ skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]);
+ }
- LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL;
- skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform));
- }
- else
- {
- // For gltf mInverseBindMatrices are optional, but not for viewer
- // todo: get a model that triggers this
- skin_info.mInvBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); // might need to be an 'identity'
- skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(mJointList[legal_name]));
- }
+ if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT)
+ {
+ LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel
+ << " Count: " << (S32)skin_info.mInvBindMatrix.size()
+ << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL;
+ LLSD args;
+ args["Message"] = "ModelTooManyJoint";
+ args["MODEL_NAME"] = pModel->mLabel;
+ args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size();
+ args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT;
+ mWarningsArray.append(args);
}
// Remap indices for pModel->mSkinWeights
@@ -755,62 +1043,195 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
return true;
}
-void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin)
+void LLGLTFLoader::populateJointFromSkin(S32 skin_idx)
{
- LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL;
+ const LL::GLTF::Skin& skin = mGLTFAsset.mSkins[skin_idx];
+
+ LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing skin " << skin_idx << " with " << skin.mJoints.size() << " joints" << LL_ENDL;
+
+ if (skin.mInverseBindMatrices > 0 && skin.mJoints.size() != skin.mInverseBindMatricesData.size())
+ {
+ LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL;
+ LLSD args;
+ args["Message"] = "InvBindCountMismatch";
+ mWarningsArray.append(args);
+ }
+
+ S32 joint_count = (S32)skin.mJoints.size();
+ S32 inverse_count = (S32)skin.mInverseBindMatricesData.size();
+ if (mInverseBindMatrices.size() <= skin_idx)
+ {
+ mInverseBindMatrices.resize(skin_idx + 1);
+ mAlternateBindMatrices.resize(skin_idx + 1);
+ mValidJointsCount.resize(skin_idx + 1, 0);
+ }
+
+ // fill up joints related data
+ joints_data_map_t joints_data;
+ joints_name_to_node_map_t names_to_nodes;
+ for (S32 i = 0; i < joint_count; i++)
+ {
+ S32 joint = skin.mJoints[i];
+ LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint];
+ JointNodeData& data = joints_data[joint];
+ data.mNodeIdx = joint;
+ data.mJointListIdx = i;
+ data.mGltfRestMatrix = buildGltfRestMatrix(joint, skin);
+ data.mGltfMatrix = jointNode.mMatrix;
+ data.mOverrideMatrix = glm::mat4(1.f);
+
+ if (mJointMap.find(jointNode.mName) != mJointMap.end())
+ {
+ data.mName = mJointMap[jointNode.mName];
+ data.mIsValidViewerJoint = true;
+ mValidJointsCount[skin_idx]++;
+ }
+ else
+ {
+ data.mName = jointNode.mName;
+ data.mIsValidViewerJoint = false;
+ }
+ names_to_nodes[data.mName] = joint;
+
+ for (S32 child : jointNode.mChildren)
+ {
+ JointNodeData& child_data = joints_data[child];
+ child_data.mParentNodeIdx = joint;
+ child_data.mIsParentValidViewerJoint = data.mIsValidViewerJoint;
+ }
+ }
+
+ if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT)
+ {
+ LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints"
+ << " Count: " << mValidJointsCount[skin_idx]
+ << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL;
- for (auto joint : skin.mJoints)
+ LLSD args;
+ args["Message"] = "SkinJointsOverLimit";
+ args["SKIN_INDEX"] = (S32)skin_idx;
+ args["JOINT_COUNT"] = mValidJointsCount[skin_idx];
+ args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT;
+ mWarningsArray.append(args);
+ }
+
+ // Go over viewer joints and build overrides
+ glm::mat4 ident(1.0);
+ for (auto &viewer_data : mViewerJointData)
{
- auto jointNode = mGLTFAsset.mNodes[joint];
+ buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident);
+ }
+ for (S32 i = 0; i < joint_count; i++)
+ {
+ S32 joint = skin.mJoints[i];
+ LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint];
std::string legal_name(jointNode.mName);
+ bool legal_joint = false;
if (mJointMap.find(legal_name) != mJointMap.end())
{
legal_name = mJointMap[legal_name];
+ legal_joint = true;
+ }
+
+ // Compute bind matrices
+
+ if (!legal_joint)
+ {
+ // Add placeholder to not break index.
+ // Not going to be used by viewer, will be stripped from skin_info.
+ LLMatrix4 gltf_transform;
+ gltf_transform.setIdentity();
+ mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform));
+ }
+ else if (inverse_count > i)
+ {
+ // Transalte existing bind matrix to viewer's skeleton
+ // todo: probably should be 'to viewer's overriden skeleton'
+ glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]);
+ glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix;
+ glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name);
+ glm::mat4 tranlated_original = skeleton_transform * rotated_original;
+ glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original);
+
+ LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix));
+ LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL;
+ mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform));
}
else
{
- // ignore unrecognized joint
- LL_DEBUGS("GLTF") << "Ignoring joint: " << legal_name << LL_ENDL;
- continue;
+ // If bind matrices aren't present (they are optional in gltf),
+ // assume an identy matrix
+ // todo: find a model with this, might need to use rotated matrix
+ glm::mat4 inv_bind(1.0f);
+ glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name);
+ inv_bind = glm::inverse(skeleton_transform * inv_bind);
+
+ LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind));
+ LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL;
+ mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform));
}
- // Debug: Log original joint matrix
- glm::mat4 gltf_joint_matrix = jointNode.mMatrix;
- LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' original matrix:" << LL_ENDL;
- for(int i = 0; i < 4; i++)
+ // Compute Alternative matrices also known as overrides
+ LLMatrix4 original_joint_transform(glm::value_ptr(joints_data[joint].mOverrideMatrix));
+
+ // Viewer seems to care only about translation part,
+ // but for parity with collada taking original value
+ LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr());
+ newInverse.setTranslation(original_joint_transform.getTranslation());
+
+ LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL;
+ mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse));
+
+ if (legal_joint)
{
- LL_INFOS("GLTF_DEBUG") << " [" << gltf_joint_matrix[i][0] << ", " << gltf_joint_matrix[i][1]
- << ", " << gltf_joint_matrix[i][2] << ", " << gltf_joint_matrix[i][3] << "]" << LL_ENDL;
+ // Might be needed for uploader UI to correctly identify overriden joints
+ // but going to be incorrect if multiple skins are present
+ mJointList[legal_name] = newInverse;
+ mJointsFromNode.push_front(legal_name);
}
+ }
+}
- // Apply coordinate system rotation to joint transform
- glm::mat4 rotated_joint_matrix = coord_system_rotation * gltf_joint_matrix;
- // Debug: Log rotated joint matrix
- LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' rotated matrix:" << LL_ENDL;
- for(int i = 0; i < 4; i++)
+S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const
+{
+ S32 source_joint_node = gltf_skin.mJoints[source_joint];
+ S32 root_node = source_joint_node;
+ S32 found_node = source_joint_node;
+ S32 size = (S32)gltf_skin.mJoints.size();
+ do
+ {
+ root_node = found_node;
+ for (S32 i = 0; i < size; i++)
{
- LL_INFOS("GLTF_DEBUG") << " [" << rotated_joint_matrix[i][0] << ", " << rotated_joint_matrix[i][1]
- << ", " << rotated_joint_matrix[i][2] << ", " << rotated_joint_matrix[i][3] << "]" << LL_ENDL;
+ S32 joint = gltf_skin.mJoints[i];
+ const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint];
+ std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node);
+ if (it != jointNode.mChildren.end())
+ {
+ // Found node's parent
+ found_node = joint;
+ if (mJointMap.find(jointNode.mName) != mJointMap.end())
+ {
+ return i;
+ }
+ break;
+ }
}
+ } while (root_node != found_node);
- LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_joint_matrix));
- mJointList[legal_name] = gltf_transform;
- mJointsFromNode.push_front(legal_name);
-
- LL_INFOS("GLTF_DEBUG") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL;
- }
+ return -1;
}
-S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const
+S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const
{
- S32 root_joint = 0;
- S32 found_joint = source_joint;
+ S32 root_node = 0;
+ S32 found_node = source_joint_node;
S32 size = (S32)gltf_skin.mJoints.size();
do
{
- root_joint = found_joint;
+ root_node = found_node;
for (S32 i = 0; i < size; i++)
{
S32 joint = gltf_skin.mJoints[i];
@@ -818,514 +1239,328 @@ S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& glt
if (mJointMap.find(jointNode.mName) != mJointMap.end())
{
- std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint);
+ std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node);
if (it != jointNode.mChildren.end())
{
- found_joint = joint;
+ // Found node's parent
+ found_node = joint;
break;
}
}
}
- } while (root_joint != found_joint);
+ } while (root_node != found_node);
- return root_joint;
+ return root_node;
}
-S32 LLGLTFLoader::findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const
+S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const
{
- S32 root_joint = 0;
- S32 found_joint = 0;
+ S32 root_node = 0;
+ S32 found_node = 0;
S32 size = (S32)gltf_skin.mJoints.size();
do
{
- root_joint = found_joint;
+ root_node = found_node;
for (S32 i = 0; i < size; i++)
{
S32 joint = gltf_skin.mJoints[i];
const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint];
- std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint);
+ std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node);
if (it != jointNode.mChildren.end())
{
- found_joint = joint;
+ // Found node's parent
+ found_node = joint;
break;
}
}
- } while (root_joint != found_joint);
+ } while (root_node != found_node);
LL_INFOS("GLTF_DEBUG") << "mJointList name: ";
- const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_joint];
- LL_CONT << jointNode.mName << " index: " << root_joint << LL_ENDL;
- return root_joint;
+ const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node];
+ LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL;
+ return root_node;
}
-bool LLGLTFLoader::parseMaterials()
+S32 LLGLTFLoader::findParentNode(S32 node) const
{
- if (!mGltfLoaded) return false;
-
- // fill local texture data structures
- mSamplers.clear();
- for (auto& in_sampler : mGLTFAsset.mSamplers)
+ S32 size = (S32)mGLTFAsset.mNodes.size();
+ for (S32 i = 0; i < size; i++)
{
- gltf_sampler sampler;
- sampler.magFilter = in_sampler.mMagFilter > 0 ? in_sampler.mMagFilter : GL_LINEAR;
- sampler.minFilter = in_sampler.mMinFilter > 0 ? in_sampler.mMinFilter : GL_LINEAR;
- sampler.wrapS = in_sampler.mWrapS;
- sampler.wrapT = in_sampler.mWrapT;
- sampler.name = in_sampler.mName;
- mSamplers.push_back(sampler);
- }
-
- mImages.clear();
- for (auto& in_image : mGLTFAsset.mImages)
- {
- gltf_image image;
- image.numChannels = in_image.mComponent;
- image.bytesPerChannel = in_image.mBits >> 3; // Convert bits to bytes
- image.pixelType = in_image.mPixelType;
- image.size = 0; // We'll calculate this below if we have valid dimensions
-
- // Get dimensions from the texture if available
- if (in_image.mTexture && in_image.mTexture->getDiscardLevel() >= 0)
+ const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i];
+ std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node);
+ if (it != jointNode.mChildren.end())
{
- image.height = in_image.mTexture->getHeight();
- image.width = in_image.mTexture->getWidth();
- // Since we don't have direct access to the raw data, we'll use the dimensions to calculate size
- if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0)
- {
- image.size = static_cast<U32>(image.height * image.width * image.numChannels * image.bytesPerChannel);
- }
- }
- else
- {
- // Fallback to provided dimensions
- image.height = in_image.mHeight;
- image.width = in_image.mWidth;
- if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0)
- {
- image.size = static_cast<U32>(image.height * image.width * image.numChannels * image.bytesPerChannel);
- }
+ return i;
}
-
- // If we couldn't determine the size, skip this image
- if (image.size == 0)
- {
- LL_WARNS("GLTF_IMPORT") << "Image size could not be determined" << LL_ENDL;
- continue;
- }
-
- // We don't have direct access to the image data, so data pointer remains nullptr
- image.data = nullptr;
- mImages.push_back(image);
}
+ return -1;
+}
- mTextures.clear();
- for (auto& in_tex : mGLTFAsset.mTextures)
+void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest) const
+{
+ glm::mat4 new_lefover(1.f);
+ glm::mat4 rest(1.f);
+ joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName);
+ if (found_node != names_to_nodes.end())
{
- gltf_texture tex;
- tex.imageIdx = in_tex.mSource;
- tex.samplerIdx = in_tex.mSampler;
- tex.imageUuid.setNull();
+ S32 gltf_node_idx = found_node->second;
+ JointNodeData& node = gltf_nodes[gltf_node_idx];
+ node.mIsOverrideValid = true;
- if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size())
+ glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix;
+ if (mApplyXYRotation)
{
- LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL;
- return false;
+ gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose;
}
-
- mTextures.push_back(tex);
+ node.mOverrideMatrix = glm::inverse(parent_rest) * gltf_joint_rest_pose;
+
+ glm::vec3 override;
+ glm::vec3 skew;
+ glm::vec3 scale;
+ glm::vec4 perspective;
+ glm::quat rotation;
+ glm::decompose(node.mOverrideMatrix, scale, rotation, override, skew, perspective);
+ glm::vec3 translate;
+ glm::decompose(viewer_data.mJointMatrix, scale, rotation, translate, skew, perspective);
+ glm::mat4 viewer_joint = glm::recompose(scale, rotation, override, skew, perspective);
+
+ node.mOverrideMatrix = viewer_joint;
+ rest = parent_rest * node.mOverrideMatrix;
+ node.mOverrideRestMatrix = rest;
}
-
- // parse each material
- mMaterials.clear();
- for (const auto& gltf_material : mGLTFAsset.mMaterials)
+ else
{
- gltf_render_material mat;
- mat.name = gltf_material.mName;
-
- // PBR Metallic Roughness properties
- mat.hasPBR = true;
-
- // Base color factor
- mat.baseColor = LLColor4(
- gltf_material.mPbrMetallicRoughness.mBaseColorFactor[0],
- gltf_material.mPbrMetallicRoughness.mBaseColorFactor[1],
- gltf_material.mPbrMetallicRoughness.mBaseColorFactor[2],
- gltf_material.mPbrMetallicRoughness.mBaseColorFactor[3]
- );
-
- // Base color texture
- mat.hasBaseTex = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0;
- mat.baseColorTexIdx = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex;
- mat.baseColorTexCoords = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mTexCoord;
-
- // Metalness and roughness
- mat.metalness = gltf_material.mPbrMetallicRoughness.mMetallicFactor;
- mat.roughness = gltf_material.mPbrMetallicRoughness.mRoughnessFactor;
-
- // Metallic-roughness texture
- mat.hasMRTex = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex >= 0;
- mat.metalRoughTexIdx = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex;
- mat.metalRoughTexCoords = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTexCoord;
-
- // Normal texture
- mat.normalScale = gltf_material.mNormalTexture.mScale;
- mat.hasNormalTex = gltf_material.mNormalTexture.mIndex >= 0;
- mat.normalTexIdx = gltf_material.mNormalTexture.mIndex;
- mat.normalTexCoords = gltf_material.mNormalTexture.mTexCoord;
-
- // Occlusion texture
- mat.occlusionScale = gltf_material.mOcclusionTexture.mStrength;
- mat.hasOcclusionTex = gltf_material.mOcclusionTexture.mIndex >= 0;
- mat.occlusionTexIdx = gltf_material.mOcclusionTexture.mIndex;
- mat.occlusionTexCoords = gltf_material.mOcclusionTexture.mTexCoord;
-
- // Emissive texture and color
- mat.emissiveColor = LLColor4(
- gltf_material.mEmissiveFactor[0],
- gltf_material.mEmissiveFactor[1],
- gltf_material.mEmissiveFactor[2],
- 1.0f
- );
- mat.hasEmissiveTex = gltf_material.mEmissiveTexture.mIndex >= 0;
- mat.emissiveTexIdx = gltf_material.mEmissiveTexture.mIndex;
- mat.emissiveTexCoords = gltf_material.mEmissiveTexture.mTexCoord;
-
- // Convert AlphaMode enum to string
- switch (gltf_material.mAlphaMode)
- {
- case LL::GLTF::Material::AlphaMode::OPAQUE:
- mat.alphaMode = "OPAQUE";
- break;
- case LL::GLTF::Material::AlphaMode::MASK:
- mat.alphaMode = "MASK";
- break;
- case LL::GLTF::Material::AlphaMode::BLEND:
- mat.alphaMode = "BLEND";
- break;
- default:
- mat.alphaMode = "OPAQUE";
- break;
- }
-
- mat.alphaMask = gltf_material.mAlphaCutoff;
-
- // Verify that all referenced textures are valid
- if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) ||
- (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) ||
- (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) ||
- (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) ||
- (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size())))
- {
- LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL;
- return false;
- }
-
- // Verify texture coordinate sets are valid (mesh can have up to 3 sets of UV)
- if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) ||
- (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) ||
- (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) ||
- (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) ||
- (mat.hasMRTex && (mat.metalRoughTexCoords > 2)))
- {
- LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL;
- return false;
- }
-
- mMaterials.push_back(mat);
+ // No override for this joint
+ rest = parent_rest * viewer_data.mJointMatrix;
+ }
+ for (LLJointData& child_data : viewer_data.mChildren)
+ {
+ buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest);
}
-
- return true;
-}
-
-// TODO: convert raw vertex buffers to UUIDs
-void LLGLTFLoader::uploadMeshes()
-{
- //llassert(0);
}
-// convert raw image buffers to texture UUIDs & assemble into a render material
-void LLGLTFLoader::uploadMaterials()
+glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const
{
- LL_INFOS("GLTF_IMPORT") << "Uploading materials, count: " << mMaterials.size() << LL_ENDL;
+ // This is inefficient since we are recalculating some joints multiple times over
+ // Todo: cache it?
- for (gltf_render_material& mat : mMaterials)
+ if (joint_node_index < 0 || joint_node_index >= static_cast<S32>(mGLTFAsset.mNodes.size()))
{
- LL_INFOS("GLTF_IMPORT") << "Processing material: " << mat.name << LL_ENDL;
+ return glm::mat4(1.0f);
+ }
- // Process base color texture
- if (mat.hasBaseTex && mat.baseColorTexIdx < mTextures.size())
- {
- gltf_texture& gtex = mTextures[mat.baseColorTexIdx];
- if (gtex.imageUuid.isNull())
- {
- LL_INFOS("GLTF_IMPORT") << "Loading base color texture for material " << mat.name << LL_ENDL;
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
+ const auto& node = mGLTFAsset.mNodes[joint_node_index];
- if (gtex.imageUuid.notNull())
- {
- LL_INFOS("GLTF_IMPORT") << "Base color texture loaded, ID: " << gtex.imageUuid.asString() << LL_ENDL;
- }
- else
- {
- LL_WARNS("GLTF_IMPORT") << "Failed to load base color texture for material " << mat.name << LL_ENDL;
- }
- }
- }
-
- // Process other textures similarly
- if (mat.hasMRTex && mat.metalRoughTexIdx < mTextures.size())
- {
- gltf_texture& gtex = mTextures[mat.metalRoughTexIdx];
- if (gtex.imageUuid.isNull())
- {
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
- }
- }
+ // Find and apply parent transform if it exists
+ for (size_t i = 0; i < mGLTFAsset.mNodes.size(); ++i)
+ {
+ const auto& potential_parent = mGLTFAsset.mNodes[i];
+ auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), joint_node_index);
- if (mat.hasNormalTex && mat.normalTexIdx < mTextures.size())
+ if (it != potential_parent.mChildren.end())
{
- gltf_texture& gtex = mTextures[mat.normalTexIdx];
- if (gtex.imageUuid.isNull())
+ // Found parent
+ if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end())
{
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
+ // parent is a joint - recursively combine transform
+ // assumes that matrix is already valid
+ return buildGltfRestMatrix(static_cast<S32>(i), gltf_skin) * node.mMatrix;
}
}
+ }
+ // Should we return armature or stop earlier?
+ return node.mMatrix;
+}
- if (mat.hasOcclusionTex && mat.occlusionTexIdx < mTextures.size())
- {
- gltf_texture& gtex = mTextures[mat.occlusionTexIdx];
- if (gtex.imageUuid.isNull())
- {
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
- }
- }
+glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const
+{
+ // This is inefficient since we are recalculating some joints multiple times over
+ // Todo: cache it?
- if (mat.hasEmissiveTex && mat.emissiveTexIdx < mTextures.size())
- {
- gltf_texture& gtex = mTextures[mat.emissiveTexIdx];
- if (gtex.imageUuid.isNull())
- {
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
- }
- }
+ if (joint_node_index < 0 || joint_node_index >= static_cast<S32>(mGLTFAsset.mNodes.size()))
+ {
+ return glm::mat4(1.0f);
}
- // Update material map for all model instances to ensure textures are properly associated
- // mScene is a std::map<LLMatrix4, model_instance_list>, not an array, so we need to iterate through it correctly
- for (auto& scene_entry : mScene)
- {
- for (LLModelInstance& instance : scene_entry.second)
- {
- LLModel* model = instance.mModel;
+ auto& data = joint_data.at(joint_node_index);
- if (model)
- {
- for (size_t i = 0; i < model->getMaterialList().size(); ++i)
- {
- const std::string& matName = model->getMaterialList()[i];
- if (!matName.empty())
- {
- // Ensure this material exists in the instance's material map
- if (instance.mMaterial.find(matName) == instance.mMaterial.end())
- {
- // Find material in our render materials
- for (const auto& renderMat : mMaterials)
- {
- if (renderMat.name == matName)
- {
- // Create an import material from the render material
- LLImportMaterial impMat;
- impMat.mDiffuseColor = renderMat.baseColor;
-
- // Set diffuse texture if available
- if (renderMat.hasBaseTex && renderMat.baseColorTexIdx < mTextures.size())
- {
- const gltf_texture& gtex = mTextures[renderMat.baseColorTexIdx];
- if (!gtex.imageUuid.isNull())
- {
- impMat.setDiffuseMap(gtex.imageUuid);
- LL_INFOS("GLTF_IMPORT") << "Setting texture " << gtex.imageUuid.asString() << " for material " << matName << LL_ENDL;
- }
- }
-
- // Add material to instance's material map
- instance.mMaterial[matName] = impMat;
- LL_INFOS("GLTF_IMPORT") << "Added material " << matName << " to instance" << LL_ENDL;
- break;
- }
- }
- }
- }
- }
- }
- }
+ if (data.mParentNodeIdx >=0)
+ {
+ return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix;
}
+ // Should we return armature or stop earlier?
+ return data.mGltfMatrix;
}
-LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex)
+// This function computes the transformation matrix needed to convert from GLTF skeleton space
+// to viewer skeleton space for a specific joint
+
+glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const
{
- if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size())
+ const JointNodeData& node_data = joints_data_map.at(gltf_node_index);
+ if (!node_data.mIsOverrideValid)
{
- LL_WARNS("GLTF_IMPORT") << "Invalid texture indices in imageBufferToTextureUUID" << LL_ENDL;
- return LLUUID::null;
+ // For now assume they are identical and return an identity (for ease of debuging)
+ return glm::mat4(1.0f);
}
- gltf_image& image = mImages[tex.imageIdx];
- gltf_sampler& sampler = mSamplers[tex.samplerIdx];
+ // Get the GLTF joint's rest pose (in GLTF coordinate system)
+ const glm::mat4 &gltf_joint_rest_pose = node_data.mGltfRestMatrix;
+ glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose;
- S32 sourceIndex = tex.imageIdx;
- if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size())
- {
- LL_WARNS("GLTF_IMPORT") << "Invalid image index: " << sourceIndex << LL_ENDL;
- return LLUUID::null;
- }
+ LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": ";
- LL::GLTF::Image& source_image = mGLTFAsset.mImages[sourceIndex];
+ LLMatrix4 transform(glm::value_ptr(rest_pose));
- // If the image already has a texture loaded, use it
- if (source_image.mTexture.notNull())
- {
- LL_INFOS("GLTF_IMPORT") << "Using already loaded texture ID: " << source_image.mTexture->getID().asString() << LL_ENDL;
- return source_image.mTexture->getID();
- }
+ LL_CONT << transform << LL_ENDL;
+
+ // Compute transformation from GLTF space to viewer space
+ // This assumes both skeletons are in rest pose initially
+ return node_data.mOverrideRestMatrix * glm::inverse(rest_pose);
+}
- // Create an import material to pass to the texture load function
- LLImportMaterial material;
+bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx)
+{
+ glm::mat4 gltf_joint_rest = buildGltfRestMatrix(joint_idx, gltf_skin);
+ glm::mat4 test_mat = glm::inverse(gltf_joint_rest) * gltf_skin.mInverseBindMatricesData[bind_indx];
+ // Normally for shoulders it should be something close to
+ // {1,0,0,0;0,-1,0,0;0,0,-1,0;0,0,0,1}
+ // rotated one will look like
+ // {0,0,0,-1;1,0,0,0;0,-1,0,0;0,0,0,1}
+ // Todo: This is a cheap hack,
+ // figure out how rotation is supposed to work
+ return abs(test_mat[0][0]) < 0.5 && abs(test_mat[1][1]) < 0.5 && abs(test_mat[2][2]) < 0.5;
+}
+
+void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin)
+{
+ // HACK: figure out model's rotation from shoulders' matrix.
+ // This is wrong on many levels:
+ // Too limited (only models that have shoulders),
+ // Will not work well with things that emulate 3 hands in some manner
+ // Only supports xy 90 degree rotation
+ // Todo: figure out how to find skeleton's orientation Correctly
+ // when model is rotated at a triangle level
+ constexpr char right_shoulder_str[] = "mShoulderRight";
+ constexpr char left_shoulder_str[] = "mShoulderLeft";
- // Try to get the texture filename from the URI
- if (!source_image.mUri.empty())
+ S32 size = (S32)gltf_skin.mJoints.size();
+ S32 joints_found = 0;
+ for (S32 i= 0; i < size; i++)
{
- std::string filename = source_image.mUri;
+ S32 joint = gltf_skin.mJoints[i];
+ auto joint_node = mGLTFAsset.mNodes[joint];
- // Extract just the filename from the URI
- size_t pos = filename.find_last_of("/\\");
- if (pos != std::string::npos)
+ // todo: we are doing this search thing everywhere,
+ // just pre-translate every joint
+ JointMap::iterator found = mJointMap.find(joint_node.mName);
+ if (found == mJointMap.end())
{
- filename = filename.substr(pos + 1);
+ // unsupported joint
+ continue;
+ }
+ if (found->second == right_shoulder_str || found->second == left_shoulder_str)
+ {
+ if (checkForXYrotation(gltf_skin, joint, i))
+ {
+ joints_found++;
+ }
+ else
+ {
+ return;
+ }
}
-
- material.mDiffuseMapFilename = filename;
- material.mDiffuseMapLabel = filename;
- }
- else if (source_image.mBufferView >= 0)
- {
- // For embedded textures, create a pseudo-filename
- std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png";
- material.mDiffuseMapFilename = pseudo_filename;
- material.mDiffuseMapLabel = pseudo_filename;
}
- else
+
+ if (joints_found == 2)
{
- LL_WARNS("GLTF_IMPORT") << "No URI or buffer data for image" << LL_ENDL;
- return LLUUID::null;
+ // Both joints in a weird position/rotation, assume rotated model
+ mApplyXYRotation = true;
}
+}
- // Create LLSD container with image and sampler data for texture upload
- LLSD texture_data = LLSD::emptyMap();
+std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type)
+{
+ if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size())
+ return "";
- // Image data
- texture_data["width"] = LLSD::Integer(image.width);
- texture_data["height"] = LLSD::Integer(image.height);
- texture_data["components"] = LLSD::Integer(image.numChannels);
- texture_data["bytes_per_component"] = LLSD::Integer(image.bytesPerChannel);
- texture_data["pixel_type"] = LLSD::Integer(image.pixelType);
+ S32 sourceIndex = mGLTFAsset.mTextures[textureIndex].mSource;
+ if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size())
+ return "";
- // Sampler data
- texture_data["min_filter"] = LLSD::Integer(sampler.minFilter);
- texture_data["mag_filter"] = LLSD::Integer(sampler.magFilter);
- texture_data["wrap_s"] = LLSD::Integer(sampler.wrapS);
- texture_data["wrap_t"] = LLSD::Integer(sampler.wrapT);
+ LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex];
- // Add URI for reference
- if (!source_image.mUri.empty())
+ // Handle URI-based textures
+ if (!image.mUri.empty())
{
- texture_data["uri"] = source_image.mUri;
+ return image.mUri; // Return URI directly
}
- // Check if we have a buffer view for embedded data
- if (source_image.mBufferView >= 0)
+ // Handle embedded textures
+ if (image.mBufferView >= 0)
{
- texture_data["has_embedded_data"] = LLSD::Boolean(true);
- texture_data["buffer_view"] = LLSD::Integer(source_image.mBufferView);
-
- // Extract embedded data for texture loading
- if (source_image.mBufferView < mGLTFAsset.mBufferViews.size())
+ if (image.mBufferView < mGLTFAsset.mBufferViews.size())
{
- const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[source_image.mBufferView];
+ const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView];
if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size())
{
const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer];
+
if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size())
{
- // Add embedded data reference to texture_data
- texture_data["buffer_index"] = LLSD::Integer(buffer_view.mBuffer);
- texture_data["byte_offset"] = LLSD::Integer(buffer_view.mByteOffset);
- texture_data["byte_length"] = LLSD::Integer(buffer_view.mByteLength);
+ // Extract image data
+ const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset];
+ U32 data_size = buffer_view.mByteLength;
- LL_INFOS("GLTF_IMPORT") << "Found embedded texture data: offset=" << buffer_view.mByteOffset
- << " length=" << buffer_view.mByteLength << LL_ENDL;
- }
- }
- }
- }
-
- // Store the texture metadata in the binding field
- std::ostringstream ostr;
- LLSDSerialize::toXML(texture_data, ostr);
- material.mBinding = ostr.str();
-
- LL_INFOS("GLTF_IMPORT") << "Loading texture: " << material.mDiffuseMapFilename << LL_ENDL;
-
- // Flag to track if texture was loaded immediately
- bool texture_loaded = false;
-
- // Call texture loading function with our import material
- if (mTextureLoadFunc)
- {
- // Increment textures to fetch counter BEFORE calling load function
- mNumOfFetchingTextures++;
+ // Determine the file extension
+ std::string extension = ".png"; // Default
+ if (!image.mMimeType.empty())
+ {
+ if (image.mMimeType == "image/jpeg")
+ extension = ".jpg";
+ else if (image.mMimeType == "image/png")
+ extension = ".png";
+ }
+ else if (data_size >= 4)
+ {
+ if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8)
+ extension = ".jpg"; // JPEG magic bytes
+ else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47)
+ extension = ".png"; // PNG magic bytes
+ }
- U32 result = mTextureLoadFunc(material, mOpaqueData);
+ // Create a temporary file
+ std::string temp_dir = gDirUtilp->getTempDir();
+ std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() +
+ "gltf_embedded_" + texture_type + "_" + std::to_string(sourceIndex) + extension;
- // If result is 0, texture is being loaded asynchronously
- // If result is >0, texture was loaded immediately
- if (result > 0)
- {
- // Texture was loaded immediately, so decrement counter
- mNumOfFetchingTextures--;
- texture_loaded = true;
+ // Write the image data to the temporary file
+ std::ofstream temp_file(temp_filename, std::ios::binary);
+ if (temp_file.is_open())
+ {
+ temp_file.write(reinterpret_cast<const char*>(data_ptr), data_size);
+ temp_file.close();
- if (material.getDiffuseMap().notNull())
- {
- LL_INFOS("GLTF_IMPORT") << "Texture loaded successfully, ID: " << material.getDiffuseMap().asString() << LL_ENDL;
+ LL_INFOS("GLTF_IMPORT") << "Extracted embedded " << texture_type << " texture to: " << temp_filename << LL_ENDL;
+ return temp_filename;
+ }
+ else
+ {
+ LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for " << texture_type << " texture: " << temp_filename << LL_ENDL;
- // Store the texture in the source image for future reference
- if (source_image.mTexture.isNull())
- {
- // Create and store a texture object using the UUID
- source_image.mTexture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap());
+ LLSD args;
+ args["Message"] = "FailedToCreateTempFile";
+ args["TEXTURE_INDEX"] = sourceIndex;
+ args["TEXTURE_TYPE"] = texture_type;
+ args["TEMP_FILE"] = temp_filename;
+ mWarningsArray.append(args);
+ }
}
-
- return material.getDiffuseMap();
}
}
- else if (result == 0)
- {
- LL_INFOS("GLTF_IMPORT") << "Texture loading queued asynchronously for " << material.mDiffuseMapFilename << LL_ENDL;
- }
- else // result < 0, indicating error
- {
- // Texture loading failed, decrement counter
- mNumOfFetchingTextures--;
- LL_WARNS("GLTF_IMPORT") << "Texture loading failed for " << material.mDiffuseMapFilename << LL_ENDL;
- }
- }
- else
- {
- LL_WARNS("GLTF_IMPORT") << "No texture loading function available" << LL_ENDL;
}
- return LLUUID::null;
+ return "";
}
void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported)
diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h
index 6e0fe2b32c..19a029d6d4 100644
--- a/indra/newview/gltf/llgltfloader.h
+++ b/indra/newview/gltf/llgltfloader.h
@@ -32,95 +32,44 @@
#include "asset.h"
#include "llglheaders.h"
+#include "lljointdata.h"
#include "llmodelloader.h"
-// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD
-
-class gltf_sampler
-{
-public:
- // Uses GL enums
- S32 minFilter; // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR
- S32 magFilter; // GL_NEAREST or GL_LINEAR
- S32 wrapS; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
- S32 wrapT; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
- //S32 wrapR; // Found in some sample files, but not part of glTF 2.0 spec. Ignored.
- std::string name; // optional, currently unused
- // extensions and extras are sampler optional fields that we don't support - at least initially
-};
-
-class gltf_image
-{
-public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL)
- U8* data; // ptr to decoded image data
- U32 size; // in bytes, regardless of channel width
- U32 width;
- U32 height;
- U32 numChannels; // range 1..4
- U32 bytesPerChannel; // converted from gltf "bits", expects only 8, 16 or 32 as input
- U32 pixelType; // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT
-};
-
-class gltf_texture
-{
-public:
- U32 imageIdx;
- U32 samplerIdx;
- LLUUID imageUuid = LLUUID::null;
-};
-
-class gltf_render_material
-{
-public:
- std::string name;
-
- // scalar values
- LLColor4 baseColor; // linear encoding. Multiplied with vertex color, if present.
- double metalness;
- double roughness;
- double normalScale; // scale applies only to X,Y components of normal
- double occlusionScale; // strength multiplier for occlusion
- LLColor4 emissiveColor; // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent)
- std::string alphaMode; // "OPAQUE", "MASK" or "BLEND"
- double alphaMask; // alpha cut-off
-
- // textures
- U32 baseColorTexIdx; // always sRGB encoded
- U32 metalRoughTexIdx; // always linear, roughness in G channel, metalness in B channel
- U32 normalTexIdx; // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0)
- U32 occlusionTexIdx; // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded
- U32 emissiveTexIdx; // always stored as sRGB, in nits (candela / meter^2)
-
- // texture coordinates
- U32 baseColorTexCoords;
- U32 metalRoughTexCoords;
- U32 normalTexCoords;
- U32 occlusionTexCoords;
- U32 emissiveTexCoords;
-
- // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry??
-
- bool hasPBR;
- bool hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex;
-
- // This field is populated after upload
- LLUUID material_uuid = LLUUID::null;
-
-};
-
-class gltf_mesh
-{
-public:
- std::string name;
-
- // TODO add mesh import DJH 2022-04
-
-};
-
class LLGLTFLoader : public LLModelLoader
{
public:
typedef std::map<std::string, LLImportMaterial> material_map;
+ typedef std::map<std::string, std::string> joint_viewer_parent_map_t;
+ typedef std::map<std::string, glm::mat4> joint_viewer_rest_map_t;
+ typedef std::map<S32, glm::mat4> joint_node_mat4_map_t;
+
+ struct JointNodeData
+ {
+ JointNodeData()
+ : mJointListIdx(-1)
+ , mNodeIdx(-1)
+ , mParentNodeIdx(-1)
+ , mIsValidViewerJoint(false)
+ , mIsParentValidViewerJoint(false)
+ , mIsOverrideValid(false)
+ {
+
+ }
+ S32 mJointListIdx;
+ S32 mNodeIdx;
+ S32 mParentNodeIdx;
+ glm::mat4 mGltfRestMatrix;
+ glm::mat4 mViewerRestMatrix;
+ glm::mat4 mOverrideRestMatrix;
+ glm::mat4 mGltfMatrix;
+ glm::mat4 mOverrideMatrix;
+ std::string mName;
+ bool mIsValidViewerJoint;
+ bool mIsParentValidViewerJoint;
+ bool mIsOverrideValid;
+ };
+ typedef std::map <S32, JointNodeData> joints_data_map_t;
+ typedef std::map <std::string, S32> joints_name_to_node_map_t;
LLGLTFLoader(std::string filename,
S32 lod,
@@ -133,7 +82,8 @@ class LLGLTFLoader : public LLModelLoader
JointNameSet & jointsFromNodes,
std::map<std::string, std::string> &jointAliasMap,
U32 maxJointsPerMesh,
- U32 modelLimit); //,
+ U32 modelLimit,
+ std::vector<LLJointData> viewer_skeleton); //,
//bool preprocess );
virtual ~LLGLTFLoader();
@@ -152,27 +102,41 @@ protected:
LL::GLTF::Asset mGLTFAsset;
tinygltf::Model mGltfModel;
bool mGltfLoaded;
- bool mMeshesLoaded;
- bool mMaterialsLoaded;
+ bool mApplyXYRotation = false;
+ U32 mGeneratedModelLimit;
+
+ // GLTF isn't aware of viewer's skeleton and uses it's own,
+ // so need to take viewer's joints and use them to
+ // recalculate iverse bind matrices
+ std::vector<LLJointData> mViewerJointData;
- std::vector<gltf_mesh> mMeshes;
- std::vector<gltf_render_material> mMaterials;
+ // vector of vectors because of a posibility of having more than one skin
+ typedef std::vector<LLMeshSkinInfo::matrix_list_t> bind_matrices_t;
+ bind_matrices_t mInverseBindMatrices;
+ bind_matrices_t mAlternateBindMatrices;
- std::vector<gltf_texture> mTextures;
- std::vector<gltf_image> mImages;
- std::vector<gltf_sampler> mSamplers;
+ // per skin joint count, needs to be tracked for the sake of limits check.
+ std::vector<S32> mValidJointsCount;
private:
bool parseMeshes();
- void uploadMeshes();
- bool parseMaterials();
- void uploadMaterials();
void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const;
+ void processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params);
bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count);
- void populateJointFromSkin(const LL::GLTF::Skin& skin);
- S32 findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const;
- S32 findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint
- LLUUID imageBufferToTextureUUID(const gltf_texture& tex);
+ void populateJointFromSkin(S32 skin_idx);
+ void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats);
+ S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const;
+ S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const;
+ S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint
+ S32 findParentNode(S32 node) const;
+ void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest) const;
+ glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const;
+ glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const;
+ glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const;
+ bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx);
+ void checkForXYrotation(const LL::GLTF::Skin& gltf_skin);
+
+ std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type);
void notifyUnsupportedExtension(bool unsupported);
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index f962217480..84b9cb18f8 100644
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -114,11 +114,6 @@ void LLMeshFilePicker::notify(const std::vector<std::string>& filenames)
if (filenames.size() > 0)
{
mMP->loadModel(filenames[0], mLOD);
-
- if (filenames[0].substr(filenames[0].length() - 4) == ".glb" || filenames[0].substr(filenames[0].length() - 5) == ".gltf")
- {
- LLMaterialEditor::loadMaterialFromFile(filenames[0], -1);
- }
}
else
{
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index c7cb2b68e0..d8373ed0cb 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -40,6 +40,7 @@
#include "lldrawable.h"
#include "llface.h"
#include "lliconctrl.h"
+#include "lljointdata.h"
#include "llmatrix4a.h"
#include "llmeshrepository.h"
#include "llmeshoptimizer.h"
@@ -810,6 +811,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable
}
else
{
+ LLVOAvatar* av = getPreviewAvatar();
+ std::vector<LLJointData> viewer_skeleton;
+ av->getJointMatricesAndHierarhy(viewer_skeleton);
mModelLoader = new LLGLTFLoader(
filename,
lod,
@@ -822,7 +826,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable
mJointsFromNode,
joint_alias_map,
LLSkinningUtil::getMaxJointCount(),
- gSavedSettings.getU32("ImporterModelLimit"));
+ gSavedSettings.getU32("ImporterModelLimit"),
+ viewer_skeleton);
}
if (force_disable_slm)
diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp
index 43836a420b..47f58afa00 100644
--- a/indra/newview/llskinningutil.cpp
+++ b/indra/newview/llskinningutil.cpp
@@ -360,7 +360,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a
{
rig_info_tab[joint_num].setIsRiggedTo(true);
- const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index];
+ size_t bind_poses_size = skin->mBindPoseMatrix.size();
+ const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity();
LLVector4a pos_joint_space;
mat.affineTransform(pos, pos_joint_space);
diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml
index 6231abf9a5..80b6427738 100644
--- a/indra/newview/skins/default/xui/en/floater_model_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml
@@ -61,9 +61,19 @@
<string name="ParsingErrorNoRoot">Document has no root</string>
<string name="ParsingErrorNoScene">Document has no visual_scene</string>
<string name="ParsingErrorPositionInvalidModel">Unable to process mesh without position data. Invalid model.</string>
- <string name="InvalidGeometryNonTriangulated">Invalid geometry: GLTF files must contain triangulated meshes only.</string>
- <string name="IgnoredExtension">Model uses unsupported extension: [EXT], related material properties are ignored.</string>
- <string name="UnsupportedExtension">Unable to load a model, unsupported extension: [EXT]</string>
+
+ <!-- GLTF specific messages -->
+ <string name="NoScenesFound">No scenes defined in GLTF file</string>
+ <string name="InvalidMeshReference">Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES])</string>
+ <string name="InvalidGeometryNonTriangulated">Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated)</string>
+ <string name="ErrorIndexLimit">Unable to process mesh [MESH_NAME] due to 65,534 vertex limit. Vertex count: [VERTEX_COUNT]</string>
+ <string name="TextureFound">Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME]</string>
+ <string name="IgnoredExtension">Model uses unsupported extension: [EXT], related material properties are ignored</string>
+ <string name="UnsupportedExtension">Unable to load model, unsupported extension: [EXT]</string>
+ <string name="TooManyMeshParts">Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT]</string>
+ <string name="FailedToCreateTempFile">Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE]</string>
+ <string name="SkinJointsOverLimit">Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis.</string>
+ <string name="ModelTooManyJoint">Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail</string>
<panel
follows="top|left"