From c8499b7f01ac3f46f0764ea8195c30a4a2ec27a8 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 8 Apr 2025 13:51:21 -0400 Subject: GLTF WIP. Still working on getting transforms working proper and need to figure out our indices. --- indra/llprimitive/CMakeLists.txt | 2 - indra/llprimitive/llgltfloader.cpp | 404 --------------------------------- indra/llprimitive/llgltfloader.h | 206 ----------------- indra/newview/CMakeLists.txt | 2 + indra/newview/gltf/asset.cpp | 20 +- indra/newview/gltf/asset.h | 3 + indra/newview/gltf/llgltfloader.cpp | 431 ++++++++++++++++++++++++++++++++++++ indra/newview/gltf/llgltfloader.h | 209 +++++++++++++++++ indra/newview/llfilepicker.cpp | 2 +- indra/newview/llmodelpreview.cpp | 2 +- 10 files changed, 660 insertions(+), 621 deletions(-) delete mode 100644 indra/llprimitive/llgltfloader.cpp delete mode 100644 indra/llprimitive/llgltfloader.h create mode 100644 indra/newview/gltf/llgltfloader.cpp create mode 100644 indra/newview/gltf/llgltfloader.h diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index 3d8e02cb16..e13f0bbd96 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -12,7 +12,6 @@ include(TinyGLTF) set(llprimitive_SOURCE_FILES lldaeloader.cpp - llgltfloader.cpp llgltfmaterial.cpp llmaterialid.cpp llmaterial.cpp @@ -32,7 +31,6 @@ set(llprimitive_SOURCE_FILES set(llprimitive_HEADER_FILES CMakeLists.txt lldaeloader.h - llgltfloader.h llgltfmaterial.h llgltfmaterial_templates.h legacy_object_types.h diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp deleted file mode 100644 index 480012699a..0000000000 --- a/indra/llprimitive/llgltfloader.cpp +++ /dev/null @@ -1,404 +0,0 @@ -/** - * @file LLGLTFLoader.cpp - * @brief LLGLTFLoader class implementation - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, 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$ - */ - -#include "llgltfloader.h" - -// Import & define single-header gltf import/export lib -#define TINYGLTF_IMPLEMENTATION -#define TINYGLTF_USE_CPP14 // default is C++ 11 - -// tinygltf by default loads image files using STB -#define STB_IMAGE_IMPLEMENTATION -// to use our own image loading: -// 1. replace this definition with TINYGLTF_NO_STB_IMAGE -// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) - -// tinygltf saves image files using STB -#define STB_IMAGE_WRITE_IMPLEMENTATION -// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) - -// Additionally, disable inclusion of STB header files entirely with -// TINYGLTF_NO_INCLUDE_STB_IMAGE -// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE -#include "tinygltf/tiny_gltf.h" - - -// TODO: includes inherited from dae loader. Validate / prune - -#include "llsdserialize.h" -#include "lljoint.h" - -#include "llmatrix4a.h" - -#include -#include - -static const std::string lod_suffix[LLModel::NUM_LODS] = -{ - "_LOD0", - "_LOD1", - "_LOD2", - "", - "_PHYS", -}; - - -LLGLTFLoader::LLGLTFLoader(std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void * opaque_userdata, - JointTransformMap & jointTransformMap, - JointNameSet & jointsFromNodes, - std::map &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit) //, - //bool preprocess) - : LLModelLoader( filename, - lod, - load_cb, - joint_lookup_func, - texture_load_func, - state_cb, - opaque_userdata, - jointTransformMap, - jointsFromNodes, - jointAliasMap, - maxJointsPerMesh ), - //mPreprocessGLTF(preprocess), - mMeshesLoaded(false), - mMaterialsLoaded(false) -{ -} - -LLGLTFLoader::~LLGLTFLoader() {} - -bool LLGLTFLoader::OpenFile(const std::string &filename) -{ - tinygltf::TinyGLTF loader; - std::string error_msg; - std::string warn_msg; - std::string filename_lc(filename); - LLStringUtil::toLower(filename_lc); - - // Load a tinygltf model fom a file. Assumes that the input filename has already been - // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish. - if (std::string::npos == filename_lc.rfind(".gltf")) - { // file is binary - mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename); - } - else - { // file is ascii - mGltfLoaded = loader.LoadASCIIFromFile(&mGltfModel, &error_msg, &warn_msg, filename); - } - - if (!mGltfLoaded) - { - if (!warn_msg.empty()) - LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL; - if (!error_msg.empty()) - LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL; - return false; - } - - mMeshesLoaded = parseMeshes(); - if (mMeshesLoaded) uploadMeshes(); - - mMaterialsLoaded = parseMaterials(); - if (mMaterialsLoaded) uploadMaterials(); - - return (mMeshesLoaded || mMaterialsLoaded); -} - -bool LLGLTFLoader::parseMeshes() -{ - if (!mGltfLoaded) return false; - - // 2022-04 DJH Volume params from dae example. TODO understand PCODE - LLVolumeParams volume_params; - volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); - - for (tinygltf::Mesh mesh : mGltfModel.meshes) - { - LLModel *pModel = new LLModel(volume_params, 0.f); - - if (populateModelFromMesh(pModel, mesh) && - (LLModel::NO_ERRORS == pModel->getStatus()) && - validate_model(pModel)) - { - mModelList.push_back(pModel); - } - else - { - setLoadState(ERROR_MODEL + pModel->getStatus()); - delete(pModel); - return false; - } - } - return true; -} - -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh) -{ - pModel->mLabel = mesh.name; - int pos_idx; - tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a; - - auto prims = mesh.primitives; - for (auto prim : prims) - { - if (prim.indices >= 0) indices_a = mGltfModel.accessors[prim.indices]; - - pos_idx = (prim.attributes.count("POSITION") > 0) ? prim.attributes.at("POSITION") : -1; - if (pos_idx >= 0) - { - positions_a = mGltfModel.accessors[pos_idx]; - if (TINYGLTF_COMPONENT_TYPE_FLOAT != positions_a.componentType) - continue; - auto positions_bv = mGltfModel.bufferViews[positions_a.bufferView]; - auto positions_buf = mGltfModel.buffers[positions_bv.buffer]; - //auto type = positions_vb. - //if (positions_buf.name - } - -#if 0 - int norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx; - norm_idx = (prim.attributes.count("NORMAL") > 0) ? prim.attributes.at("NORMAL") : -1; - tan_idx = (prim.attributes.count("TANGENT") > 0) ? prim.attributes.at("TANGENT") : -1; - uv0_idx = (prim.attributes.count("TEXCOORDS_0") > 0) ? prim.attributes.at("TEXCOORDS_0") : -1; - uv1_idx = (prim.attributes.count("TEXCOORDS_1") > 0) ? prim.attributes.at("TEXCOORDS_1") : -1; - color0_idx = (prim.attributes.count("COLOR_0") > 0) ? prim.attributes.at("COLOR_0") : -1; - color1_idx = (prim.attributes.count("COLOR_1") > 0) ? prim.attributes.at("COLOR_1") : -1; -#endif - - if (prim.mode == TINYGLTF_MODE_TRIANGLES) - { - //auto pos = mesh. TODO resume here DJH 2022-04 - } - } - - //pModel->addFace() - return false; -} - -bool LLGLTFLoader::parseMaterials() -{ - if (!mGltfLoaded) return false; - - // fill local texture data structures - mSamplers.clear(); - for (auto in_sampler : mGltfModel.samplers) - { - gltf_sampler sampler; - sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR; - sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;; - sampler.wrapS = in_sampler.wrapS; - sampler.wrapT = in_sampler.wrapT; - sampler.name = in_sampler.name; // unused - mSamplers.push_back(sampler); - } - - mImages.clear(); - for (auto in_image : mGltfModel.images) - { - gltf_image image; - image.numChannels = in_image.component; - image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes - image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc - image.size = static_cast(in_image.image.size()); - image.height = in_image.height; - image.width = in_image.width; - image.data = in_image.image.data(); - - if (in_image.as_is) - { - LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; - return false; - } - - if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) - { - LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; - return false; - } - - mImages.push_back(image); - } - - mTextures.clear(); - for (auto in_tex : mGltfModel.textures) - { - gltf_texture tex; - tex.imageIdx = in_tex.source; - tex.samplerIdx = in_tex.sampler; - tex.imageUuid.setNull(); - - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; - return false; - } - - mTextures.push_back(tex); - } - - // parse each material - for (tinygltf::Material gltf_material : mGltfModel.materials) - { - gltf_render_material mat; - mat.name = gltf_material.name; - - tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; - mat.hasPBR = true; // Always true, for now - - mat.baseColor.set(pbr.baseColorFactor.data()); - mat.hasBaseTex = pbr.baseColorTexture.index >= 0; - mat.baseColorTexIdx = pbr.baseColorTexture.index; - mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; - - mat.metalness = pbr.metallicFactor; - mat.roughness = pbr.roughnessFactor; - mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; - mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; - mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; - - mat.normalScale = gltf_material.normalTexture.scale; - mat.hasNormalTex = gltf_material.normalTexture.index >= 0; - mat.normalTexIdx = gltf_material.normalTexture.index; - mat.normalTexCoords = gltf_material.normalTexture.texCoord; - - mat.occlusionScale = gltf_material.occlusionTexture.strength; - mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; - mat.occlusionTexIdx = gltf_material.occlusionTexture.index; - mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; - - mat.emissiveColor.set(gltf_material.emissiveFactor.data()); - mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; - mat.emissiveTexIdx = gltf_material.emissiveTexture.index; - mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; - - mat.alphaMode = gltf_material.alphaMode; - mat.alphaMask = gltf_material.alphaCutoff; - - 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; - } - - if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV - (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || - (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || - (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || - (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) - { - LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; - return false; - } - - mMaterials.push_back(mat); - } - - return true; -} - -// TODO: convert raw vertex buffers to UUIDs -void LLGLTFLoader::uploadMeshes() -{ - llassert(0); -} - -// convert raw image buffers to texture UUIDs & assemble into a render material -void LLGLTFLoader::uploadMaterials() -{ - for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple - { - if (mat.hasBaseTex) - { - gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasMRTex) - { - gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasNormalTex) - { - gltf_texture& gtex = mTextures[mat.normalTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasOcclusionTex) - { - gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasEmissiveTex) - { - gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - } -} - -LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) -{ - //gltf_image& image = mImages[tex.imageIdx]; - //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; - - // fill an LLSD container with image+sampler data - - // upload texture - - // retrieve UUID - - return LLUUID::null; -} diff --git a/indra/llprimitive/llgltfloader.h b/indra/llprimitive/llgltfloader.h deleted file mode 100644 index 66671d1c5a..0000000000 --- a/indra/llprimitive/llgltfloader.h +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @file LLGLTFLoader.h - * @brief LLGLTFLoader class definition - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, 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_LLGLTFLoader_H -#define LL_LLGLTFLoader_H - -#include "tinygltf/tiny_gltf.h" - -#include "llglheaders.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 material_map; - - LLGLTFLoader(std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void * opaque_userdata, - JointTransformMap & jointTransformMap, - JointNameSet & jointsFromNodes, - std::map &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit); //, - //bool preprocess ); - virtual ~LLGLTFLoader(); - - virtual bool OpenFile(const std::string &filename); - -protected: - tinygltf::Model mGltfModel; - bool mGltfLoaded; - bool mMeshesLoaded; - bool mMaterialsLoaded; - - std::vector mMeshes; - std::vector mMaterials; - - std::vector mTextures; - std::vector mImages; - std::vector mSamplers; - -private: - bool parseMeshes(); - void uploadMeshes(); - bool parseMaterials(); - void uploadMaterials(); - bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh); - LLUUID imageBufferToTextureUUID(const gltf_texture& tex); - - // bool mPreprocessGLTF; - - /* Below inherited from dae loader - unknown if/how useful here - - void processElement(gltfElement *element, bool &badElement, GLTF *gltf); - void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin); - - material_map getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf); - LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf); - LLColor4 getGltfColor(gltfElement *element); - - gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name); - - bool isNodeAJoint(gltfNode *pNode); - void processJointNode(gltfNode *pNode, std::map &jointTransforms); - void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform); - void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform); - void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform); - void buildJointToNodeMappingFromScene(gltfElement *pRoot); - void processJointToNodeMapping(gltfNode *pNode); - void processChildJoints(gltfNode *pParentNode); - - bool verifyCount(int expected, int result); - - // Verify that a controller matches vertex counts - bool verifyController(gltfController *pController); - - static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg); - static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh); - - static LLModel *loadModelFromGltfMesh(gltfMesh *mesh); - - // Loads a mesh breaking it into one or more models as necessary - // to get around volume face limitations while retaining >8 materials - // - bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector &models_out, U32 submodel_limit); - - static std::string getElementLabel(gltfElement *element); - static size_t getSuffixPosition(std::string label); - static std::string getLodlessLabel(gltfElement *element); - - static std::string preprocessGLTF(std::string filename); - */ - -}; -#endif // LL_LLGLTFLLOADER_H diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index ed29911a43..7dfe01898a 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -76,6 +76,7 @@ set(viewer_SOURCE_FILES gltf/accessor.cpp gltf/primitive.cpp gltf/animation.cpp + gltf/llgltfloader.cpp groupchatlistener.cpp llaccountingcostmanager.cpp llaisapi.cpp @@ -744,6 +745,7 @@ set(viewer_HEADER_FILES gltf/buffer_util.h gltf/primitive.h gltf/animation.h + gltf/llgltfloader.h llaccountingcost.h llaccountingcostmanager.h llaisapi.h diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index c210b9c61d..beccb02bd4 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -472,11 +472,14 @@ void Asset::update() for (auto& image : mImages) { - if (image.mTexture.notNull()) - { // HACK - force texture to be loaded full rez - // TODO: calculate actual vsize - image.mTexture->addTextureStats(2048.f * 2048.f); - image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); + if (image.mLoadIntoTexturePipe) + { + if (image.mTexture.notNull()) + { // HACK - force texture to be loaded full rez + // TODO: calculate actual vsize + image.mTexture->addTextureStats(2048.f * 2048.f); + image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); + } } } } @@ -603,6 +606,7 @@ bool Asset::prep() if (vertex_count[variant] > 0) { U32 mat_idx = mat_id + 1; + #if 0 LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); rd.mBatches[variant][mat_idx].mVertexBuffer = vb; @@ -624,6 +628,7 @@ bool Asset::prep() vb->unmapBuffer(); vb->unbind(); + #endif } } } @@ -634,10 +639,10 @@ bool Asset::prep() { for (auto& primitive : mesh.mPrimitives) { - llassert(primitive.mVertexBuffer.notNull()); + //llassert(primitive.mVertexBuffer.notNull()); } } - + #if 0 // build render batches for (S32 node_id = 0; node_id < mNodes.size(); ++node_id) { @@ -664,6 +669,7 @@ bool Asset::prep() } } } + #endif return true; } diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 27821659db..e70fffa986 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -286,6 +286,7 @@ namespace LL void serialize(boost::json::object& dst) const; }; + // Image is for images that we want to load for the given asset. This acts as an interface into the viewer's texture pipe. class Image { public: @@ -301,6 +302,8 @@ namespace LL S32 mBits = -1; S32 mPixelType = -1; + bool mLoadIntoTexturePipe = false; + LLPointer mTexture; const Image& operator=(const Value& src); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp new file mode 100644 index 0000000000..106c20d4d3 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.cpp @@ -0,0 +1,431 @@ +/** + * @file LLGLTFLoader.cpp + * @brief LLGLTFLoader class implementation + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, 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$ + */ + +#include "llgltfloader.h" + +// Import & define single-header gltf import/export lib +#define TINYGLTF_IMPLEMENTATION +#define TINYGLTF_USE_CPP14 // default is C++ 11 + +// tinygltf by default loads image files using STB +#define STB_IMAGE_IMPLEMENTATION +// to use our own image loading: +// 1. replace this definition with TINYGLTF_NO_STB_IMAGE +// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) + +// tinygltf saves image files using STB +#define STB_IMAGE_WRITE_IMPLEMENTATION +// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) + +// Additionally, disable inclusion of STB header files entirely with +// TINYGLTF_NO_INCLUDE_STB_IMAGE +// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE +#include "tinygltf/tiny_gltf.h" + + +// TODO: includes inherited from dae loader. Validate / prune + +#include "llsdserialize.h" +#include "lljoint.h" + +#include "llmatrix4a.h" + +#include +#include + +static const std::string lod_suffix[LLModel::NUM_LODS] = +{ + "_LOD0", + "_LOD1", + "_LOD2", + "", + "_PHYS", +}; + + +LLGLTFLoader::LLGLTFLoader(std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void * opaque_userdata, + JointTransformMap & jointTransformMap, + JointNameSet & jointsFromNodes, + std::map &jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit) //, + //bool preprocess) + : LLModelLoader( filename, + lod, + load_cb, + joint_lookup_func, + texture_load_func, + state_cb, + opaque_userdata, + jointTransformMap, + jointsFromNodes, + jointAliasMap, + maxJointsPerMesh ), + //mPreprocessGLTF(preprocess), + mMeshesLoaded(false), + mMaterialsLoaded(false) +{ +} + +LLGLTFLoader::~LLGLTFLoader() {} + +bool LLGLTFLoader::OpenFile(const std::string &filename) +{ + tinygltf::TinyGLTF loader; + std::string error_msg; + std::string warn_msg; + std::string filename_lc(filename); + LLStringUtil::toLower(filename_lc); + + mGltfLoaded = mGLTFAsset.load(filename); + + if (!mGltfLoaded) + { + if (!warn_msg.empty()) + LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL; + if (!error_msg.empty()) + LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL; + return false; + } + + mMeshesLoaded = parseMeshes(); + if (mMeshesLoaded) uploadMeshes(); + + /* + mMaterialsLoaded = parseMaterials(); + if (mMaterialsLoaded) uploadMaterials(); + */ + + setLoadState(DONE); + + return (mMeshesLoaded); +} + +bool LLGLTFLoader::parseMeshes() +{ + if (!mGltfLoaded) return false; + + // 2022-04 DJH Volume params from dae example. TODO understand PCODE + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + + for (auto node : mGLTFAsset.mNodes) + { + LLMatrix4 transform; + material_map mats; + auto meshidx = node.mMesh; + + if (meshidx >= 0) + { + LLModel* pModel = new LLModel(volume_params, 0.f); + auto mesh = mGLTFAsset.mMeshes[meshidx]; + + if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) + { + mModelList.push_back(pModel); + 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 *= transform; + transform = mesh_translation; + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transform; + transform = mesh_scale; + + + mScene[transform].push_back(LLModelInstance(pModel, node.mName, transform, mats)); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete (pModel); + return false; + } + } + } + + return true; +} + +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map &mats) +{ + pModel->mLabel = mesh.mName; + pModel->ClearFacesAndMaterials(); + + auto prims = mesh.mPrimitives; + for (auto prim : prims) + { + // 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; + LLVolumeFace::VertexMapData::PointMap point_map; + + std::vector vertices; + std::vector indices; + + LLImportMaterial impMat; + + auto material = mGLTFAsset.mMaterials[prim.mMaterial]; + + impMat.mDiffuseColor = LLColor4::white; + + + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + LLVolumeFace::VertexData vert; + vert.setPosition(prim.mPositions[i]); + vert.setNormal(prim.mNormals[i]); + vert.mTexCoord = prim.mTexCoords0[i]; + vertices.push_back(vert); + } + + for (S32 i = 0; i < prim.mIndexArray.size(); i++) + { + indices.push_back(prim.mIndexArray[i]); + } + + face.fillFromLegacyData(vertices, indices); + + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); + mats["mat" + std::to_string(prim.mMaterial)] = impMat; + } + + return true; +} + +bool LLGLTFLoader::parseMaterials() +{ + return true; + /* + if (!mGltfLoaded) return false; + + // fill local texture data structures + mSamplers.clear(); + for (auto in_sampler : mGltfModel.samplers) + { + gltf_sampler sampler; + sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR; + sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;; + sampler.wrapS = in_sampler.wrapS; + sampler.wrapT = in_sampler.wrapT; + sampler.name = in_sampler.name; // unused + mSamplers.push_back(sampler); + } + + mImages.clear(); + for (auto in_image : mGltfModel.images) + { + gltf_image image; + image.numChannels = in_image.component; + image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes + image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc + image.size = static_cast(in_image.image.size()); + image.height = in_image.height; + image.width = in_image.width; + image.data = in_image.image.data(); + + if (in_image.as_is) + { + LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; + return false; + } + + if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) + { + LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; + return false; + } + + mImages.push_back(image); + } + + mTextures.clear(); + for (auto in_tex : mGltfModel.textures) + { + gltf_texture tex; + tex.imageIdx = in_tex.source; + tex.samplerIdx = in_tex.sampler; + tex.imageUuid.setNull(); + + if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) + { + LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; + return false; + } + + mTextures.push_back(tex); + } + + // parse each material + for (tinygltf::Material gltf_material : mGltfModel.materials) + { + gltf_render_material mat; + mat.name = gltf_material.name; + + tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; + mat.hasPBR = true; // Always true, for now + + mat.baseColor.set(pbr.baseColorFactor.data()); + mat.hasBaseTex = pbr.baseColorTexture.index >= 0; + mat.baseColorTexIdx = pbr.baseColorTexture.index; + mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; + + mat.metalness = pbr.metallicFactor; + mat.roughness = pbr.roughnessFactor; + mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; + mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; + mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; + + mat.normalScale = gltf_material.normalTexture.scale; + mat.hasNormalTex = gltf_material.normalTexture.index >= 0; + mat.normalTexIdx = gltf_material.normalTexture.index; + mat.normalTexCoords = gltf_material.normalTexture.texCoord; + + mat.occlusionScale = gltf_material.occlusionTexture.strength; + mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; + mat.occlusionTexIdx = gltf_material.occlusionTexture.index; + mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; + + mat.emissiveColor.set(gltf_material.emissiveFactor.data()); + mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; + mat.emissiveTexIdx = gltf_material.emissiveTexture.index; + mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; + + mat.alphaMode = gltf_material.alphaMode; + mat.alphaMask = gltf_material.alphaCutoff; + + 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; + } + + if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV + (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || + (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || + (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || + (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) + { + LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; + return false; + } + + mMaterials.push_back(mat); + } + + return true; + */ +} + +// TODO: convert raw vertex buffers to UUIDs +void LLGLTFLoader::uploadMeshes() +{ + //llassert(0); +} + +// convert raw image buffers to texture UUIDs & assemble into a render material +void LLGLTFLoader::uploadMaterials() +{ + for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple + { + if (mat.hasBaseTex) + { + gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasMRTex) + { + gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasNormalTex) + { + gltf_texture& gtex = mTextures[mat.normalTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasOcclusionTex) + { + gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasEmissiveTex) + { + gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + } +} + +LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) +{ + //gltf_image& image = mImages[tex.imageIdx]; + //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + + // fill an LLSD container with image+sampler data + + // upload texture + + // retrieve UUID + + return LLUUID::null; +} diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h new file mode 100644 index 0000000000..3b7147f588 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.h @@ -0,0 +1,209 @@ +/** + * @file LLGLTFLoader.h + * @brief LLGLTFLoader class definition + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, 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_LLGLTFLoader_H +#define LL_LLGLTFLoader_H + +#include "tinygltf/tiny_gltf.h" + +#include "asset.h" + +#include "llglheaders.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 material_map; + + LLGLTFLoader(std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void * opaque_userdata, + JointTransformMap & jointTransformMap, + JointNameSet & jointsFromNodes, + std::map &jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit); //, + //bool preprocess ); + virtual ~LLGLTFLoader(); + + virtual bool OpenFile(const std::string &filename); + +protected: + LL::GLTF::Asset mGLTFAsset; + tinygltf::Model mGltfModel; + bool mGltfLoaded; + bool mMeshesLoaded; + bool mMaterialsLoaded; + + std::vector mMeshes; + std::vector mMaterials; + + std::vector mTextures; + std::vector mImages; + std::vector mSamplers; + +private: + bool parseMeshes(); + void uploadMeshes(); + bool parseMaterials(); + void uploadMaterials(); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map& mats); + LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + + // bool mPreprocessGLTF; + + /* Below inherited from dae loader - unknown if/how useful here + + void processElement(gltfElement *element, bool &badElement, GLTF *gltf); + void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin); + + material_map getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf); + LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf); + LLColor4 getGltfColor(gltfElement *element); + + gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name); + + bool isNodeAJoint(gltfNode *pNode); + void processJointNode(gltfNode *pNode, std::map &jointTransforms); + void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform); + void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform); + void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform); + void buildJointToNodeMappingFromScene(gltfElement *pRoot); + void processJointToNodeMapping(gltfNode *pNode); + void processChildJoints(gltfNode *pParentNode); + + bool verifyCount(int expected, int result); + + // Verify that a controller matches vertex counts + bool verifyController(gltfController *pController); + + static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg); + static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh); + + static LLModel *loadModelFromGltfMesh(gltfMesh *mesh); + + // Loads a mesh breaking it into one or more models as necessary + // to get around volume face limitations while retaining >8 materials + // + bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector &models_out, U32 submodel_limit); + + static std::string getElementLabel(gltfElement *element); + static size_t getSuffixPosition(std::string label); + static std::string getLodlessLabel(gltfElement *element); + + static std::string preprocessGLTF(std::string filename); + */ + +}; +#endif // LL_LLGLTFLLOADER_H diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 716e6cd9e3..09a2206cd7 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -59,7 +59,7 @@ LLFilePicker LLFilePicker::sInstance; #define XML_FILTER L"XML files (*.xml)\0*.xml\0" #define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0" #define RAW_FILTER L"RAW files (*.raw)\0*.raw\0" -#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0" +#define MODEL_FILTER L"Model files (*.dae, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0" #define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0" #define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0" #define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 5a8fd299bf..14d23b73c9 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -30,7 +30,7 @@ #include "llmodelloader.h" #include "lldaeloader.h" -#include "llgltfloader.h" +#include "gltf/llgltfloader.h" #include "llfloatermodelpreview.h" #include "llagent.h" -- cgit v1.2.3 From f6b066073e9a6318dc9ebac4b0137b81e2982f14 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 28 Apr 2025 03:33:03 -0400 Subject: Cursed stuff! --- indra/newview/gltf/llgltfloader.cpp | 278 ++++++++++++++++++++++++++++++++---- indra/newview/gltf/llgltfloader.h | 7 + 2 files changed, 254 insertions(+), 31 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 106c20d4d3..ce87a9594f 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -25,6 +25,7 @@ */ #include "llgltfloader.h" +#include "meshoptimizer.h" // Import & define single-header gltf import/export lib #define TINYGLTF_IMPLEMENTATION @@ -152,20 +153,19 @@ bool LLGLTFLoader::parseMeshes() if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { mModelList.push_back(pModel); - LLVector3 mesh_scale_vector; - LLVector3 mesh_translation_vector; - pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + LLVector3 mesh_scale_vector = LLVector3(node.mScale); + LLVector3 mesh_translation_vector = LLVector3(node.mTranslation); LLMatrix4 mesh_translation; mesh_translation.setTranslation(mesh_translation_vector); mesh_translation *= transform; - transform = mesh_translation; - + transform = LLMatrix4((float*)&node.mAssetMatrix[0][0]); + /* LLMatrix4 mesh_scale; mesh_scale.initScale(mesh_scale_vector); mesh_scale *= transform; transform = mesh_scale; - + */ mScene[transform].push_back(LLModelInstance(pModel, node.mName, transform, mats)); } @@ -189,47 +189,263 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & auto prims = mesh.mPrimitives; for (auto prim : prims) { - // 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; - LLVolumeFace::VertexMapData::PointMap point_map; + // 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; + LLVolumeFace::VertexMapData::PointMap point_map; + + std::vector vertices; + std::vector indices; + + LLImportMaterial impMat; + + LL::GLTF::Material* material = nullptr; + + if (prim.mMaterial >= 0) + material = &mGLTFAsset.mMaterials[prim.mMaterial]; + + impMat.mDiffuseColor = LLColor4::white; + + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + GLTFVertex vert; + vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[2][i]); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], prim.mTexCoords0[i][1]); + vertices.push_back(vert); + } + + for (U32 i = 0; i < prim.getIndexCount(); i++) + { + indices.push_back(prim.mIndexArray[i]); + } + + std::vector faceVertices; + + for (U32 i = 0; i < vertices.size(); i++) + { + LLVolumeFace::VertexData vert; + 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); + } + + face.fillFromLegacyData(faceVertices, indices); + + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); + mats["mat" + std::to_string(prim.mMaterial)] = impMat; + } + } + + return true; +} +/* +LLModel::EModelStatus loadFaceFromGLTFModel(LLModel* pModel, const LL::GLTF::Mesh& mesh, material_map& mats, LLSD& log_msg) +{ + LLVolumeFace face; + std::vector verts; + std::vector indices; + + S32 pos_offset = -1; + S32 tc_offset = -1; + S32 norm_offset = -1; + + auto pos_source = mesh.mPrimitives[0].mPositions; + auto tc_source = mesh.mPrimitives[0].mNormals; + auto norm_source = mesh.mPrimitives[0].mTexCoords0; + + S32 idx_stride = 0; + + if (pos_source.size() > USHRT_MAX) + { + LL_WARNS() << "Unable to process mesh due to 16-bit index limits; invalid model; invalid model." << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorBadElement"; + log_msg.append(args); + return LLModel::BAD_ELEMENT; + + } + + std::vector idx = mesh.mPrimitives[0].mIndexArray; + + domListOfFloats dummy; + domListOfFloats& v = pos_source ? pos_source->getFloat_array()->getValue() : dummy; + domListOfFloats& tc = tc_source ? tc_source->getFloat_array()->getValue() : dummy; + domListOfFloats& n = norm_source ? norm_source->getFloat_array()->getValue() : dummy; + + if (pos_source.size() == 0) + { + return LLModel::BAD_ELEMENT; + } + + // VFExtents change + face.mExtents[0].set(pos_source[0][0], pos_source[0][1], pos_source[0][2]); + face.mExtents[1].set(pos_source[0][0], pos_source[0][1], pos_source[0][2]); + + LLVolumeFace::VertexMapData::PointMap point_map; + + if (idx_stride <= 0 || (pos_source && pos_offset >= idx_stride) || (tc_source && tc_offset >= idx_stride) || + (norm_source && norm_offset >= idx_stride)) + { + // Looks like these offsets should fit inside idx_stride + // Might be good idea to also check idx.getCount()%idx_stride != 0 + LL_WARNS() << "Invalid pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL; + return LLModel::BAD_ELEMENT; + } + + for (U32 i = 0; i < idx.getCount(); i += idx_stride) + { + LLVolumeFace::VertexData cv; + if (pos_source) + { + cv.setPosition( + LLVector4a((F32)v[idx[i + pos_offset] * 3 + 0], (F32)v[idx[i + pos_offset] * 3 + 1], (F32)v[idx[i + pos_offset] * 3 + 2])); + } - std::vector vertices; - std::vector indices; + if (tc_source) + { + cv.mTexCoord.setVec((F32)tc[idx[i + tc_offset] * 2 + 0], (F32)tc[idx[i + tc_offset] * 2 + 1]); + } - LLImportMaterial impMat; + if (norm_source) + { + cv.setNormal(LLVector4a((F32)n[idx[i + norm_offset] * 3 + 0], + (F32)n[idx[i + norm_offset] * 3 + 1], + (F32)n[idx[i + norm_offset] * 3 + 2])); + } - auto material = mGLTFAsset.mMaterials[prim.mMaterial]; + bool found = false; - impMat.mDiffuseColor = LLColor4::white; + LLVolumeFace::VertexMapData::PointMap::iterator point_iter; + point_iter = point_map.find(LLVector3(cv.getPosition().getF32ptr())); + if (point_iter != point_map.end()) + { + for (U32 j = 0; j < point_iter->second.size(); ++j) + { + // We have a matching loc + // + if ((point_iter->second)[j] == cv) + { + U16 shared_index = (point_iter->second)[j].mIndex; + + // Don't share verts within the same tri, degenerate + // + U32 indx_size = static_cast(indices.size()); + U32 verts_new_tri = indx_size % 3; + if ((verts_new_tri < 1 || indices[indx_size - 1] != shared_index) && + (verts_new_tri < 2 || indices[indx_size - 2] != shared_index)) + { + found = true; + indices.push_back(shared_index); + } + break; + } + } + } - for (U32 i = 0; i < prim.getVertexCount(); i++) + if (!found) { - LLVolumeFace::VertexData vert; - vert.setPosition(prim.mPositions[i]); - vert.setNormal(prim.mNormals[i]); - vert.mTexCoord = prim.mTexCoords0[i]; - vertices.push_back(vert); + // VFExtents change + update_min_max(face.mExtents[0], face.mExtents[1], cv.getPosition()); + verts.push_back(cv); + if (verts.size() >= 65535) + { + // llerrs << "Attempted to write model exceeding 16-bit index buffer limitation." << LL_ENDL; + return LLModel::VERTEX_NUMBER_OVERFLOW; + } + U16 index = (U16)(verts.size() - 1); + indices.push_back(index); + + LLVolumeFace::VertexMapData d; + d.setPosition(cv.getPosition()); + d.mTexCoord = cv.mTexCoord; + d.setNormal(cv.getNormal()); + d.mIndex = index; + if (point_iter != point_map.end()) + { + point_iter->second.push_back(d); + } + else + { + point_map[LLVector3(d.getPosition().getF32ptr())].push_back(d); + } } - for (S32 i = 0; i < prim.mIndexArray.size(); i++) + if (indices.size() % 3 == 0 && verts.size() >= 65532) { - indices.push_back(prim.mIndexArray[i]); + std::string material; + + if (tri->getMaterial()) + { + material = std::string(tri->getMaterial()); + } + + materials.push_back(material); + face_list.push_back(face); + face_list.rbegin()->fillFromLegacyData(verts, indices); + LLVolumeFace& new_face = *face_list.rbegin(); + if (!norm_source) + { + // ll_aligned_free_16(new_face.mNormals); + new_face.mNormals = NULL; + } + + if (!tc_source) + { + // ll_aligned_free_16(new_face.mTexCoords); + new_face.mTexCoords = NULL; + } + + face = LLVolumeFace(); + // VFExtents change + face.mExtents[0].set((F32)v[0], (F32)v[1], (F32)v[2]); + face.mExtents[1].set((F32)v[0], (F32)v[1], (F32)v[2]); + + verts.clear(); + indices.clear(); + point_map.clear(); } + } - face.fillFromLegacyData(vertices, indices); + if (!verts.empty()) + { + std::string material; - pModel->getVolumeFaces().push_back(face); - pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); - mats["mat" + std::to_string(prim.mMaterial)] = impMat; + if (tri->getMaterial()) + { + material = std::string(tri->getMaterial()); + } + + materials.push_back(material); + face_list.push_back(face); + + face_list.rbegin()->fillFromLegacyData(verts, indices); + LLVolumeFace& new_face = *face_list.rbegin(); + if (!norm_source) + { + // ll_aligned_free_16(new_face.mNormals); + new_face.mNormals = NULL; + } + + if (!tc_source) + { + // ll_aligned_free_16(new_face.mTexCoords); + new_face.mTexCoords = NULL; + } } - return true; + return LLModel::NO_ERRORS; } - +*/ bool LLGLTFLoader::parseMaterials() { return true; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 3b7147f588..cfb545be6f 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -139,6 +139,13 @@ class LLGLTFLoader : public LLModelLoader virtual bool OpenFile(const std::string &filename); + struct GLTFVertex + { + glm::vec3 position; + glm::vec3 normal; + glm::vec2 uv0; + }; + protected: LL::GLTF::Asset mGLTFAsset; tinygltf::Model mGltfModel; -- cgit v1.2.3 From 008f89f7e9698028e8beaa42a64428909271ebf3 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Sun, 4 May 2025 19:05:34 -0400 Subject: More fixes - still a bit hacky but getting there. --- indra/newview/gltf/llgltfloader.cpp | 98 +++++++++++++++++++++++++++---------- indra/newview/gltf/llgltfloader.h | 2 + 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index ce87a9594f..bb9cedb262 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -118,6 +118,8 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } + mTransform.setIdentity(); + mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); @@ -147,33 +149,44 @@ bool LLGLTFLoader::parseMeshes() if (meshidx >= 0) { - LLModel* pModel = new LLModel(volume_params, 0.f); - auto mesh = mGLTFAsset.mMeshes[meshidx]; - - if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) - { - mModelList.push_back(pModel); - LLVector3 mesh_scale_vector = LLVector3(node.mScale); - LLVector3 mesh_translation_vector = LLVector3(node.mTranslation); - - LLMatrix4 mesh_translation; - mesh_translation.setTranslation(mesh_translation_vector); - mesh_translation *= transform; - transform = LLMatrix4((float*)&node.mAssetMatrix[0][0]); - /* - LLMatrix4 mesh_scale; - mesh_scale.initScale(mesh_scale_vector); - mesh_scale *= transform; - transform = mesh_scale; - */ - - mScene[transform].push_back(LLModelInstance(pModel, node.mName, transform, mats)); - } - else + if (mGLTFAsset.mMeshes.size() > meshidx) { - setLoadState(ERROR_MODEL + pModel->getStatus()); - delete (pModel); - return false; + LLModel* pModel = new LLModel(volume_params, 0.f); + auto mesh = mGLTFAsset.mMeshes[meshidx]; + if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) + { + mModelList.push_back(pModel); + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(LLVector3(node.mTranslation)); + mesh_translation *= mTransform; + mTransform = mesh_translation; + mTransform.condition(); + + LLMatrix4 mesh_rotation; + mesh_rotation.initRotation(LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3])); + mesh_rotation *= mTransform; + mTransform = mesh_rotation; + mTransform.condition(); + + LLMatrix4 mesh_scale; + mesh_scale.initScale(LLVector3(glm::abs(node.mScale))); + mesh_scale *= mTransform; + mTransform = mesh_scale; + mTransform.condition(); + + LLVector3 mesh_scale_vector = LLVector3(node.mScale); + LLVector3 mesh_translation_vector = LLVector3(node.mTranslation); + + transform = mTransform; + + mScene[transform].push_back(LLModelInstance(pModel, pModel->mLabel, transform, mats)); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete (pModel); + return false; + } } } } @@ -243,11 +256,42 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); mats["mat" + std::to_string(prim.mMaterial)] = impMat; } + else { + LL_INFOS() << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorBadElement"; + mWarningsArray.append(args); + return false; + } } return true; } -/* + +void LLGLTFLoader::processPrimitive(const LL::GLTF::Primitive& primitive, const LL::GLTF::Node& node) +{ + LLMatrix4 translation; + translation.setTranslation(LLVector3(node.mTranslation)); + translation *= mTransform; + mTransform = translation; + mTransform.condition(); + + LLMatrix4 rotation; + rotation = LLMatrix4(LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3])); + rotation *= mTransform; + mTransform = rotation; + mTransform.condition(); + + LLMatrix4 scale; + scale.initScale(LLVector3(glm::abs(node.mScale))); + scale *= mTransform; + mTransform = scale; + mTransform.condition(); + + if (primitive.mPositions.size()) { + } +} + /* LLModel::EModelStatus loadFaceFromGLTFModel(LLModel* pModel, const LL::GLTF::Mesh& mesh, material_map& mats, LLSD& log_msg) { LLVolumeFace face; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index cfb545be6f..32275d168d 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -168,6 +168,8 @@ private: bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map& mats); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + void processPrimitive(const LL::GLTF::Primitive& primitive, const LL::GLTF::Node& node); + // bool mPreprocessGLTF; /* Below inherited from dae loader - unknown if/how useful here -- cgit v1.2.3 From 3a4a2a525fe667250d014ad5d09a84a5c35aedf9 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Sun, 4 May 2025 19:22:01 -0400 Subject: Fix normals --- indra/newview/gltf/llgltfloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index bb9cedb262..3f5dbb3f87 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -227,7 +227,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & { GLTFVertex vert; vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); - vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[2][i]); + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], prim.mTexCoords0[i][1]); vertices.push_back(vert); } -- cgit v1.2.3 From e87c62940656aa740ddfa20bb2eed7a13cdb59c7 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Sun, 4 May 2025 21:06:10 -0400 Subject: Transforms transmigrofied --- indra/newview/gltf/llgltfloader.cpp | 75 ++++++++++++++++--------------------- indra/newview/gltf/llgltfloader.h | 2 - 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3f5dbb3f87..3f58f512e5 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -118,7 +118,6 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } - mTransform.setIdentity(); mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); @@ -141,9 +140,10 @@ bool LLGLTFLoader::parseMeshes() LLVolumeParams volume_params; volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + mTransform.setIdentity(); for (auto node : mGLTFAsset.mNodes) { - LLMatrix4 transform; + LLMatrix4 transformation; material_map mats; auto meshidx = node.mMesh; @@ -156,30 +156,42 @@ bool LLGLTFLoader::parseMeshes() if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { mModelList.push_back(pModel); - LLMatrix4 mesh_translation; - mesh_translation.setTranslation(LLVector3(node.mTranslation)); - mesh_translation *= mTransform; - mTransform = mesh_translation; - mTransform.condition(); + LLMatrix4 saved_transform = mTransform; - LLMatrix4 mesh_rotation; - mesh_rotation.initRotation(LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3])); - mesh_rotation *= mTransform; - mTransform = mesh_rotation; + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); + mTransform *= gltf_transform; mTransform.condition(); - LLMatrix4 mesh_scale; - mesh_scale.initScale(LLVector3(glm::abs(node.mScale))); - mesh_scale *= mTransform; - mTransform = mesh_scale; - mTransform.condition(); + 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); - LLVector3 mesh_scale_vector = LLVector3(node.mScale); - LLVector3 mesh_translation_vector = LLVector3(node.mTranslation); + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transformation; + transformation = mesh_translation; - transform = mTransform; + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transformation; + transformation = mesh_scale; + + if (transformation.determinant() < 0) + { // negative scales are not supported + LL_INFOS() << "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); + + } - mScene[transform].push_back(LLModelInstance(pModel, pModel->mLabel, transform, mats)); + mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); + stretch_extents(pModel, transformation); + mTransform = saved_transform; } else { @@ -268,29 +280,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & return true; } -void LLGLTFLoader::processPrimitive(const LL::GLTF::Primitive& primitive, const LL::GLTF::Node& node) -{ - LLMatrix4 translation; - translation.setTranslation(LLVector3(node.mTranslation)); - translation *= mTransform; - mTransform = translation; - mTransform.condition(); - - LLMatrix4 rotation; - rotation = LLMatrix4(LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3])); - rotation *= mTransform; - mTransform = rotation; - mTransform.condition(); - - LLMatrix4 scale; - scale.initScale(LLVector3(glm::abs(node.mScale))); - scale *= mTransform; - mTransform = scale; - mTransform.condition(); - - if (primitive.mPositions.size()) { - } -} /* LLModel::EModelStatus loadFaceFromGLTFModel(LLModel* pModel, const LL::GLTF::Mesh& mesh, material_map& mats, LLSD& log_msg) { diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 32275d168d..cfb545be6f 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -168,8 +168,6 @@ private: bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map& mats); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); - void processPrimitive(const LL::GLTF::Primitive& primitive, const LL::GLTF::Node& node); - // bool mPreprocessGLTF; /* Below inherited from dae loader - unknown if/how useful here -- cgit v1.2.3 From dd74b361e35a4e2516fee0b16ca0acf00da58547 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Sun, 4 May 2025 23:40:10 -0400 Subject: Fix import rotation and UVs --- indra/llprimitive/llmodelloader.h | 1 + indra/newview/gltf/llgltfloader.cpp | 241 +++++------------------------------- indra/newview/gltf/llgltfloader.h | 2 +- 3 files changed, 35 insertions(+), 209 deletions(-) diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 530e61e2b8..2dd0058ba6 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -111,6 +111,7 @@ public: bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info) model_list mModelList; + // The scene is pretty much what ends up getting loaded for upload. Basically assign things to this guy if you want something uploaded. scene mScene; typedef std::queue > model_queue; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3f58f512e5..3c723c7acd 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -153,15 +153,20 @@ bool LLGLTFLoader::parseMeshes() { LLModel* pModel = new LLModel(volume_params, 0.f); auto mesh = mGLTFAsset.mMeshes[meshidx]; - if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) + if (populateModelFromMesh(pModel, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { mModelList.push_back(pModel); LLMatrix4 saved_transform = mTransform; - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); + mTransform *= gltf_transform; mTransform.condition(); + // GLTF is +Y up, SL is +Z up + LLMatrix4 rotation; + rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); + mTransform *= rotation; + transformation = mTransform; // adjust the transformation to compensate for mesh normalization LLVector3 mesh_scale_vector; @@ -206,7 +211,7 @@ bool LLGLTFLoader::parseMeshes() return true; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map &mats) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& node, material_map& mats) { pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); @@ -222,10 +227,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 LLVolumeFace face; LLVolumeFace::VertexMapData::PointMap point_map; - + std::vector vertices; std::vector indices; - + LLImportMaterial impMat; LL::GLTF::Material* material = nullptr; @@ -240,7 +245,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & GLTFVertex vert; vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], prim.mTexCoords0[i][1]); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); vertices.push_back(vert); } @@ -250,12 +255,29 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & } std::vector faceVertices; - + glm::vec3 min = glm::vec3(0); + glm::vec3 max = glm::vec3(0); for (U32 i = 0; i < vertices.size(); i++) { LLVolumeFace::VertexData vert; - 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); + + if (vertices[i].position.x > max.x) + max.x = vertices[i].position.x; + if (vertices[i].position.y > max.y) + max.y = vertices[i].position.y; + if (vertices[i].position.z > max.z) + max.z = vertices[i].position.z; + + + if (vertices[i].position.x < min.x) + min.x = vertices[i].position.x; + if (vertices[i].position.y < min.y) + min.y = vertices[i].position.y; + if (vertices[i].position.z < min.z) + min.z = vertices[i].position.z; + + 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); @@ -263,6 +285,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & } 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("mat" + std::to_string(prim.mMaterial)); @@ -280,205 +304,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & return true; } - /* -LLModel::EModelStatus loadFaceFromGLTFModel(LLModel* pModel, const LL::GLTF::Mesh& mesh, material_map& mats, LLSD& log_msg) -{ - LLVolumeFace face; - std::vector verts; - std::vector indices; - - S32 pos_offset = -1; - S32 tc_offset = -1; - S32 norm_offset = -1; - - auto pos_source = mesh.mPrimitives[0].mPositions; - auto tc_source = mesh.mPrimitives[0].mNormals; - auto norm_source = mesh.mPrimitives[0].mTexCoords0; - - S32 idx_stride = 0; - - if (pos_source.size() > USHRT_MAX) - { - LL_WARNS() << "Unable to process mesh due to 16-bit index limits; invalid model; invalid model." << LL_ENDL; - LLSD args; - args["Message"] = "ParsingErrorBadElement"; - log_msg.append(args); - return LLModel::BAD_ELEMENT; - - } - - std::vector idx = mesh.mPrimitives[0].mIndexArray; - - domListOfFloats dummy; - domListOfFloats& v = pos_source ? pos_source->getFloat_array()->getValue() : dummy; - domListOfFloats& tc = tc_source ? tc_source->getFloat_array()->getValue() : dummy; - domListOfFloats& n = norm_source ? norm_source->getFloat_array()->getValue() : dummy; - - if (pos_source.size() == 0) - { - return LLModel::BAD_ELEMENT; - } - - // VFExtents change - face.mExtents[0].set(pos_source[0][0], pos_source[0][1], pos_source[0][2]); - face.mExtents[1].set(pos_source[0][0], pos_source[0][1], pos_source[0][2]); - - LLVolumeFace::VertexMapData::PointMap point_map; - - if (idx_stride <= 0 || (pos_source && pos_offset >= idx_stride) || (tc_source && tc_offset >= idx_stride) || - (norm_source && norm_offset >= idx_stride)) - { - // Looks like these offsets should fit inside idx_stride - // Might be good idea to also check idx.getCount()%idx_stride != 0 - LL_WARNS() << "Invalid pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL; - return LLModel::BAD_ELEMENT; - } - - for (U32 i = 0; i < idx.getCount(); i += idx_stride) - { - LLVolumeFace::VertexData cv; - if (pos_source) - { - cv.setPosition( - LLVector4a((F32)v[idx[i + pos_offset] * 3 + 0], (F32)v[idx[i + pos_offset] * 3 + 1], (F32)v[idx[i + pos_offset] * 3 + 2])); - } - - if (tc_source) - { - cv.mTexCoord.setVec((F32)tc[idx[i + tc_offset] * 2 + 0], (F32)tc[idx[i + tc_offset] * 2 + 1]); - } - - if (norm_source) - { - cv.setNormal(LLVector4a((F32)n[idx[i + norm_offset] * 3 + 0], - (F32)n[idx[i + norm_offset] * 3 + 1], - (F32)n[idx[i + norm_offset] * 3 + 2])); - } - - bool found = false; - - LLVolumeFace::VertexMapData::PointMap::iterator point_iter; - point_iter = point_map.find(LLVector3(cv.getPosition().getF32ptr())); - - if (point_iter != point_map.end()) - { - for (U32 j = 0; j < point_iter->second.size(); ++j) - { - // We have a matching loc - // - if ((point_iter->second)[j] == cv) - { - U16 shared_index = (point_iter->second)[j].mIndex; - - // Don't share verts within the same tri, degenerate - // - U32 indx_size = static_cast(indices.size()); - U32 verts_new_tri = indx_size % 3; - if ((verts_new_tri < 1 || indices[indx_size - 1] != shared_index) && - (verts_new_tri < 2 || indices[indx_size - 2] != shared_index)) - { - found = true; - indices.push_back(shared_index); - } - break; - } - } - } - - if (!found) - { - // VFExtents change - update_min_max(face.mExtents[0], face.mExtents[1], cv.getPosition()); - verts.push_back(cv); - if (verts.size() >= 65535) - { - // llerrs << "Attempted to write model exceeding 16-bit index buffer limitation." << LL_ENDL; - return LLModel::VERTEX_NUMBER_OVERFLOW; - } - U16 index = (U16)(verts.size() - 1); - indices.push_back(index); - - LLVolumeFace::VertexMapData d; - d.setPosition(cv.getPosition()); - d.mTexCoord = cv.mTexCoord; - d.setNormal(cv.getNormal()); - d.mIndex = index; - if (point_iter != point_map.end()) - { - point_iter->second.push_back(d); - } - else - { - point_map[LLVector3(d.getPosition().getF32ptr())].push_back(d); - } - } - - if (indices.size() % 3 == 0 && verts.size() >= 65532) - { - std::string material; - - if (tri->getMaterial()) - { - material = std::string(tri->getMaterial()); - } - - materials.push_back(material); - face_list.push_back(face); - face_list.rbegin()->fillFromLegacyData(verts, indices); - LLVolumeFace& new_face = *face_list.rbegin(); - if (!norm_source) - { - // ll_aligned_free_16(new_face.mNormals); - new_face.mNormals = NULL; - } - - if (!tc_source) - { - // ll_aligned_free_16(new_face.mTexCoords); - new_face.mTexCoords = NULL; - } - - face = LLVolumeFace(); - // VFExtents change - face.mExtents[0].set((F32)v[0], (F32)v[1], (F32)v[2]); - face.mExtents[1].set((F32)v[0], (F32)v[1], (F32)v[2]); - - verts.clear(); - indices.clear(); - point_map.clear(); - } - } - - if (!verts.empty()) - { - std::string material; - - if (tri->getMaterial()) - { - material = std::string(tri->getMaterial()); - } - - materials.push_back(material); - face_list.push_back(face); - - face_list.rbegin()->fillFromLegacyData(verts, indices); - LLVolumeFace& new_face = *face_list.rbegin(); - if (!norm_source) - { - // ll_aligned_free_16(new_face.mNormals); - new_face.mNormals = NULL; - } - - if (!tc_source) - { - // ll_aligned_free_16(new_face.mTexCoords); - new_face.mTexCoords = NULL; - } - } - - return LLModel::NO_ERRORS; -} -*/ bool LLGLTFLoader::parseMaterials() { return true; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index cfb545be6f..044cc262d7 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -165,7 +165,7 @@ private: void uploadMeshes(); bool parseMaterials(); void uploadMaterials(); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map& mats); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); // bool mPreprocessGLTF; -- cgit v1.2.3 From 4dedd13e61c6ba287c9145d232b6adec6f4bb7e3 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 5 May 2025 00:18:12 -0400 Subject: Support bulk material uploads. --- indra/newview/llfloatermodelpreview.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 8332a430e6..96f9ddbf8a 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -64,6 +64,7 @@ #include "llcallbacklist.h" #include "llviewertexteditor.h" #include "llviewernetwork.h" +#include "llmaterialeditor.h" //static @@ -113,6 +114,11 @@ void LLMeshFilePicker::notify(const std::vector& filenames) if (filenames.size() > 0) { mMP->loadModel(filenames[0], mLOD); + + if (filenames[0].substr(filenames[0].length() - 4) == ".glb" || filenames[0].substr(filenames[0].length() - 5) == ".gltf") + { + LLMaterialEditor::loadMaterialFromFile(filenames[0], -1); + } } else { -- cgit v1.2.3 From 629e56d864726eaa0340be220be8c3e3f500ca32 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 5 May 2025 05:06:46 -0400 Subject: Make sure transformation matricies are actually setup. Start getting joints in. --- indra/newview/gltf/llgltfloader.cpp | 75 ++++++++++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 1 + 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3c723c7acd..26257dfc33 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -26,6 +26,7 @@ #include "llgltfloader.h" #include "meshoptimizer.h" +#include // Import & define single-header gltf import/export lib #define TINYGLTF_IMPLEMENTATION @@ -118,7 +119,6 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } - mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); @@ -141,6 +141,14 @@ bool LLGLTFLoader::parseMeshes() volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); mTransform.setIdentity(); + + // 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) + { + populateJointFromSkin(skin); + } + for (auto node : mGLTFAsset.mNodes) { LLMatrix4 transformation; @@ -157,10 +165,12 @@ bool LLGLTFLoader::parseMeshes() { mModelList.push_back(pModel); LLMatrix4 saved_transform = mTransform; - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); - mTransform *= gltf_transform; - mTransform.condition(); + // This will make sure the matrix is always valid from the node. + node.makeMatrixValid(); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); + mTransform = gltf_transform; // GLTF is +Y up, SL is +Z up LLMatrix4 rotation; @@ -168,6 +178,7 @@ bool LLGLTFLoader::parseMeshes() mTransform *= rotation; transformation = mTransform; + // adjust the transformation to compensate for mesh normalization LLVector3 mesh_scale_vector; LLVector3 mesh_translation_vector; @@ -182,7 +193,7 @@ bool LLGLTFLoader::parseMeshes() mesh_scale.initScale(mesh_scale_vector); mesh_scale *= transformation; transformation = mesh_scale; - + if (transformation.determinant() < 0) { // negative scales are not supported LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " @@ -210,12 +221,25 @@ bool LLGLTFLoader::parseMeshes() return true; } + +void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) +{ + for (auto joint : skin.mJoints) + { + auto jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + } +} -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& node, material_map& mats) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); + auto skinIdx = nodeno.mSkin; + auto prims = mesh.mPrimitives; for (auto prim : prims) { @@ -284,6 +308,45 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& faceVertices.push_back(vert); } + if (skinIdx >= 0) + { + auto skin = mGLTFAsset.mSkins[skinIdx]; + + for (int i = 0; i < prim.mJoints.size(); 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; + } + glm::u16vec4 joint; + if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) + { + auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); + joint = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); + } + else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) + { + joint = glm::unpackUint4x16(prim.mJoints[i]); + } + + // Look up the joint index in the skin + auto jointIndex0 = skin.mJoints[joint.x]; + auto jointIndex1 = skin.mJoints[joint.y]; + auto jointIndex2 = skin.mJoints[joint.z]; + auto jointIndex3 = skin.mJoints[joint.w]; + + // Get the nodes for these joints. + auto node0 = mGLTFAsset.mNodes[jointIndex0]; + auto node1 = mGLTFAsset.mNodes[jointIndex1]; + auto node2 = mGLTFAsset.mNodes[jointIndex2]; + auto node3 = mGLTFAsset.mNodes[jointIndex3]; + + } + } + 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); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 044cc262d7..9d0560de63 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -166,6 +166,7 @@ private: bool parseMaterials(); void uploadMaterials(); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); + void populateJointFromSkin(const LL::GLTF::Skin& skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); // bool mPreprocessGLTF; -- cgit v1.2.3 From 47a5c7a41340a18d90a5a8724762ff32f9ac8afd Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 5 May 2025 16:12:26 -0400 Subject: Make sure we're pushing to the joints name set as well. --- indra/llprimitive/llmodelloader.h | 5 +++++ indra/newview/gltf/llgltfloader.cpp | 1 + 2 files changed, 6 insertions(+) diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 2dd0058ba6..73ec0ed1f4 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -120,9 +120,14 @@ public: model_queue mPhysicsQ; //map of avatar joints as named in COLLADA assets to internal joint names + // Do not use this for anything other than looking up the name of a joint. This is populated elsewhere. JointMap mJointMap; + + // The joint list is what you want to use to actually setup the specific joint transformations. JointTransformMap& mJointList; JointNameSet& mJointsFromNode; + + U32 mMaxJointsPerMesh; LLModelLoader( diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 26257dfc33..7f3f158fd7 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -230,6 +230,7 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) jointNode.makeMatrixValid(); mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + mJointsFromNode.push_front(jointNode.mName); } } -- cgit v1.2.3 From b3d1a1cb1600ca34994050a14cdfe9e474e57d43 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 5 May 2025 20:21:45 +0300 Subject: GLTF import texture upload --- indra/newview/gltf/llgltfloader.cpp | 573 +++++++++++++++++++++++++++++------- 1 file changed, 466 insertions(+), 107 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7f3f158fd7..15ed5f40ed 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -122,10 +122,8 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); - /* mMaterialsLoaded = parseMaterials(); if (mMaterialsLoaded) uploadMaterials(); - */ setLoadState(DONE); @@ -178,7 +176,7 @@ bool LLGLTFLoader::parseMeshes() mTransform *= rotation; transformation = mTransform; - + // adjust the transformation to compensate for mesh normalization LLVector3 mesh_scale_vector; LLVector3 mesh_translation_vector; @@ -193,7 +191,7 @@ bool LLGLTFLoader::parseMeshes() mesh_scale.initScale(mesh_scale_vector); mesh_scale *= transformation; transformation = mesh_scale; - + if (transformation.determinant() < 0) { // negative scales are not supported LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " @@ -233,7 +231,7 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) mJointsFromNode.push_front(jointNode.mName); } } - + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { pModel->mLabel = mesh.mName; @@ -252,25 +250,98 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 LLVolumeFace face; LLVolumeFace::VertexMapData::PointMap point_map; - + std::vector vertices; std::vector indices; - - LLImportMaterial impMat; - - LL::GLTF::Material* material = nullptr; - if (prim.mMaterial >= 0) - material = &mGLTFAsset.mMaterials[prim.mMaterial]; + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color - impMat.mDiffuseColor = LLColor4::white; + // 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) + { + S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; + if (texIndex < mGLTFAsset.mTextures.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()) + { + // 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; + } + } + else if (image.mTexture.notNull()) + { + // No URI but we have a texture, use it directly + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + } + else if (image.mBufferView >= 0) + { + // For embedded textures (no URI but has buffer data) + // Create a pseudo filename for the embedded texture + std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; + impMat.mDiffuseMapFilename = pseudo_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; + + // Mark for loading + mNumOfFetchingTextures++; + LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + } + } + } + } + } for (U32 i = 0; i < prim.getVertexCount(); i++) { GLTFVertex vert; vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); - vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); vertices.push_back(vert); } @@ -286,19 +357,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LLVolumeFace::VertexData vert; - if (vertices[i].position.x > max.x) + if (i == 0 || vertices[i].position.x > max.x) max.x = vertices[i].position.x; - if (vertices[i].position.y > max.y) + if (i == 0 || vertices[i].position.y > max.y) max.y = vertices[i].position.y; - if (vertices[i].position.z > max.z) + if (i == 0 || vertices[i].position.z > max.z) max.z = vertices[i].position.z; - - if (vertices[i].position.x < min.x) + if (i == 0 || vertices[i].position.x < min.x) min.x = vertices[i].position.x; - if (vertices[i].position.y < min.y) + if (i == 0 || vertices[i].position.y < min.y) min.y = vertices[i].position.y; - if (vertices[i].position.z < min.z) + if (i == 0 || vertices[i].position.z < min.z) min.z = vertices[i].position.z; LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); @@ -353,8 +423,26 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); pModel->getVolumeFaces().push_back(face); - pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); - mats["mat" + std::to_string(prim.mMaterial)] = impMat; + + // 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; + + if (materialName.empty()) + { + materialName = "mat" + std::to_string(prim.mMaterial); + } + } + else + { + materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + } + + pModel->getMaterialList().push_back(materialName); + mats[materialName] = impMat; } else { LL_INFOS() << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; @@ -370,56 +458,70 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& bool LLGLTFLoader::parseMaterials() { - return true; - /* if (!mGltfLoaded) return false; // fill local texture data structures mSamplers.clear(); - for (auto in_sampler : mGltfModel.samplers) + for (auto& in_sampler : mGLTFAsset.mSamplers) { gltf_sampler sampler; - sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR; - sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;; - sampler.wrapS = in_sampler.wrapS; - sampler.wrapT = in_sampler.wrapT; - sampler.name = in_sampler.name; // unused + 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 : mGltfModel.images) + for (auto& in_image : mGLTFAsset.mImages) { gltf_image image; - image.numChannels = in_image.component; - image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes - image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc - image.size = static_cast(in_image.image.size()); - image.height = in_image.height; - image.width = in_image.width; - image.data = in_image.image.data(); - - if (in_image.as_is) + 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) { - LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; - return false; + image.height = in_image.mTexture->getHeight(); + image.width = in_image.mTexture->getWidth(); + // Since we don't have direct access to the raw data, we'll use the dimensions to calculate size + if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) + { + image.size = static_cast(image.height * image.width * image.numChannels * image.bytesPerChannel); + } + } + else + { + // Fallback to provided dimensions + image.height = in_image.mHeight; + image.width = in_image.mWidth; + if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) + { + image.size = static_cast(image.height * image.width * image.numChannels * image.bytesPerChannel); + } } - if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) + // If we couldn't determine the size, skip this image + if (image.size == 0) { - LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; - return false; + LL_WARNS("GLTF_IMPORT") << "Image size could not be determined" << LL_ENDL; + continue; } + // We don't have direct access to the image data, so data pointer remains nullptr + image.data = nullptr; mImages.push_back(image); } mTextures.clear(); - for (auto in_tex : mGltfModel.textures) + for (auto& in_tex : mGLTFAsset.mTextures) { gltf_texture tex; - tex.imageIdx = in_tex.source; - tex.samplerIdx = in_tex.sampler; + tex.imageIdx = in_tex.mSource; + tex.samplerIdx = in_tex.mSampler; tex.imageUuid.setNull(); if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) @@ -432,58 +534,96 @@ bool LLGLTFLoader::parseMaterials() } // parse each material - for (tinygltf::Material gltf_material : mGltfModel.materials) + mMaterials.clear(); + for (const auto& gltf_material : mGLTFAsset.mMaterials) { gltf_render_material mat; - mat.name = gltf_material.name; - - tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; - mat.hasPBR = true; // Always true, for now - - mat.baseColor.set(pbr.baseColorFactor.data()); - mat.hasBaseTex = pbr.baseColorTexture.index >= 0; - mat.baseColorTexIdx = pbr.baseColorTexture.index; - mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; - - mat.metalness = pbr.metallicFactor; - mat.roughness = pbr.roughnessFactor; - mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; - mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; - mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; - - mat.normalScale = gltf_material.normalTexture.scale; - mat.hasNormalTex = gltf_material.normalTexture.index >= 0; - mat.normalTexIdx = gltf_material.normalTexture.index; - mat.normalTexCoords = gltf_material.normalTexture.texCoord; - - mat.occlusionScale = gltf_material.occlusionTexture.strength; - mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; - mat.occlusionTexIdx = gltf_material.occlusionTexture.index; - mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; - - mat.emissiveColor.set(gltf_material.emissiveFactor.data()); - mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; - mat.emissiveTexIdx = gltf_material.emissiveTexture.index; - mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; - - mat.alphaMode = gltf_material.alphaMode; - mat.alphaMask = gltf_material.alphaCutoff; - - 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()))) + 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; } - if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV - (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || - (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || - (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || - (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) + // 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; @@ -493,7 +633,6 @@ bool LLGLTFLoader::parseMaterials() } return true; - */ } // TODO: convert raw vertex buffers to UUIDs @@ -505,18 +644,34 @@ void LLGLTFLoader::uploadMeshes() // convert raw image buffers to texture UUIDs & assemble into a render material void LLGLTFLoader::uploadMaterials() { - for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple + LL_INFOS("GLTF_IMPORT") << "Uploading materials, count: " << mMaterials.size() << LL_ENDL; + + for (gltf_render_material& mat : mMaterials) { - if (mat.hasBaseTex) + LL_INFOS("GLTF_IMPORT") << "Processing material: " << mat.name << LL_ENDL; + + // Process base color texture + if (mat.hasBaseTex && mat.baseColorTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; if (gtex.imageUuid.isNull()) { + LL_INFOS("GLTF_IMPORT") << "Loading base color texture for material " << mat.name << LL_ENDL; gtex.imageUuid = imageBufferToTextureUUID(gtex); + + if (gtex.imageUuid.notNull()) + { + LL_INFOS("GLTF_IMPORT") << "Base color texture loaded, ID: " << gtex.imageUuid.asString() << LL_ENDL; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to load base color texture for material " << mat.name << LL_ENDL; + } } } - if (mat.hasMRTex) + // Process other textures similarly + if (mat.hasMRTex && mat.metalRoughTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; if (gtex.imageUuid.isNull()) @@ -525,7 +680,7 @@ void LLGLTFLoader::uploadMaterials() } } - if (mat.hasNormalTex) + if (mat.hasNormalTex && mat.normalTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.normalTexIdx]; if (gtex.imageUuid.isNull()) @@ -534,7 +689,7 @@ void LLGLTFLoader::uploadMaterials() } } - if (mat.hasOcclusionTex) + if (mat.hasOcclusionTex && mat.occlusionTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; if (gtex.imageUuid.isNull()) @@ -543,7 +698,7 @@ void LLGLTFLoader::uploadMaterials() } } - if (mat.hasEmissiveTex) + if (mat.hasEmissiveTex && mat.emissiveTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; if (gtex.imageUuid.isNull()) @@ -552,18 +707,222 @@ void LLGLTFLoader::uploadMaterials() } } } + + // Update material map for all model instances to ensure textures are properly associated + // mScene is a std::map, not an array, so we need to iterate through it correctly + for (auto& scene_entry : mScene) + { + for (LLModelInstance& instance : scene_entry.second) + { + LLModel* model = instance.mModel; + + if (model) + { + for (size_t i = 0; i < model->getMaterialList().size(); ++i) + { + const std::string& matName = model->getMaterialList()[i]; + if (!matName.empty()) + { + // Ensure this material exists in the instance's material map + if (instance.mMaterial.find(matName) == instance.mMaterial.end()) + { + // Find material in our render materials + for (const auto& renderMat : mMaterials) + { + if (renderMat.name == matName) + { + // Create an import material from the render material + LLImportMaterial impMat; + impMat.mDiffuseColor = renderMat.baseColor; + + // Set diffuse texture if available + if (renderMat.hasBaseTex && renderMat.baseColorTexIdx < mTextures.size()) + { + const gltf_texture& gtex = mTextures[renderMat.baseColorTexIdx]; + if (!gtex.imageUuid.isNull()) + { + impMat.setDiffuseMap(gtex.imageUuid); + LL_INFOS("GLTF_IMPORT") << "Setting texture " << gtex.imageUuid.asString() << " for material " << matName << LL_ENDL; + } + } + + // Add material to instance's material map + instance.mMaterial[matName] = impMat; + LL_INFOS("GLTF_IMPORT") << "Added material " << matName << " to instance" << LL_ENDL; + break; + } + } + } + } + } + } + } + } } LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) { - //gltf_image& image = mImages[tex.imageIdx]; - //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) + { + LL_WARNS("GLTF_IMPORT") << "Invalid texture indices in imageBufferToTextureUUID" << LL_ENDL; + return LLUUID::null; + } - // fill an LLSD container with image+sampler data + gltf_image& image = mImages[tex.imageIdx]; + gltf_sampler& sampler = mSamplers[tex.samplerIdx]; - // upload texture + 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::GLTF::Image& source_image = mGLTFAsset.mImages[sourceIndex]; + + // 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(); + } + + // Create an import material to pass to the texture load function + LLImportMaterial material; + + // Try to get the texture filename from the URI + if (!source_image.mUri.empty()) + { + std::string filename = source_image.mUri; + + // Extract just the filename from the URI + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + { + filename = filename.substr(pos + 1); + } + + material.mDiffuseMapFilename = filename; + material.mDiffuseMapLabel = filename; + } + else if (source_image.mBufferView >= 0) + { + // For embedded textures, create a pseudo-filename + std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; + material.mDiffuseMapFilename = pseudo_filename; + material.mDiffuseMapLabel = pseudo_filename; + } + else + { + LL_WARNS("GLTF_IMPORT") << "No URI or buffer data for image" << LL_ENDL; + return LLUUID::null; + } + + // Create LLSD container with image and sampler data for texture upload + LLSD texture_data = LLSD::emptyMap(); + + // Image data + texture_data["width"] = LLSD::Integer(image.width); + texture_data["height"] = LLSD::Integer(image.height); + texture_data["components"] = LLSD::Integer(image.numChannels); + texture_data["bytes_per_component"] = LLSD::Integer(image.bytesPerChannel); + texture_data["pixel_type"] = LLSD::Integer(image.pixelType); + + // Sampler data + texture_data["min_filter"] = LLSD::Integer(sampler.minFilter); + texture_data["mag_filter"] = LLSD::Integer(sampler.magFilter); + texture_data["wrap_s"] = LLSD::Integer(sampler.wrapS); + texture_data["wrap_t"] = LLSD::Integer(sampler.wrapT); + + // Add URI for reference + if (!source_image.mUri.empty()) + { + texture_data["uri"] = source_image.mUri; + } + + // Check if we have a buffer view for embedded data + if (source_image.mBufferView >= 0) + { + texture_data["has_embedded_data"] = LLSD::Boolean(true); + texture_data["buffer_view"] = LLSD::Integer(source_image.mBufferView); + + // Extract embedded data for texture loading + if (source_image.mBufferView < mGLTFAsset.mBufferViews.size()) + { + const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[source_image.mBufferView]; + 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); + + 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; - // retrieve UUID + // Flag to track if texture was loaded immediately + bool texture_loaded = false; + + // Call texture loading function with our import material + if (mTextureLoadFunc) + { + // Increment textures to fetch counter BEFORE calling load function + mNumOfFetchingTextures++; + + U32 result = mTextureLoadFunc(material, mOpaqueData); + + // 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; + + if (material.getDiffuseMap().notNull()) + { + LL_INFOS("GLTF_IMPORT") << "Texture loaded successfully, ID: " << material.getDiffuseMap().asString() << 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()); + } + + 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; } + -- cgit v1.2.3 From 5e8f871aea8f5a4393ab8f9080c227ea1bf46c21 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 6 May 2025 18:13:24 +0300 Subject: Apply 1/100th scale to the vertex positions --- indra/newview/gltf/llgltfloader.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 15ed5f40ed..08d396d1e2 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -338,8 +338,16 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (U32 i = 0; i < prim.getVertexCount(); i++) { + // Apply scaling directly to the vertex positions as they're read from the file + const float DIRECT_SCALE = 0.01f; // 1/100th scale + GLTFVertex vert; - vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); + vert.position = glm::vec3( + prim.mPositions[i][0] * DIRECT_SCALE, + prim.mPositions[i][1] * DIRECT_SCALE, + prim.mPositions[i][2] * DIRECT_SCALE + ); + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); vertices.push_back(vert); -- cgit v1.2.3 From 2efe514f14a0f0b405599301a14413edfce873ee Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 14 May 2025 10:33:01 -0400 Subject: Make pulling weights per vertex. --- indra/newview/gltf/llgltfloader.cpp | 88 +++++++++++++++++++++++-------------- indra/newview/gltf/llgltfloader.h | 2 + 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7f3f158fd7..2db803ef3e 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -271,6 +271,32 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); vert.uv0 = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); + + if (skinIdx >= 0) + { + 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]); + } + + vert.weights = glm::vec4(prim.mWeights[i]); + } + vertices.push_back(vert); } @@ -282,6 +308,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::vector faceVertices; glm::vec3 min = glm::vec3(0); glm::vec3 max = glm::vec3(0); + for (U32 i = 0; i < vertices.size(); i++) { LLVolumeFace::VertexData vert; @@ -307,45 +334,42 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.setNormal(normal); vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); faceVertices.push_back(vert); - } - if (skinIdx >= 0) - { - auto skin = mGLTFAsset.mSkins[skinIdx]; + + // create list of weights that influence this vertex + LLModel::weight_list weight_list; - for (int i = 0; i < prim.mJoints.size(); 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; - } - glm::u16vec4 joint; - if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + + std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + + + std::vector wght; + F32 total = 0.f; + + for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) + { // take up to 4 most significant weights + // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. + if (weight_list[i].mWeight > 0.f) { - auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); - joint = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); + wght.push_back(weight_list[i]); + total += weight_list[i].mWeight; } - else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) + } + + F32 scale = 1.f / total; + if (scale != 1.f) + { // normalize weights + for (U32 i = 0; i < wght.size(); ++i) { - joint = glm::unpackUint4x16(prim.mJoints[i]); + wght[i].mWeight *= scale; } - - // Look up the joint index in the skin - auto jointIndex0 = skin.mJoints[joint.x]; - auto jointIndex1 = skin.mJoints[joint.y]; - auto jointIndex2 = skin.mJoints[joint.z]; - auto jointIndex3 = skin.mJoints[joint.w]; - - // Get the nodes for these joints. - auto node0 = mGLTFAsset.mNodes[jointIndex0]; - auto node1 = mGLTFAsset.mNodes[jointIndex1]; - auto node2 = mGLTFAsset.mNodes[jointIndex2]; - auto node3 = mGLTFAsset.mNodes[jointIndex3]; - } + + pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } face.fillFromLegacyData(faceVertices, indices); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 9d0560de63..519f2d8a07 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -144,6 +144,8 @@ class LLGLTFLoader : public LLModelLoader glm::vec3 position; glm::vec3 normal; glm::vec2 uv0; + glm::u16vec4 joints; + glm::vec4 weights; }; protected: -- cgit v1.2.3 From b572bf75968005388fab8ad0b3259d417c7f224c Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 14 May 2025 19:59:20 +0300 Subject: Improve mesh scaling --- indra/newview/gltf/llgltfloader.cpp | 148 ++++++++++++++++++++++++++++-------- indra/newview/gltf/llgltfloader.h | 3 +- 2 files changed, 120 insertions(+), 31 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 52147ad5c3..2064fa4dd9 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -147,6 +147,96 @@ bool LLGLTFLoader::parseMeshes() populateJointFromSkin(skin); } + /* Two-pass mesh processing approach: + * 1. First pass: Calculate global bounds across all meshes to determine proper scaling + * and centering for the entire model. This ensures consistent normalization. + * 2. Second pass: Process each mesh node with the calculated global scale factor and + * center offset, ensuring the entire model is properly proportioned and centered. + */ + + // First pass: Calculate global bounds across all meshes in the model + LLVector3 global_min_bounds(FLT_MAX, FLT_MAX, FLT_MAX); + LLVector3 global_max_bounds(-FLT_MAX, -FLT_MAX, -FLT_MAX); + bool has_geometry = false; + + // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up + LLMatrix4 coord_system_rotation; + coord_system_rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); + + // Gather bounds from all meshes + for (auto node : mGLTFAsset.mNodes) + { + auto meshidx = node.mMesh; + if (meshidx >= 0 && meshidx < mGLTFAsset.mMeshes.size()) + { + auto mesh = mGLTFAsset.mMeshes[meshidx]; + + // Make node matrix valid for correct transformation + node.makeMatrixValid(); + + // Apply coordinate system rotation to node transform + LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix)); + LLMatrix4 node_transform = coord_system_rotation; + node_transform *= node_matrix; + + // Examine all primitives in this mesh + for (auto prim : mesh.mPrimitives) + { + if (prim.getVertexCount() >= USHRT_MAX) + continue; + + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + // Transform vertex position by node transform with coordinate system rotation + LLVector4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); + LLVector4 transformed_pos = pos * node_transform; + + global_min_bounds.mV[VX] = llmin(global_min_bounds.mV[VX], transformed_pos.mV[VX]); + global_min_bounds.mV[VY] = llmin(global_min_bounds.mV[VY], transformed_pos.mV[VY]); + global_min_bounds.mV[VZ] = llmin(global_min_bounds.mV[VZ], transformed_pos.mV[VZ]); + + global_max_bounds.mV[VX] = llmax(global_max_bounds.mV[VX], transformed_pos.mV[VX]); + global_max_bounds.mV[VY] = llmax(global_max_bounds.mV[VY], transformed_pos.mV[VY]); + global_max_bounds.mV[VZ] = llmax(global_max_bounds.mV[VZ], transformed_pos.mV[VZ]); + + has_geometry = true; + } + } + } + } + + // Calculate model dimensions and center point for the entire model + F32 global_scale_factor = 1.0f; + LLVector3 global_center_offset(0.0f, 0.0f, 0.0f); + + if (has_geometry) + { + // Calculate bounding box center - this will be our new origin + LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f; + global_center_offset = -center; // Offset to move center to origin + + // Calculate diagonal length of the bounding box + LLVector3 dimensions = global_max_bounds - global_min_bounds; + F32 diagonal = dimensions.length(); + + // Always scale to the target size to ensure consistent bounding box + const F32 TARGET_SIZE = 1.0f; // Target diagonal size for models in meters + + if (diagonal > 0.0f) + { + // Calculate scale factor to normalize model size to TARGET_SIZE + global_scale_factor = TARGET_SIZE / diagonal; + + LL_INFOS("GLTF_IMPORT") << "Model dimensions: " << dimensions.mV[VX] << "x" + << dimensions.mV[VY] << "x" << dimensions.mV[VZ] + << ", diagonal: " << diagonal + << ", applying global scale factor: " << global_scale_factor + << ", global centering offset: (" << global_center_offset.mV[VX] << ", " + << global_center_offset.mV[VY] << ", " << global_center_offset.mV[VZ] << ")" << LL_ENDL; + } + } + + // Second pass: Process each node with the global scale and offset for (auto node : mGLTFAsset.mNodes) { LLMatrix4 transformation; @@ -159,7 +249,9 @@ bool LLGLTFLoader::parseMeshes() { LLModel* pModel = new LLModel(volume_params, 0.f); auto mesh = mGLTFAsset.mMeshes[meshidx]; - if (populateModelFromMesh(pModel, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) + if (populateModelFromMesh(pModel, mesh, node, mats, global_scale_factor, global_center_offset) && + (LLModel::NO_ERRORS == pModel->getStatus()) && + validate_model(pModel)) { mModelList.push_back(pModel); LLMatrix4 saved_transform = mTransform; @@ -171,9 +263,7 @@ bool LLGLTFLoader::parseMeshes() mTransform = gltf_transform; // GLTF is +Y up, SL is +Z up - LLMatrix4 rotation; - rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); - mTransform *= rotation; + mTransform *= coord_system_rotation; transformation = mTransform; @@ -200,7 +290,6 @@ bool LLGLTFLoader::parseMeshes() args["Message"] = "NegativeScaleNormTrans"; args["LABEL"] = pModel->mLabel; mWarningsArray.append(args); - } mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); @@ -220,19 +309,8 @@ bool LLGLTFLoader::parseMeshes() return true; } -void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) -{ - for (auto joint : skin.mJoints) - { - auto jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); - - mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); - mJointsFromNode.push_front(jointNode.mName); - } -} - -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, + const F32 scale_factor, const LLVector3& center_offset) { pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); @@ -336,20 +414,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } + // Apply the global scale and center offset to all vertices for (U32 i = 0; i < prim.getVertexCount(); i++) { - // Apply scaling directly to the vertex positions as they're read from the file - const float DIRECT_SCALE = 0.01f; // 1/100th scale - GLTFVertex vert; vert.position = glm::vec3( - prim.mPositions[i][0] * DIRECT_SCALE, - prim.mPositions[i][1] * DIRECT_SCALE, - prim.mPositions[i][2] * DIRECT_SCALE + (prim.mPositions[i][0] + center_offset.mV[VX]) * scale_factor, + (prim.mPositions[i][1] + center_offset.mV[VY]) * scale_factor, + (prim.mPositions[i][2] + center_offset.mV[VZ]) * scale_factor ); - vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); - vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); + + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); if (skinIdx >= 0) { @@ -357,7 +433,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; if (accessorIdx >= 0) { - auto accessor = mGLTFAsset.mAccessors[accessorIdx]; + auto accessor = mGLTFAsset.mAccessors[accessorIdx]; componentType = accessor.mComponentType; } @@ -412,7 +488,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); faceVertices.push_back(vert); - + // create list of weights that influence this vertex LLModel::weight_list weight_list; @@ -423,7 +499,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - + std::vector wght; F32 total = 0.f; @@ -487,6 +563,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& return true; } +void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) +{ + for (auto joint : skin.mJoints) + { + auto jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + mJointsFromNode.push_front(jointNode.mName); + } +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 519f2d8a07..bfcdccac00 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -167,7 +167,8 @@ private: void uploadMeshes(); bool parseMaterials(); void uploadMaterials(); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, + const F32 scale_factor = 1.0f, const LLVector3& center_offset = LLVector3()); void populateJointFromSkin(const LL::GLTF::Skin& skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); -- cgit v1.2.3 From 3569cc1993dc03637b29fcd77fe5b01b6ea372fb Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 14 May 2025 22:04:55 +0300 Subject: Use correct model dimensions and bounding box rotation --- indra/newview/gltf/llgltfloader.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2064fa4dd9..c63459b172 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -174,9 +174,8 @@ bool LLGLTFLoader::parseMeshes() // Make node matrix valid for correct transformation node.makeMatrixValid(); - // Apply coordinate system rotation to node transform LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix)); - LLMatrix4 node_transform = coord_system_rotation; + LLMatrix4 node_transform; node_transform *= node_matrix; // Examine all primitives in this mesh @@ -187,7 +186,7 @@ bool LLGLTFLoader::parseMeshes() for (U32 i = 0; i < prim.getVertexCount(); i++) { - // Transform vertex position by node transform with coordinate system rotation + // Transform vertex position by node transform LLVector4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); LLVector4 transformed_pos = pos * node_transform; @@ -215,21 +214,23 @@ bool LLGLTFLoader::parseMeshes() LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f; global_center_offset = -center; // Offset to move center to origin - // Calculate diagonal length of the bounding box + // Calculate dimensions of the bounding box LLVector3 dimensions = global_max_bounds - global_min_bounds; - F32 diagonal = dimensions.length(); + + // Find the maximum dimension rather than the diagonal + F32 max_dimension = llmax(dimensions.mV[VX], llmax(dimensions.mV[VY], dimensions.mV[VZ])); // Always scale to the target size to ensure consistent bounding box - const F32 TARGET_SIZE = 1.0f; // Target diagonal size for models in meters + const F32 TARGET_SIZE = 1.0f; // Target size for maximum dimension in meters - if (diagonal > 0.0f) + if (max_dimension > 0.0f) { - // Calculate scale factor to normalize model size to TARGET_SIZE - global_scale_factor = TARGET_SIZE / diagonal; + // Calculate scale factor to normalize model's maximum dimension to TARGET_SIZE + global_scale_factor = TARGET_SIZE / max_dimension; LL_INFOS("GLTF_IMPORT") << "Model dimensions: " << dimensions.mV[VX] << "x" << dimensions.mV[VY] << "x" << dimensions.mV[VZ] - << ", diagonal: " << diagonal + << ", max dimension: " << max_dimension << ", applying global scale factor: " << global_scale_factor << ", global centering offset: (" << global_center_offset.mV[VX] << ", " << global_center_offset.mV[VY] << ", " << global_center_offset.mV[VZ] << ")" << LL_ENDL; -- cgit v1.2.3 From 47d95f1eae379f16ed0ae320c9bb1a6002186e32 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 16 May 2025 19:10:23 -0400 Subject: White space. --- indra/newview/gltf/llgltfloader.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 52147ad5c3..7570c33dcf 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -174,24 +174,19 @@ bool LLGLTFLoader::parseMeshes() LLMatrix4 rotation; rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); mTransform *= rotation; - 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 (transformation.determinant() < 0) { // negative scales are not supported LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " @@ -390,21 +385,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (U32 i = 0; i < vertices.size(); i++) { LLVolumeFace::VertexData vert; - if (i == 0 || vertices[i].position.x > max.x) max.x = vertices[i].position.x; if (i == 0 || vertices[i].position.y > max.y) max.y = vertices[i].position.y; if (i == 0 || vertices[i].position.z > max.z) max.z = vertices[i].position.z; - if (i == 0 || vertices[i].position.x < min.x) min.x = vertices[i].position.x; if (i == 0 || vertices[i].position.y < min.y) min.y = vertices[i].position.y; if (i == 0 || vertices[i].position.z < min.z) min.z = vertices[i].position.z; - 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); @@ -422,8 +414,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - - std::vector wght; F32 total = 0.f; -- cgit v1.2.3 From a9c75d8136f9df6885e525ea8f2b383fe4a22593 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 16 May 2025 19:12:40 -0400 Subject: More white space. --- indra/newview/gltf/llgltfloader.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7570c33dcf..d9cee8b2ea 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -403,8 +403,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.setNormal(normal); vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); faceVertices.push_back(vert); - - // create list of weights that influence this vertex LLModel::weight_list weight_list; -- cgit v1.2.3 From 875a4180803aa6903bb13263a63e02b38552b742 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 15 May 2025 18:28:25 +0300 Subject: #4080 Import GLTF skin data --- indra/llprimitive/llmodelloader.cpp | 2 + indra/newview/gltf/llgltfloader.cpp | 85 ++++++++++++++++++++-- .../skins/default/xui/en/floater_model_preview.xml | 1 + 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 7facd53a72..8f86b90b69 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -150,6 +150,8 @@ void LLModelLoader::run() { mWarningsArray.clear(); doLoadModel(); + // todo: we are inside of a thread, push this into main thread worker, + // not into doOnIdleOneTime that laks tread safety doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this)); } diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 11c1b05ee5..8d109b610c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -142,7 +142,7 @@ bool LLGLTFLoader::parseMeshes() // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. - for (auto skin : mGLTFAsset.mSkins) + for (auto& skin : mGLTFAsset.mSkins) { populateJointFromSkin(skin); } @@ -164,7 +164,7 @@ bool LLGLTFLoader::parseMeshes() coord_system_rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); // Gather bounds from all meshes - for (auto node : mGLTFAsset.mNodes) + for (auto &node : mGLTFAsset.mNodes) { auto meshidx = node.mMesh; if (meshidx >= 0 && meshidx < mGLTFAsset.mMeshes.size()) @@ -238,7 +238,7 @@ bool LLGLTFLoader::parseMeshes() } // Second pass: Process each node with the global scale and offset - for (auto node : mGLTFAsset.mNodes) + for (auto &node : mGLTFAsset.mNodes) { LLMatrix4 transformation; material_map mats; @@ -281,7 +281,7 @@ bool LLGLTFLoader::parseMeshes() transformation = mesh_scale; if (transformation.determinant() < 0) { // negative scales are not supported - LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + LL_INFOS("GLTF") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " << pModel->mLabel << LL_ENDL; LLSD args; args["Message"] = "NegativeScaleNormTrans"; @@ -292,6 +292,60 @@ bool LLGLTFLoader::parseMeshes() mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); stretch_extents(pModel, transformation); mTransform = saved_transform; + + S32 skin_index = node.mSkin; + if (skin_index >= 0 && mGLTFAsset.mSkins.size() > skin_index) + { + LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skin_index]; + 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") << "Bind matrices count mismatch joints count" << LL_ENDL; + LLSD args; + args["Message"] = "InvBindCountMismatch"; + mWarningsArray.append(args); + } + + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + std::string legal_name(jointNode.mName); + if (mJointMap.find(legal_name) != mJointMap.end()) + { + legal_name = mJointMap[legal_name]; + } + skin_info.mJointNames.push_back(legal_name); + skin_info.mJointNums.push_back(-1); + + if (i < gltf_skin.mInverseBindMatricesData.size()) + { + // Process bind matrix + LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; + LLMatrix4 gltf_transform(glm::value_ptr(gltf_mat)); + skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); + + LL_DEBUGS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + + // Translate based of mJointList + gltf_transform.setTranslation(mJointList[legal_name].getTranslation()); + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(gltf_transform)); + } + } + + // "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. + LLMatrix4 bind_shape; + bind_shape.setIdentity(); + skin_info.mBindShapeMatrix.loadu(bind_shape); + } } else { @@ -560,10 +614,29 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) for (auto joint : skin.mJoints) { auto jointNode = mGLTFAsset.mNodes[joint]; + + std::string legal_name(jointNode.mName); + if (mJointMap.find(legal_name) != mJointMap.end()) + { + legal_name = mJointMap[legal_name]; + } + else + { + LL_INFOS("GLTF") << "Rigged to unrecognized joint name : " + << legal_name << LL_ENDL; + LLSD args; + args["Message"] = "UnrecognizedJoint"; + args["[NAME]"] = legal_name; + mWarningsArray.append(args); + } + jointNode.makeMatrixValid(); - mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); - mJointsFromNode.push_front(jointNode.mName); + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + mJointList[legal_name] = gltf_transform; + mJointsFromNode.push_front(legal_name); + + LL_DEBUGS("GLTF") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; } } 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 90223fcda8..99c348c236 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -45,6 +45,7 @@ Rigged to unrecognized joint name [NAME] Skinning disabled due to [COUNT] unknown joints Model [MODEL_NAME] loaded + Bind matrices count mismatch joints count Texture coordinates data is not complete. Found NaN while loading position data from DAE-Model, invalid model. -- cgit v1.2.3 From a3386f8b3aa57281c526d543461620b82896fe8f Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 19 May 2025 22:30:37 +0300 Subject: #4102 Allow selecting gltf models on mac --- indra/newview/llfilepicker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 09a2206cd7..150b5481ca 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -671,6 +671,8 @@ std::unique_ptr> LLFilePicker::navOpenFilterProc(ELoadF case FFLOAD_HDRI: allowedv->push_back("exr"); case FFLOAD_MODEL: + allowedv->push_back("gltf"); + allowedv->push_back("glb"); case FFLOAD_COLLADA: allowedv->push_back("dae"); break; -- cgit v1.2.3 From a58adfff8f4af25196352b140bab35d209edf7b0 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 20 May 2025 23:59:06 +0300 Subject: #4105 Support .gltf and .glb files in lookupLODModelFiles --- indra/newview/llmodelpreview.cpp | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 7ccd299135..2d568ecb8b 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -3090,25 +3090,48 @@ void LLModelPreview::lookupLODModelFiles(S32 lod) S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS; std::string lod_filename = mLODFile[LLModel::LOD_HIGH]; - std::string ext = ".dae"; std::string lod_filename_lower(lod_filename); LLStringUtil::toLower(lod_filename_lower); - std::string::size_type i = lod_filename_lower.rfind(ext); - if (i != std::string::npos) + + // Check for each supported file extension + std::vector supported_exts = { ".dae", ".gltf", ".glb" }; + std::string found_ext; + std::string::size_type ext_pos = std::string::npos; + + for (const auto& ext : supported_exts) { - lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext); + std::string::size_type i = lod_filename_lower.rfind(ext); + if (i != std::string::npos) + { + ext_pos = i; + found_ext = ext; + break; + } } - if (gDirUtilp->fileExists(lod_filename)) + + if (ext_pos != std::string::npos) { - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - if (fmp) + // Replace extension with LOD suffix + original extension + std::string lod_file_to_check = lod_filename; + lod_file_to_check.replace(ext_pos, found_ext.size(), getLodSuffix(next_lod) + found_ext); + + if (gDirUtilp->fileExists(lod_file_to_check)) + { + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (fmp) + { + fmp->setCtrlLoadFromFile(next_lod); + } + loadModel(lod_file_to_check, next_lod); + } + else { - fmp->setCtrlLoadFromFile(next_lod); + lookupLODModelFiles(next_lod); } - loadModel(lod_filename, next_lod); } else { + // No recognized extension found, continue with next LOD lookupLODModelFiles(next_lod); } } -- cgit v1.2.3 From a9e9c03762f176e0f930d74ccfc96e3c04112b13 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 20 May 2025 20:10:14 +0300 Subject: #4080 Rigged mesh support #2 --- indra/newview/gltf/llgltfloader.cpp | 248 ++++++++++++++++++++++++------------ 1 file changed, 164 insertions(+), 84 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 8d109b610c..a5fb4108c9 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -208,7 +208,8 @@ bool LLGLTFLoader::parseMeshes() F32 global_scale_factor = 1.0f; LLVector3 global_center_offset(0.0f, 0.0f, 0.0f); - if (has_geometry) + if (has_geometry + && mJointList.empty()) // temporary disable offset and scaling for rigged meshes { // Calculate bounding box center - this will be our new origin LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f; @@ -292,60 +293,6 @@ bool LLGLTFLoader::parseMeshes() mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); stretch_extents(pModel, transformation); mTransform = saved_transform; - - S32 skin_index = node.mSkin; - if (skin_index >= 0 && mGLTFAsset.mSkins.size() > skin_index) - { - LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skin_index]; - 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") << "Bind matrices count mismatch joints count" << LL_ENDL; - LLSD args; - args["Message"] = "InvBindCountMismatch"; - mWarningsArray.append(args); - } - - for (size_t i = 0; i < jointCnt; ++i) - { - // Process joint name and idnex - S32 joint = gltf_skin.mJoints[i]; - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); - - std::string legal_name(jointNode.mName); - if (mJointMap.find(legal_name) != mJointMap.end()) - { - legal_name = mJointMap[legal_name]; - } - skin_info.mJointNames.push_back(legal_name); - skin_info.mJointNums.push_back(-1); - - if (i < gltf_skin.mInverseBindMatricesData.size()) - { - // Process bind matrix - LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - LLMatrix4 gltf_transform(glm::value_ptr(gltf_mat)); - skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); - - LL_DEBUGS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; - - // Translate based of mJointList - gltf_transform.setTranslation(mJointList[legal_name].getTranslation()); - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(gltf_transform)); - } - } - - // "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. - LLMatrix4 bind_shape; - bind_shape.setIdentity(); - skin_info.mBindShapeMatrix.loadu(bind_shape); - } } else { @@ -366,7 +313,33 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); - auto skinIdx = nodeno.mSkin; + S32 skinIdx = nodeno.mSkin; + + // Mark unsuported joints with '-1' so that they won't get added into weights + // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. + std::vector gltf_joint_index_use_count; + 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); + + S32 replacement_index = 0; + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + std::string legal_name(jointNode.mName); + if (mJointMap.find(legal_name) == mJointMap.end()) + { + gltf_joint_index_use_count[i] = -1; // mark as unsupported + } + } + } auto prims = mesh.mPrimitives; for (auto prim : prims) @@ -536,39 +509,70 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); faceVertices.push_back(vert); - // create list of weights that influence this vertex - LLModel::weight_list weight_list; - - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + if (skinIdx >= 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_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]++; + } - std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - std::vector wght; - F32 total = 0.f; + std::vector wght; + F32 total = 0.f; - for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) - { // take up to 4 most significant weights - // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. - if (weight_list[i].mWeight > 0.f) + for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) { + // 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[i]); total += weight_list[i].mWeight; } - } - F32 scale = 1.f / total; - if (scale != 1.f) - { // normalize weights - for (U32 i = 0; i < wght.size(); ++i) + if (total != 0.f) + { + F32 scale = 1.f / total; + if (scale != 1.f) + { // normalize weights + for (U32 i = 0; i < wght.size(); ++i) + { + wght[i].mWeight *= scale; + } + } + } + + if (wght.size() > 0) { - wght[i].mWeight *= scale; + pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } } - - pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } face.fillFromLegacyData(faceVertices, indices); @@ -606,6 +610,85 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } + // Fill joint names, bind matrices and prepare to remap weight indices + if (skinIdx >= 0) + { + 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") << "Bind matrices count mismatch joints count" << LL_ENDL; + LLSD args; + args["Message"] = "InvBindCountMismatch"; + mWarningsArray.append(args); + } + + std::vector gltfindex_to_joitindex_map; + gltfindex_to_joitindex_map.resize(jointCnt); + + S32 replacement_index = 0; + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use_count[i] <= 0) + { + // Unused (0) or unsupported (-1) joint, drop it + continue; + } + LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + std::string legal_name(jointNode.mName); + if (mJointMap.find(legal_name) != mJointMap.end()) + { + legal_name = mJointMap[legal_name]; + } + else + { + llassert(false); // should have been stopped by gltf_joint_index_use_count[i] == -1 + continue; + } + gltfindex_to_joitindex_map[i] = replacement_index++; + + skin_info.mJointNames.push_back(legal_name); + skin_info.mJointNums.push_back(-1); + + if (i < gltf_skin.mInverseBindMatricesData.size()) + { + // Process bind matrix + LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; + LLMatrix4 gltf_transform(glm::value_ptr(gltf_mat)); + skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); + + LL_INFOS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + + // Translate based of mJointList + gltf_transform.setTranslation(mJointList[legal_name].getTranslation()); // name is supposed to be in mJointList + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(gltf_transform)); + } + } + + // "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. + LLMatrix4 bind_shape; + bind_shape.setIdentity(); + skin_info.mBindShapeMatrix.loadu(bind_shape); + + // Remap indices for pModel->mSkinWeights + for (auto& weights : pModel->mSkinWeights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; + } + } + } + return true; } @@ -622,12 +705,9 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) } else { - LL_INFOS("GLTF") << "Rigged to unrecognized joint name : " - << legal_name << LL_ENDL; - LLSD args; - args["Message"] = "UnrecognizedJoint"; - args["[NAME]"] = legal_name; - mWarningsArray.append(args); + // ignore unrecognized joint + LL_DEBUGS("GLTF") << "Ignoring joing: " << legal_name << LL_ENDL; + continue; } jointNode.makeMatrixValid(); -- cgit v1.2.3 From 18182b137a6ad99c314b43c0cd388d73248a0e41 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 21 May 2025 20:09:15 -0400 Subject: Fix for crash when loading texutres on a GLTF mesh. --- indra/newview/gltf/asset.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index beccb02bd4..d4d935ecc0 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -957,12 +957,13 @@ bool Image::prep(Asset& asset) { // embedded in a buffer, load the texture from the buffer BufferView& bufferView = asset.mBufferViews[mBufferView]; Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; - - U8* data = buffer.mData.data() + bufferView.mByteOffset; - - mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); - - if (mTexture.isNull()) + if (mLoadIntoTexturePipe) + { + U8* data = buffer.mData.data() + bufferView.mByteOffset; + + mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); + } + else if (mTexture.isNull()) { LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -977,7 +978,7 @@ bool Image::prep(Asset& asset) std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri; LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file); - if (tracking_id.notNull()) + if (tracking_id.notNull() && mLoadIntoTexturePipe) { LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); mTexture = LLViewerTextureManager::getFetchedTexture(world_id); -- cgit v1.2.3 From 30b72ba4106a3d818666e9a6e8a0aad5fbc0c7cb Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 21 May 2025 20:43:45 -0400 Subject: Make sure we're guarding texture uploads to the GPU properly If we're not loading a texture, most of this should not be necessary. --- indra/newview/gltf/asset.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index d4d935ecc0..4e66bf2cbf 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -963,7 +963,7 @@ bool Image::prep(Asset& asset) mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); } - else if (mTexture.isNull()) + else if (mTexture.isNull() && mLoadIntoTexturePipe) { LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -983,7 +983,7 @@ bool Image::prep(Asset& asset) LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); mTexture = LLViewerTextureManager::getFetchedTexture(world_id); } - else + else if (mLoadIntoTexturePipe) { LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -998,7 +998,7 @@ bool Image::prep(Asset& asset) return false; } - if (!asset.mFilename.empty()) + if (!asset.mFilename.empty() && mLoadIntoTexturePipe) { // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW); mTexture->forceToSaveRawImage(0, F32_MAX); -- cgit v1.2.3 From 30aa14d0b3f4e87580982e8a8f936bad1283e21a Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 21 May 2025 21:00:06 -0400 Subject: Make loading the asset into VRAM optional. --- indra/newview/gltf/asset.cpp | 145 +++++++++++++++++++----------------- indra/newview/gltf/asset.h | 7 +- indra/newview/gltf/llgltfloader.cpp | 2 +- indra/newview/gltfscenemanager.cpp | 2 +- 4 files changed, 81 insertions(+), 75 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 4e66bf2cbf..55b275ecb4 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -516,7 +516,7 @@ bool Asset::prep() for (auto& image : mImages) { - if (!image.prep(*this)) + if (!image.prep(*this, mLoadIntoVRAM)) { return false; } @@ -545,101 +545,103 @@ bool Asset::prep() return false; } } + if (mLoadIntoVRAM) + { + // prepare vertex buffers - // prepare vertex buffers - - // material count is number of materials + 1 for default material - U32 mat_count = (U32) mMaterials.size() + 1; - - if (LLGLSLShader::sCurBoundShaderPtr == nullptr) - { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer - gDebugProgram.bind(); - } + // material count is number of materials + 1 for default material + U32 mat_count = (U32) mMaterials.size() + 1; - for (S32 double_sided = 0; double_sided < 2; ++double_sided) - { - RenderData& rd = mRenderData[double_sided]; - for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) - { - rd.mBatches[i].resize(mat_count); + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); } - // for each material - for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) + for (S32 double_sided = 0; double_sided < 2; ++double_sided) { - // for each shader variant - U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; - U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; - - S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; - if (ds_mat != double_sided) + RenderData& rd = mRenderData[double_sided]; + for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) { - continue; + rd.mBatches[i].resize(mat_count); } - for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) + // for each material + for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) { - U32 attribute_mask = 0; - // for each mesh - for (auto& mesh : mMeshes) + // for each shader variant + U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + + S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; + if (ds_mat != double_sided) { - // for each primitive - for (auto& primitive : mesh.mPrimitives) + continue; + } + + for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) + { + U32 attribute_mask = 0; + // for each mesh + for (auto& mesh : mMeshes) { - if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + // for each primitive + for (auto& primitive : mesh.mPrimitives) { - // accumulate vertex and index counts - primitive.mVertexOffset = vertex_count[variant]; - primitive.mIndexOffset = index_count[variant]; + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + { + // accumulate vertex and index counts + primitive.mVertexOffset = vertex_count[variant]; + primitive.mIndexOffset = index_count[variant]; - vertex_count[variant] += primitive.getVertexCount(); - index_count[variant] += primitive.getIndexCount(); + vertex_count[variant] += primitive.getVertexCount(); + index_count[variant] += primitive.getIndexCount(); - // all primitives of a given variant and material should all have the same attribute mask - llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); - attribute_mask |= primitive.mAttributeMask; + // all primitives of a given variant and material should all have the same attribute mask + llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); + attribute_mask |= primitive.mAttributeMask; + } } } - } - // allocate vertex buffer and pack it - if (vertex_count[variant] > 0) - { - U32 mat_idx = mat_id + 1; - #if 0 - LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); + // allocate vertex buffer and pack it + if (vertex_count[variant] > 0) + { + U32 mat_idx = mat_id + 1; + #if 0 + LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); - rd.mBatches[variant][mat_idx].mVertexBuffer = vb; - vb->allocateBuffer(vertex_count[variant], - index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used - vb->setBuffer(); + rd.mBatches[variant][mat_idx].mVertexBuffer = vb; + vb->allocateBuffer(vertex_count[variant], + index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used + vb->setBuffer(); - for (auto& mesh : mMeshes) - { - for (auto& primitive : mesh.mPrimitives) + for (auto& mesh : mMeshes) { - if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + for (auto& primitive : mesh.mPrimitives) { - primitive.upload(vb); + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + { + primitive.upload(vb); + } } } - } - vb->unmapBuffer(); + vb->unmapBuffer(); - vb->unbind(); - #endif + vb->unbind(); + #endif + } } } } - } - // sanity check that all primitives have a vertex buffer - for (auto& mesh : mMeshes) - { - for (auto& primitive : mesh.mPrimitives) + // sanity check that all primitives have a vertex buffer + for (auto& mesh : mMeshes) { - //llassert(primitive.mVertexBuffer.notNull()); + for (auto& primitive : mesh.mPrimitives) + { + //llassert(primitive.mVertexBuffer.notNull()); + } } } #if 0 @@ -678,9 +680,10 @@ Asset::Asset(const Value& src) *this = src; } -bool Asset::load(std::string_view filename) +bool Asset::load(std::string_view filename, bool loadIntoVRAM) { LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; + mLoadIntoVRAM = loadIntoVRAM; mFilename = filename; std::string ext = gDirUtilp->getExtension(mFilename); @@ -698,7 +701,7 @@ bool Asset::load(std::string_view filename) } else if (ext == "glb") { - return loadBinary(str); + return loadBinary(str, mLoadIntoVRAM); } else { @@ -715,8 +718,9 @@ bool Asset::load(std::string_view filename) return false; } -bool Asset::loadBinary(const std::string& data) +bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM) { + mLoadIntoVRAM = loadIntoVRAM; // load from binary gltf const U8* ptr = (const U8*)data.data(); const U8* end = ptr + data.size(); @@ -941,8 +945,9 @@ void Asset::eraseBufferView(S32 bufferView) LLViewerFetchedTexture* fetch_texture(const LLUUID& id); -bool Image::prep(Asset& asset) +bool Image::prep(Asset& asset, bool loadIntoVRAM) { + mLoadIntoTexturePipe = loadIntoVRAM; LLUUID id; if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull()) { // loaded from an asset, fetch the texture from the asset system diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index e70fffa986..3cec18268b 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -319,7 +319,7 @@ namespace LL // preserve only uri and name void clearData(Asset& asset); - bool prep(Asset& asset); + bool prep(Asset& asset, bool loadIntoVRAM); }; // Render Batch -- vertex buffer and list of primitives to render using @@ -394,6 +394,7 @@ namespace LL // UBO for storing material data U32 mMaterialsUBO = 0; + bool mLoadIntoVRAM = false; // prepare for first time use bool prep(); @@ -431,12 +432,12 @@ namespace LL // accepts .gltf and .glb files // Any existing data will be lost // returns result of prep() on success - bool load(std::string_view filename); + bool load(std::string_view filename, bool loadIntoVRAM); // load .glb contents from memory // data - binary contents of .glb file // returns result of prep() on success - bool loadBinary(const std::string& data); + bool loadBinary(const std::string& data, bool loadIntoVRAM); const Asset& operator=(const Value& src); void serialize(boost::json::object& dst) const; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index a5fb4108c9..22c7c5d13e 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -108,7 +108,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); - mGltfLoaded = mGLTFAsset.load(filename); + mGltfLoaded = mGLTFAsset.load(filename, false); if (!mGltfLoaded) { diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index 9faead9533..3cb5e9a0d7 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -317,7 +317,7 @@ void GLTFSceneManager::load(const std::string& filename) { std::shared_ptr asset = std::make_shared(); - if (asset->load(filename)) + if (asset->load(filename, true)) { gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions asset->updateTransforms(); -- cgit v1.2.3 From 3670cf64eb31d8e8f76db1006cf35a87fc3d5b54 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 21 May 2025 21:04:14 -0400 Subject: White space. --- indra/newview/gltf/asset.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 55b275ecb4..129661c195 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -965,7 +965,6 @@ bool Image::prep(Asset& asset, bool loadIntoVRAM) if (mLoadIntoTexturePipe) { U8* data = buffer.mData.data() + bufferView.mByteOffset; - mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); } else if (mTexture.isNull() && mLoadIntoTexturePipe) -- cgit v1.2.3 From fe84905b1762c821fbc0ef1db97b8df885e6cce1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 21 May 2025 21:53:59 +0300 Subject: #4128 Crash on mInvBindMatrix --- indra/newview/llskinningutil.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index cee43f3cff..43836a420b 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -135,6 +135,12 @@ void LLSkinningUtil::initSkinningMatrixPalette( initJointNums(const_cast(skin), avatar); + if (skin->mInvBindMatrix.size() < count ) + { + // faulty model? mInvBindMatrix.size() should have matched mJointNames.size() + return; + } + LLMatrix4a world[LL_CHARACTER_MAX_ANIMATED_JOINTS]; for (S32 j = 0; j < count; ++j) -- cgit v1.2.3 From 6a92f8777de3e5031509af7dca44d334881faf47 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 21 May 2025 22:06:51 +0300 Subject: Crash at LLVOVolume::isAnimatedObject --- indra/newview/llvovolume.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 63ff6bc79d..fb6aa7a326 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -3783,7 +3783,12 @@ bool LLVOVolume::canBeAnimatedObject() const bool LLVOVolume::isAnimatedObject() const { - LLVOVolume *root_vol = (LLVOVolume*)getRootEdit(); + LLViewerObject *root_obj = getRootEdit(); + if (root_obj->getPCode() != LL_PCODE_VOLUME) + { + return false; // at the moment only volumes can be animated + } + LLVOVolume* root_vol = (LLVOVolume*)root_obj; mIsAnimatedObject = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; return mIsAnimatedObject; } -- cgit v1.2.3 From 9d3a03e6da9604dc3d76ed0649c06c58beb22771 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 22 May 2025 14:58:13 +0300 Subject: #4118 Allow Bulk Upload to initialize on Bulk Upload All option --- indra/newview/llmaterialeditor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 28160177f6..378f5fdf91 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -137,7 +137,8 @@ LLFloaterComboOptions* LLFloaterComboOptions::showUI( { combo_picker->mComboOptions->addSimpleElement(*iter); } - combo_picker->mComboOptions->selectFirstItem(); + // select 'Bulk Upload All' option + combo_picker->mComboOptions->selectNthItem((S32)options.size() - 1); combo_picker->openFloater(LLSD(title)); combo_picker->setFocus(true); -- cgit v1.2.3 From f5d4d3f86224df61bacabf219b6d053bcdebc3ef Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 22 May 2025 19:53:35 +0300 Subject: #4109 Improve handling of GLTF transform hierarchy --- indra/newview/gltf/llgltfloader.cpp | 44 ++++++++++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 1 + 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 22c7c5d13e..871936db95 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -307,6 +307,34 @@ bool LLGLTFLoader::parseMeshes() return true; } +void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) +{ + auto& node = asset.mNodes[node_index]; + + // Start with this node's transform + glm::mat4 node_transform = node.mMatrix; + + // Find parent node and apply its transform if it exists + for (auto& other_node : asset.mNodes) + { + for (auto& child_index : other_node.mChildren) + { + if (child_index == node_index) + { + // Found a parent, recursively get its combined transform + glm::mat4 parent_transform; + computeCombinedNodeTransform(asset, static_cast(&other_node - &asset.mNodes[0]), parent_transform); + + // Apply parent transform to current node transform + node_transform = parent_transform * node_transform; + break; + } + } + } + + combined_transform = node_transform; +} + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, const F32 scale_factor, const LLVector3& center_offset) { @@ -438,14 +466,24 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } + // Compute combined transform for this node considering parent hierarchy + S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); + glm::mat4 combined_transform; + computeCombinedNodeTransform(mGLTFAsset, node_index, combined_transform); + // Apply the global scale and center offset to all vertices for (U32 i = 0; i < prim.getVertexCount(); i++) { + // Transform vertex position with combined hierarchy transform + glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); + glm::vec4 transformed_pos = combined_transform * pos; + + // Apply scaling and centering after hierarchy transform GLTFVertex vert; vert.position = glm::vec3( - (prim.mPositions[i][0] + center_offset.mV[VX]) * scale_factor, - (prim.mPositions[i][1] + center_offset.mV[VY]) * scale_factor, - (prim.mPositions[i][2] + center_offset.mV[VZ]) * scale_factor + (transformed_pos.x + center_offset.mV[VX]) * scale_factor, + (transformed_pos.y + center_offset.mV[VY]) * scale_factor, + (transformed_pos.z + center_offset.mV[VZ]) * scale_factor ); vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index bfcdccac00..4d02909f72 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -167,6 +167,7 @@ private: void uploadMeshes(); bool parseMaterials(); void uploadMaterials(); + void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, const F32 scale_factor = 1.0f, const LLVector3& center_offset = LLVector3()); void populateJointFromSkin(const LL::GLTF::Skin& skin); -- cgit v1.2.3 From d713be4bbe8c5d35aaeeff7744906f4fc1833643 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 23 May 2025 01:08:58 +0300 Subject: #4109 Use correct GLTF coordinate system rotation --- indra/newview/gltf/llgltfloader.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 871936db95..cfc55903d5 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -176,6 +176,7 @@ bool LLGLTFLoader::parseMeshes() LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix)); LLMatrix4 node_transform; + node_transform *= coord_system_rotation; // Apply coordinate rotation first node_transform *= node_matrix; // Examine all primitives in this mesh @@ -261,25 +262,24 @@ bool LLGLTFLoader::parseMeshes() // This will make sure the matrix is always valid from the node. node.makeMatrixValid(); - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); - mTransform = gltf_transform; - - // GLTF is +Y up, SL is +Z up - mTransform *= coord_system_rotation; - + 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 (transformation.determinant() < 0) { // negative scales are not supported LL_INFOS("GLTF") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " @@ -471,10 +471,16 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& glm::mat4 combined_transform; computeCombinedNodeTransform(mGLTFAsset, node_index, combined_transform); + // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up + glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + + // Apply coordinate system rotation to the combined transform + combined_transform = coord_system_rotation * combined_transform; + // Apply the global scale and center offset to all vertices for (U32 i = 0; i < prim.getVertexCount(); i++) { - // Transform vertex position with combined hierarchy transform + // Transform vertex position with combined hierarchy transform (including coord rotation) glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); glm::vec4 transformed_pos = combined_transform * pos; @@ -486,7 +492,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& (transformed_pos.z + center_offset.mV[VZ]) * scale_factor ); - vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + // Also rotate the normal vector + glm::vec4 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2], 0.0f); + glm::vec4 transformed_normal = coord_system_rotation * normal_vec; + vert.normal = glm::normalize(glm::vec3(transformed_normal)); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); if (skinIdx >= 0) -- cgit v1.2.3 From 71d543ff07dba5a530e5c96891ccb6629e2fa173 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 23 May 2025 02:07:05 +0300 Subject: #4109 #4080 Apply coordinate rotation to GLTF inverse bind matrices --- indra/newview/gltf/llgltfloader.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index cfc55903d5..f0dcbf9cf4 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -257,10 +257,6 @@ bool LLGLTFLoader::parseMeshes() validate_model(pModel)) { mModelList.push_back(pModel); - LLMatrix4 saved_transform = mTransform; - - // This will make sure the matrix is always valid from the node. - node.makeMatrixValid(); mTransform.setIdentity(); transformation = mTransform; @@ -292,7 +288,6 @@ bool LLGLTFLoader::parseMeshes() mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); stretch_extents(pModel, transformation); - mTransform = saved_transform; } else { @@ -708,7 +703,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { // Process bind matrix LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - LLMatrix4 gltf_transform(glm::value_ptr(gltf_mat)); + + // For inverse bind matrices, we need to: + // 1. Get the original bind matrix by inverting + // 2. Apply coordinate rotation to the original + // 3. Invert again to get the rotated inverse bind matrix + glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); + glm::mat4 coord_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + glm::mat4 rotated_original = coord_rotation * original_bind_matrix; + glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); + + LLMatrix4 gltf_transform(glm::value_ptr(rotated_inverse_bind_matrix)); skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); LL_INFOS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; @@ -742,6 +747,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) { + // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up + glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + for (auto joint : skin.mJoints) { auto jointNode = mGLTFAsset.mNodes[joint]; @@ -754,13 +762,17 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) else { // ignore unrecognized joint - LL_DEBUGS("GLTF") << "Ignoring joing: " << legal_name << LL_ENDL; + LL_DEBUGS("GLTF") << "Ignoring joint: " << legal_name << LL_ENDL; continue; } jointNode.makeMatrixValid(); - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + // Apply coordinate system rotation to joint transform + glm::mat4 gltf_joint_matrix = jointNode.mMatrix; + glm::mat4 rotated_joint_matrix = coord_system_rotation * gltf_joint_matrix; + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_joint_matrix)); mJointList[legal_name] = gltf_transform; mJointsFromNode.push_front(legal_name); -- cgit v1.2.3 From 8a9f09ea0ff03ead5d5040c45d7c0a56c325ec07 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 23 May 2025 02:59:47 +0300 Subject: #4109 #4080 Undistort GLTF rigged mesh in Z-up coordinate transformation --- indra/newview/gltf/llgltfloader.cpp | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index f0dcbf9cf4..f028c8cba8 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -716,11 +716,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LLMatrix4 gltf_transform(glm::value_ptr(rotated_inverse_bind_matrix)); skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); - LL_INFOS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; - // Translate based of mJointList - gltf_transform.setTranslation(mJointList[legal_name].getTranslation()); // name is supposed to be in mJointList - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(gltf_transform)); + // 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]; + jointNode.makeMatrixValid(); + LLMatrix4 original_joint_transform(glm::value_ptr(jointNode.mMatrix)); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } } @@ -750,6 +756,8 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL; + for (auto joint : skin.mJoints) { auto jointNode = mGLTFAsset.mNodes[joint]; @@ -768,15 +776,31 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) jointNode.makeMatrixValid(); - // Apply coordinate system rotation to joint 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++) + { + LL_INFOS("GLTF_DEBUG") << " [" << gltf_joint_matrix[i][0] << ", " << gltf_joint_matrix[i][1] + << ", " << gltf_joint_matrix[i][2] << ", " << gltf_joint_matrix[i][3] << "]" << LL_ENDL; + } + + // Apply coordinate system rotation to joint transform glm::mat4 rotated_joint_matrix = coord_system_rotation * gltf_joint_matrix; + // Debug: Log rotated joint matrix + LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' rotated matrix:" << LL_ENDL; + for(int i = 0; i < 4; i++) + { + 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; + } + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_joint_matrix)); mJointList[legal_name] = gltf_transform; mJointsFromNode.push_front(legal_name); - LL_DEBUGS("GLTF") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; } } -- cgit v1.2.3 From 4faebc08301f57e5191d29010c00e72748a45323 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sat, 24 May 2025 19:45:52 +0300 Subject: #4109 Remove workaround code --- indra/newview/gltf/llgltfloader.cpp | 109 +++--------------------------------- indra/newview/gltf/llgltfloader.h | 3 +- 2 files changed, 10 insertions(+), 102 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index f028c8cba8..c2211838d1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -147,100 +147,14 @@ bool LLGLTFLoader::parseMeshes() populateJointFromSkin(skin); } - /* Two-pass mesh processing approach: - * 1. First pass: Calculate global bounds across all meshes to determine proper scaling - * and centering for the entire model. This ensures consistent normalization. - * 2. Second pass: Process each mesh node with the calculated global scale factor and - * center offset, ensuring the entire model is properly proportioned and centered. - */ - - // First pass: Calculate global bounds across all meshes in the model - LLVector3 global_min_bounds(FLT_MAX, FLT_MAX, FLT_MAX); - LLVector3 global_max_bounds(-FLT_MAX, -FLT_MAX, -FLT_MAX); - bool has_geometry = false; - - // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up - LLMatrix4 coord_system_rotation; - coord_system_rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); - - // Gather bounds from all meshes - for (auto &node : mGLTFAsset.mNodes) - { - auto meshidx = node.mMesh; - if (meshidx >= 0 && meshidx < mGLTFAsset.mMeshes.size()) - { - auto mesh = mGLTFAsset.mMeshes[meshidx]; - - // Make node matrix valid for correct transformation - node.makeMatrixValid(); - - LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix)); - LLMatrix4 node_transform; - node_transform *= coord_system_rotation; // Apply coordinate rotation first - node_transform *= node_matrix; - - // Examine all primitives in this mesh - for (auto prim : mesh.mPrimitives) - { - if (prim.getVertexCount() >= USHRT_MAX) - continue; - - for (U32 i = 0; i < prim.getVertexCount(); i++) - { - // Transform vertex position by node transform - LLVector4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); - LLVector4 transformed_pos = pos * node_transform; - - global_min_bounds.mV[VX] = llmin(global_min_bounds.mV[VX], transformed_pos.mV[VX]); - global_min_bounds.mV[VY] = llmin(global_min_bounds.mV[VY], transformed_pos.mV[VY]); - global_min_bounds.mV[VZ] = llmin(global_min_bounds.mV[VZ], transformed_pos.mV[VZ]); - - global_max_bounds.mV[VX] = llmax(global_max_bounds.mV[VX], transformed_pos.mV[VX]); - global_max_bounds.mV[VY] = llmax(global_max_bounds.mV[VY], transformed_pos.mV[VY]); - global_max_bounds.mV[VZ] = llmax(global_max_bounds.mV[VZ], transformed_pos.mV[VZ]); - - has_geometry = true; - } - } - } - } - - // Calculate model dimensions and center point for the entire model - F32 global_scale_factor = 1.0f; - LLVector3 global_center_offset(0.0f, 0.0f, 0.0f); - - if (has_geometry - && mJointList.empty()) // temporary disable offset and scaling for rigged meshes + for (auto& node : mGLTFAsset.mNodes) { - // Calculate bounding box center - this will be our new origin - LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f; - global_center_offset = -center; // Offset to move center to origin - - // Calculate dimensions of the bounding box - LLVector3 dimensions = global_max_bounds - global_min_bounds; - - // Find the maximum dimension rather than the diagonal - F32 max_dimension = llmax(dimensions.mV[VX], llmax(dimensions.mV[VY], dimensions.mV[VZ])); - - // Always scale to the target size to ensure consistent bounding box - const F32 TARGET_SIZE = 1.0f; // Target size for maximum dimension in meters - - if (max_dimension > 0.0f) - { - // Calculate scale factor to normalize model's maximum dimension to TARGET_SIZE - global_scale_factor = TARGET_SIZE / max_dimension; - - LL_INFOS("GLTF_IMPORT") << "Model dimensions: " << dimensions.mV[VX] << "x" - << dimensions.mV[VY] << "x" << dimensions.mV[VZ] - << ", max dimension: " << max_dimension - << ", applying global scale factor: " << global_scale_factor - << ", global centering offset: (" << global_center_offset.mV[VX] << ", " - << global_center_offset.mV[VY] << ", " << global_center_offset.mV[VZ] << ")" << LL_ENDL; - } + // Make node matrix valid for correct transformation + node.makeMatrixValid(); } - // Second pass: Process each node with the global scale and offset - for (auto &node : mGLTFAsset.mNodes) + // Process each node + for (auto& node : mGLTFAsset.mNodes) { LLMatrix4 transformation; material_map mats; @@ -252,7 +166,7 @@ bool LLGLTFLoader::parseMeshes() { LLModel* pModel = new LLModel(volume_params, 0.f); auto mesh = mGLTFAsset.mMeshes[meshidx]; - if (populateModelFromMesh(pModel, mesh, node, mats, global_scale_factor, global_center_offset) && + if (populateModelFromMesh(pModel, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { @@ -292,7 +206,7 @@ bool LLGLTFLoader::parseMeshes() else { setLoadState(ERROR_MODEL + pModel->getStatus()); - delete (pModel); + delete pModel; return false; } } @@ -330,8 +244,7 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 combined_transform = node_transform; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, - const F32 scale_factor, const LLVector3& center_offset) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); @@ -481,11 +394,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Apply scaling and centering after hierarchy transform GLTFVertex vert; - vert.position = glm::vec3( - (transformed_pos.x + center_offset.mV[VX]) * scale_factor, - (transformed_pos.y + center_offset.mV[VY]) * scale_factor, - (transformed_pos.z + center_offset.mV[VZ]) * scale_factor - ); + vert.position = glm::vec3(transformed_pos.x, transformed_pos.y, transformed_pos.z); // Also rotate the normal vector glm::vec4 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2], 0.0f); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 4d02909f72..fac5b719fb 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -168,8 +168,7 @@ private: bool parseMaterials(); void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, - const F32 scale_factor = 1.0f, const LLVector3& center_offset = LLVector3()); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); void populateJointFromSkin(const LL::GLTF::Skin& skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); -- cgit v1.2.3 From 3eab945d904610d30d1efd6ab8dd25d17822dbc7 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sat, 24 May 2025 20:41:55 +0300 Subject: #4109 Refactor LLGLTFLoader::populateModelFromMesh() --- indra/newview/gltf/llgltfloader.cpp | 56 +++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c2211838d1..40dae9a7c3 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -251,6 +251,20 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& S32 skinIdx = nodeno.mSkin; + // Pre-compute coordinate system rotation matrix (GLTF Y-up to SL Z-up) + static const glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + + // Compute final combined transform matrix (hierarchy + coordinate rotation) + S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); + glm::mat4 hierarchy_transform; + computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform); + + // Combine transforms: coordinate rotation applied to hierarchy transform + const glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + + // Pre-compute normal transform matrix (transpose of inverse of upper-left 3x3) + const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(final_transform))); + // Mark unsuported joints with '-1' so that they won't get added into weights // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. std::vector gltf_joint_index_use_count; @@ -286,11 +300,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // 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; - LLVolumeFace::VertexMapData::PointMap point_map; - + LLVolumeFace face; std::vector vertices; - std::vector indices; + std::vector indices; LLImportMaterial impMat; impMat.mDiffuseColor = LLColor4::white; // Default color @@ -374,32 +386,19 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // Compute combined transform for this node considering parent hierarchy - S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); - glm::mat4 combined_transform; - computeCombinedNodeTransform(mGLTFAsset, node_index, combined_transform); - - // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up - glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - - // Apply coordinate system rotation to the combined transform - combined_transform = coord_system_rotation * combined_transform; - // Apply the global scale and center offset to all vertices for (U32 i = 0; i < prim.getVertexCount(); i++) { - // Transform vertex position with combined hierarchy transform (including coord rotation) + // 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 = combined_transform * pos; + glm::vec4 transformed_pos = final_transform * pos; - // Apply scaling and centering after hierarchy transform GLTFVertex vert; - vert.position = glm::vec3(transformed_pos.x, transformed_pos.y, transformed_pos.z); + vert.position = glm::vec3(transformed_pos); - // Also rotate the normal vector - glm::vec4 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2], 0.0f); - glm::vec4 transformed_normal = coord_system_rotation * normal_vec; - vert.normal = glm::normalize(glm::vec3(transformed_normal)); + // 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]); @@ -610,19 +609,14 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (i < gltf_skin.mInverseBindMatricesData.size()) { - // Process bind matrix + // Use pre-computed coord_system_rotation instead of recreating it LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - // For inverse bind matrices, we need to: - // 1. Get the original bind matrix by inverting - // 2. Apply coordinate rotation to the original - // 3. Invert again to get the rotated inverse bind matrix glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); - glm::mat4 coord_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - glm::mat4 rotated_original = coord_rotation * original_bind_matrix; + glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); - LLMatrix4 gltf_transform(glm::value_ptr(rotated_inverse_bind_matrix)); + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; -- cgit v1.2.3 From e8eac13b7ba238172c7d5d677cff744bff8dff6d Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sat, 24 May 2025 21:10:10 +0300 Subject: #4109 Refactor LLGLTFLoader::populateModelFromMesh() #2 --- indra/newview/gltf/llgltfloader.cpp | 49 +++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 40dae9a7c3..2e8521b597 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -434,25 +434,36 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& indices.push_back(prim.mIndexArray[i]); } + // Check for empty vertex array before processing + if (vertices.empty()) + { + LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL; + continue; // Skip this primitive + } + std::vector faceVertices; - glm::vec3 min = glm::vec3(0); - glm::vec3 max = glm::vec3(0); + 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; - if (i == 0 || vertices[i].position.x > max.x) - max.x = vertices[i].position.x; - if (i == 0 || vertices[i].position.y > max.y) - max.y = vertices[i].position.y; - if (i == 0 || vertices[i].position.z > max.z) - max.z = vertices[i].position.z; - if (i == 0 || vertices[i].position.x < min.x) - min.x = vertices[i].position.x; - if (i == 0 || vertices[i].position.y < min.y) - min.y = vertices[i].position.y; - if (i == 0 || vertices[i].position.z < min.z) - min.z = vertices[i].position.z; + + // Update min/max bounds + if (i == 0) + { + min = max = vertices[i].position; + } + else + { + min.x = std::min(min.x, vertices[i].position.x); + min.y = std::min(min.y, vertices[i].position.y); + min.z = std::min(min.z, vertices[i].position.z); + max.x = std::max(max.x, vertices[i].position.x); + max.y = std::max(max.y, vertices[i].position.y); + max.z = std::max(max.z, vertices[i].position.z); + } + 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); @@ -499,12 +510,12 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::vector wght; F32 total = 0.f; - for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) + 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[i]); - total += weight_list[i].mWeight; + wght.push_back(weight_list[j]); + total += weight_list[j].mWeight; } if (total != 0.f) @@ -512,9 +523,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& F32 scale = 1.f / total; if (scale != 1.f) { // normalize weights - for (U32 i = 0; i < wght.size(); ++i) + for (U32 j = 0; j < wght.size(); ++j) { - wght[i].mWeight *= scale; + wght[j].mWeight *= scale; } } } -- cgit v1.2.3 From 83fa366de9df385daec05c262f88eefd86af6adf Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sun, 25 May 2025 00:53:25 +0300 Subject: #4109 Fix inside-out geometry from negative scale transforms in GLTF loader --- indra/newview/gltf/llgltfloader.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2e8521b597..a64df86770 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -262,6 +262,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Combine transforms: coordinate rotation applied to hierarchy transform const glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + // Check if we have a negative scale (flipped coordinate system) + bool hasNegativeScale = glm::determinant(final_transform) < 0.0f; + // Pre-compute normal transform matrix (transpose of inverse of upper-left 3x3) const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(final_transform))); @@ -429,9 +432,22 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vertices.push_back(vert); } - for (U32 i = 0; i < prim.getIndexCount(); i++) + // When processing indices, flip winding order if needed + for (U32 i = 0; i < prim.getIndexCount(); i += 3) { - indices.push_back(prim.mIndexArray[i]); + 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]); + } } // Check for empty vertex array before processing -- cgit v1.2.3 From d6419f729e3997c3d7cd67c7ff4ee38ab835c509 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sun, 25 May 2025 01:21:36 +0300 Subject: #4109 Fix GLTF model extents calculation --- indra/newview/gltf/llgltfloader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index a64df86770..3312e61595 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -588,6 +588,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } + // Call normalizeVolumeFaces to compute proper extents + pModel->normalizeVolumeFaces(); + // Fill joint names, bind matrices and prepare to remap weight indices if (skinIdx >= 0) { -- cgit v1.2.3 From b423900792aca2e094fac85f9aeb34b26f9c2109 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sun, 25 May 2025 19:28:24 +0300 Subject: #4105 Fix duplicate GLTF model instances causing upload errors --- indra/newview/gltf/llgltfloader.cpp | 35 ++++++++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 2 +- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3312e61595..17f52af6b1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -153,6 +153,9 @@ bool LLGLTFLoader::parseMeshes() node.makeMatrixValid(); } + // Track how many times each mesh name has been used + std::map mesh_name_counts; + // Process each node for (auto& node : mGLTFAsset.mNodes) { @@ -166,7 +169,17 @@ bool LLGLTFLoader::parseMeshes() { LLModel* pModel = new LLModel(volume_params, 0.f); auto mesh = mGLTFAsset.mMeshes[meshidx]; - if (populateModelFromMesh(pModel, mesh, node, mats) && + + // Get base mesh name and track usage + std::string base_name = mesh.mName; + if (base_name.empty()) + { + base_name = "mesh_" + std::to_string(meshidx); + } + + S32 instance_count = mesh_name_counts[base_name]++; + + if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { @@ -244,9 +257,25 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 combined_transform = node_transform; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) { - pModel->mLabel = mesh.mName; + // Create unique model name + std::string base_name = mesh.mName; + if (base_name.empty()) + { + S32 mesh_index = static_cast(&mesh - &mGLTFAsset.mMeshes[0]); + base_name = "mesh_" + std::to_string(mesh_index); + } + + if (instance_count > 0) + { + pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); + } + else + { + pModel->mLabel = base_name; + } + pModel->ClearFacesAndMaterials(); S32 skinIdx = nodeno.mSkin; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index fac5b719fb..69289aabce 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -168,7 +168,7 @@ private: bool parseMaterials(); void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); + 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); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); -- cgit v1.2.3 From d342aa79c24fe20d06a018eabdb03912d11d4702 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 26 May 2025 17:31:37 +0300 Subject: #4080 Rigged mesh support #3 --- indra/newview/gltf/llgltfloader.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 17f52af6b1..de7c341cdf 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -690,6 +690,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } + 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])); + } } // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh -- cgit v1.2.3 From a364b5ee1d4c009b2e571fde66272ae06e434c21 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 26 May 2025 17:43:56 +0300 Subject: #4109 Add validation for non-triangulated geometry in GLTF loader --- indra/newview/gltf/llgltfloader.cpp | 10 ++++++++++ indra/newview/skins/default/xui/en/floater_model_preview.xml | 1 + 2 files changed, 11 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index de7c341cdf..93ed904f2e 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -461,6 +461,16 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vertices.push_back(vert); } + if (prim.getIndexCount() % 3 != 0) + { + LL_WARNS("GLTF_IMPORT") << "Invalid primitive: index count " << prim.getIndexCount() + << " is not divisible by 3. GLTF files must contain triangulated geometry." << LL_ENDL; + LLSD args; + args["Message"] = "InvalidGeometryNonTriangulated"; + mWarningsArray.append(args); + continue; // Skip this primitive + } + // When processing indices, flip winding order if needed for (U32 i = 0; i < prim.getIndexCount(); i += 3) { 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 99c348c236..067a1e0c8b 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -61,6 +61,7 @@ Document has no root Document has no visual_scene Unable to process mesh without position data. Invalid model. + Invalid geometry: GLTF files must contain triangulated meshes only. Date: Mon, 26 May 2025 21:27:09 +0300 Subject: #4080 Rigged mesh support #4 --- indra/newview/gltf/llgltfloader.cpp | 40 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 93ed904f2e..82666761df 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -67,6 +67,14 @@ static const std::string lod_suffix[LLModel::NUM_LODS] = "_PHYS", }; +// Premade rotation matrix, GLTF is Y-up while SL is Z-up +static const glm::mat4 coord_system_rotation( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f +); + LLGLTFLoader::LLGLTFLoader(std::string filename, S32 lod, @@ -203,9 +211,21 @@ bool LLGLTFLoader::parseMeshes() mesh_scale *= transformation; transformation = mesh_scale; + // "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: This appers 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") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " << pModel->mLabel << LL_ENDL; LLSD args; args["Message"] = "NegativeScaleNormTrans"; @@ -280,9 +300,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& S32 skinIdx = nodeno.mSkin; - // Pre-compute coordinate system rotation matrix (GLTF Y-up to SL Z-up) - static const glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - // Compute final combined transform matrix (hierarchy + coordinate rotation) S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); glm::mat4 hierarchy_transform; @@ -619,7 +636,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mats[materialName] = impMat; } else { - LL_INFOS() << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; LLSD args; args["Message"] = "ParsingErrorBadElement"; mWarningsArray.append(args); @@ -639,7 +656,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& size_t jointCnt = gltf_skin.mJoints.size(); if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size()) { - LL_INFOS("GLTF") << "Bind matrices count mismatch joints count" << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; LLSD args; args["Message"] = "InvBindCountMismatch"; mWarningsArray.append(args); @@ -709,14 +726,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // "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. - LLMatrix4 bind_shape; - bind_shape.setIdentity(); - skin_info.mBindShapeMatrix.loadu(bind_shape); - // Remap indices for pModel->mSkinWeights for (auto& weights : pModel->mSkinWeights) { @@ -732,9 +741,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) { - // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up - glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL; for (auto joint : skin.mJoints) -- cgit v1.2.3 From 3f0aa3383a25d203dcdf35d0b7937cbcba3ea5e3 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 26 May 2025 22:03:55 +0300 Subject: #4109 Improve LLGLTFLoader::computeCombinedNodeTransform() --- indra/newview/gltf/llgltfloader.cpp | 39 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 82666761df..5922385358 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -251,30 +251,35 @@ bool LLGLTFLoader::parseMeshes() void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) { - auto& node = asset.mNodes[node_index]; + if (node_index < 0 || node_index >= static_cast(asset.mNodes.size())) + { + combined_transform = glm::mat4(1.0f); + return; + } + + const auto& node = asset.mNodes[node_index]; + + // Ensure the node's matrix is valid + const_cast(node).makeMatrixValid(); // Start with this node's transform - glm::mat4 node_transform = node.mMatrix; + combined_transform = node.mMatrix; - // Find parent node and apply its transform if it exists - for (auto& other_node : asset.mNodes) + // Find and apply parent transform if it exists + for (size_t i = 0; i < asset.mNodes.size(); ++i) { - for (auto& child_index : other_node.mChildren) - { - if (child_index == node_index) - { - // Found a parent, recursively get its combined transform - glm::mat4 parent_transform; - computeCombinedNodeTransform(asset, static_cast(&other_node - &asset.mNodes[0]), parent_transform); + const auto& potential_parent = asset.mNodes[i]; + auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), node_index); - // Apply parent transform to current node transform - node_transform = parent_transform * node_transform; - break; - } + if (it != potential_parent.mChildren.end()) + { + // Found parent - recursively get its combined transform and apply it + glm::mat4 parent_transform; + computeCombinedNodeTransform(asset, static_cast(i), parent_transform); + combined_transform = parent_transform * combined_transform; + return; // Early exit - a node can only have one parent } } - - combined_transform = node_transform; } bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) -- cgit v1.2.3 From 0d99487d46d6ccf7000723c35ac78018b5763dea Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 27 May 2025 20:28:00 +0300 Subject: #4107 upload the model ignoring unsupported extension --- indra/newview/gltf/asset.cpp | 10 ++-------- indra/newview/gltf/asset.h | 2 ++ indra/newview/gltf/llgltfloader.cpp | 7 +++++++ indra/newview/skins/default/xui/en/floater_model_preview.xml | 1 + 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 129661c195..6576904874 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -489,22 +489,16 @@ void Asset::update() bool Asset::prep() { LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; - // check required extensions and fail if not supported - bool unsupported = false; + // check required extensions for (auto& extension : mExtensionsRequired) { if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) { LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; - unsupported = true; + mUnsupportedExtension = true; } } - if (unsupported) - { - return false; - } - // do buffers first as other resources depend on them for (auto& buffer : mBuffers) { diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 3cec18268b..3a20f7d6ea 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -396,6 +396,8 @@ namespace LL U32 mMaterialsUBO = 0; bool mLoadIntoVRAM = false; + bool mUnsupportedExtension = false; + // prepare for first time use bool prep(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5922385358..236c75a125 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -127,6 +127,13 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } + if (mGLTFAsset.mUnsupportedExtension) + { + LLSD args; + args["Message"] = "UnsupportedExtension"; + mWarningsArray.append(args); + } + mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 067a1e0c8b..d326cbde3b 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -62,6 +62,7 @@ Document has no visual_scene Unable to process mesh without position data. Invalid model. Invalid geometry: GLTF files must contain triangulated meshes only. + Model uses unsupported extension, related material properties are ignored. Date: Tue, 27 May 2025 22:01:35 +0300 Subject: #4157 Improve "Unable to upload material" error message --- indra/newview/skins/default/xui/en/notifications.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index a51feeb7ab..82e77119ab 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -9453,8 +9453,11 @@ Unable to upload texture: '[NAME]' icon="alertmodal.tga" name="CannotUploadMaterial" type="alertmodal"> -There was a problem uploading the file +Unable to upload material file. The file may be corrupted, in an unsupported format, or contain invalid data. Please check that you're using a valid GLTF/GLB file with proper material definitions. fail + Date: Mon, 26 May 2025 21:27:09 +0300 Subject: #4080 Rigged mesh support #5 --- indra/newview/gltf/llgltfloader.cpp | 110 ++++++++++++++++++++++++++++-------- indra/newview/gltf/llgltfloader.h | 4 +- 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 236c75a125..1f8733f4ff 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -155,6 +155,12 @@ bool LLGLTFLoader::parseMeshes() mTransform.setIdentity(); + for (auto& node : mGLTFAsset.mNodes) + { + // Make node matrix valid for correct transformation + node.makeMatrixValid(); + } + // 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) @@ -162,12 +168,6 @@ bool LLGLTFLoader::parseMeshes() populateJointFromSkin(skin); } - for (auto& node : mGLTFAsset.mNodes) - { - // Make node matrix valid for correct transformation - node.makeMatrixValid(); - } - // Track how many times each mesh name has been used std::map mesh_name_counts; @@ -218,17 +218,21 @@ bool LLGLTFLoader::parseMeshes() mesh_scale *= transformation; transformation = mesh_scale; - // "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: This appers 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 (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 @@ -256,7 +260,7 @@ bool LLGLTFLoader::parseMeshes() return true; } -void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) +void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const { if (node_index < 0 || node_index >= static_cast(asset.mNodes.size())) { @@ -342,7 +346,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Process joint name and idnex S32 joint = gltf_skin.mJoints[i]; LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) == mJointMap.end()) @@ -688,7 +691,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& continue; } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) != mJointMap.end()) @@ -723,8 +725,15 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Get the original joint node and use its matrix directly S32 joint = gltf_skin.mJoints[i]; LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); - LLMatrix4 original_joint_transform(glm::value_ptr(jointNode.mMatrix)); + glm::mat4 joint_mat = jointNode.mMatrix; + S32 root_joint = findValidRootJoint(joint, gltf_skin); // skeleton can have multiple real roots + if (root_joint == joint) + { + // This is very likely incomplete in some way. + // Root shouldn't be the only one to need full coordinate fix + joint_mat = coord_system_rotation * joint_mat; + } + LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); @@ -771,8 +780,6 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) continue; } - jointNode.makeMatrixValid(); - // Debug: Log original joint matrix glm::mat4 gltf_joint_matrix = jointNode.mMatrix; LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' original matrix:" << LL_ENDL; @@ -801,6 +808,61 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) } } +S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_joint = 0; + S32 found_joint = source_joint; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_joint = found_joint; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + if (it != jointNode.mChildren.end()) + { + found_joint = joint; + break; + } + } + } + } while (root_joint != found_joint); + + return root_joint; +} + +S32 LLGLTFLoader::findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_joint = 0; + S32 found_joint = 0; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_joint = found_joint; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + if (it != jointNode.mChildren.end()) + { + found_joint = joint; + break; + } + } + } while (root_joint != found_joint); + + 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; +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 69289aabce..d0f761df2c 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -167,9 +167,11 @@ private: void uploadMeshes(); bool parseMaterials(); void uploadMaterials(); - void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform); + void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointFromSkin(const LL::GLTF::Skin& skin); + S32 findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; + S32 findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint LLUUID imageBufferToTextureUUID(const gltf_texture& tex); // bool mPreprocessGLTF; -- cgit v1.2.3 From 4c60231c3fa52a0875ff5ddd7cc4e416f839da95 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 28 May 2025 12:02:50 +0300 Subject: #4080 Rigged mesh support #6 For now not touching normalizeVolumeFaces() to not brick dae upload --- indra/llprimitive/llmodel.cpp | 156 ++++++++++++++++++++++++++++++++++++ indra/llprimitive/llmodel.h | 1 + indra/newview/gltf/llgltfloader.cpp | 13 ++- 3 files changed, 166 insertions(+), 4 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 4e3e49ec9f..c67dac2733 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -334,6 +334,162 @@ void LLModel::normalizeVolumeFaces() } } +void LLModel::normalizeVolumeFacesAndWeights() +{ + if (!mVolumeFaces.empty()) + { + LLVector4a min, max; + + // For all of the volume faces + // in the model, loop over + // them and see what the extents + // of the volume along each axis. + min = mVolumeFaces[0].mExtents[0]; + max = mVolumeFaces[0].mExtents[1]; + + for (U32 i = 1; i < mVolumeFaces.size(); ++i) + { + LLVolumeFace& face = mVolumeFaces[i]; + + update_min_max(min, max, face.mExtents[0]); + update_min_max(min, max, face.mExtents[1]); + + if (face.mTexCoords) + { + LLVector2& min_tc = face.mTexCoordExtents[0]; + LLVector2& max_tc = face.mTexCoordExtents[1]; + + min_tc = face.mTexCoords[0]; + max_tc = face.mTexCoords[0]; + + for (S32 j = 1; j < face.mNumVertices; ++j) + { + update_min_max(min_tc, max_tc, face.mTexCoords[j]); + } + } + else + { + face.mTexCoordExtents[0].set(0, 0); + face.mTexCoordExtents[1].set(1, 1); + } + } + + // Now that we have the extents of the model + // we can compute the offset needed to center + // the model at the origin. + + // Compute center of the model + // and make it negative to get translation + // needed to center at origin. + LLVector4a trans; + trans.setAdd(min, max); + trans.mul(-0.5f); + + // Compute the total size along all + // axes of the model. + LLVector4a size; + size.setSub(max, min); + + // Prevent division by zero. + F32 x = size[0]; + F32 y = size[1]; + F32 z = size[2]; + F32 w = size[3]; + if (fabs(x) < F_APPROXIMATELY_ZERO) + { + x = 1.0; + } + if (fabs(y) < F_APPROXIMATELY_ZERO) + { + y = 1.0; + } + if (fabs(z) < F_APPROXIMATELY_ZERO) + { + z = 1.0; + } + size.set(x, y, z, w); + + // Compute scale as reciprocal of size + LLVector4a scale; + scale.splat(1.f); + scale.div(size); + + LLVector4a inv_scale(1.f); + inv_scale.div(scale); + + for (U32 i = 0; i < mVolumeFaces.size(); ++i) + { + LLVolumeFace& face = mVolumeFaces[i]; + + // We shrink the extents so + // that they fall within + // the unit cube. + // VFExtents change + face.mExtents[0].add(trans); + face.mExtents[0].mul(scale); + + face.mExtents[1].add(trans); + face.mExtents[1].mul(scale); + + // For all the positions, we scale + // the positions to fit within the unit cube. + LLVector4a* pos = (LLVector4a*)face.mPositions; + LLVector4a* norm = (LLVector4a*)face.mNormals; + LLVector4a* t = (LLVector4a*)face.mTangents; + + for (S32 j = 0; j < face.mNumVertices; ++j) + { + pos[j].add(trans); + pos[j].mul(scale); + if (norm && !norm[j].equals3(LLVector4a::getZero())) + { + norm[j].mul(inv_scale); + norm[j].normalize3(); + } + + if (t) + { + F32 w = t[j].getF32ptr()[3]; + t[j].mul(inv_scale); + t[j].normalize3(); + t[j].getF32ptr()[3] = w; + } + } + } + + weight_map old_weights = mSkinWeights; + mSkinWeights.clear(); + mPosition.clear(); + + for (auto& weights : old_weights) + { + LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]); + pos.add(trans); + pos.mul(scale); + LLVector3 scaled_pos(pos.getF32ptr()); + mPosition.push_back(scaled_pos); + mSkinWeights[scaled_pos] = weights.second; + } + + // mNormalizedScale is the scale at which + // we would need to multiply the model + // by to get the original size of the + // model instead of the normalized size. + LLVector4a normalized_scale; + normalized_scale.splat(1.f); + normalized_scale.div(scale); + mNormalizedScale.set(normalized_scale.getF32ptr()); + mNormalizedTranslation.set(trans.getF32ptr()); + mNormalizedTranslation *= -1.f; + + // remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation) + for (auto& face : mVolumeFaces) + { + face.mNormalizedScale = mNormalizedScale; + } + } +} + void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const { scale_out = mNormalizedScale; diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index fe28926720..5c6d0a55d2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -202,6 +202,7 @@ public: void sortVolumeFacesByMaterialName(); void normalizeVolumeFaces(); + void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); void optimizeVolumeFaces(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1f8733f4ff..f16efe2ff1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -468,6 +468,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& 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) @@ -487,8 +489,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { vert.joints = glm::unpackUint4x16(prim.mJoints[i]); } - - vert.weights = glm::vec4(prim.mWeights[i]); + else + { + vert.joints = glm::zero(); + vert.weights = glm::zero(); + } } vertices.push_back(vert); } @@ -659,8 +664,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // Call normalizeVolumeFaces to compute proper extents - pModel->normalizeVolumeFaces(); + // Call normalizeVolumeFacesAndWeights to compute proper extents + pModel->normalizeVolumeFacesAndWeights(); // Fill joint names, bind matrices and prepare to remap weight indices if (skinIdx >= 0) -- cgit v1.2.3 From 0dbf2b40f6f30728858fadb6390c465dc36669aa Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 28 May 2025 20:54:03 +0300 Subject: #4080 Fix gltf crash when opening --- indra/newview/gltf/buffer_util.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index ef9bba8128..2632a0f263 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -158,6 +158,12 @@ namespace LL dst.load3(src); } + template<> + inline void copyVec3(F32* src, LLColor4U& dst) + { + dst.set((U8)(src[0] * 255.f), (U8)(src[1] * 255.f), (U8)(src[2] * 255.f), 255); + } + template<> inline void copyVec3(U16* src, LLColor4U& dst) { -- cgit v1.2.3 From be40d20bca15b97ccba557dc530fe55a92456ebf Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 29 May 2025 20:20:29 +0300 Subject: #4190 provide unsupported extension info in log --- indra/newview/gltf/asset.cpp | 2 +- indra/newview/gltf/asset.h | 2 +- indra/newview/gltf/llgltfloader.cpp | 11 ++++++++++- indra/newview/skins/default/xui/en/floater_model_preview.xml | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 6576904874..3859bdc103 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -495,7 +495,7 @@ bool Asset::prep() if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) { LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; - mUnsupportedExtension = true; + mUnsupportedExtensions.push_back(extension); } } diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 3a20f7d6ea..a22de4d59d 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -396,7 +396,7 @@ namespace LL U32 mMaterialsUBO = 0; bool mLoadIntoVRAM = false; - bool mUnsupportedExtension = false; + std::vector mUnsupportedExtensions; // prepare for first time use bool prep(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index f16efe2ff1..2461a878fb 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -127,10 +127,19 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } - if (mGLTFAsset.mUnsupportedExtension) + if (mGLTFAsset.mUnsupportedExtensions.size() > 0) { LLSD args; args["Message"] = "UnsupportedExtension"; + std::string del; + std::string ext; + for (auto& extension : mGLTFAsset.mUnsupportedExtensions) + { + ext += del; + ext += extension; + del = ","; + } + args["EXT"] = ext; mWarningsArray.append(args); } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index d326cbde3b..c0cddf0984 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -62,7 +62,7 @@ Document has no visual_scene Unable to process mesh without position data. Invalid model. Invalid geometry: GLTF files must contain triangulated meshes only. - Model uses unsupported extension, related material properties are ignored. + Model uses unsupported extension, related material properties are ignored: [EXT] Date: Fri, 30 May 2025 03:06:33 +0300 Subject: #4191 skip loading model compressed with Draco --- indra/newview/gltf/asset.cpp | 19 +++++++++- indra/newview/gltf/asset.h | 1 + indra/newview/gltf/llgltfloader.cpp | 43 +++++++++++----------- indra/newview/gltf/llgltfloader.h | 2 + .../skins/default/xui/en/floater_model_preview.xml | 3 +- 5 files changed, 44 insertions(+), 24 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 3859bdc103..8c9f77686a 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -50,6 +50,10 @@ namespace LL "KHR_texture_transform" }; + static std::unordered_set ExtensionsIgnored = { + "KHR_materials_pbrSpecularGlossiness" + }; + Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode) { if (alpha_mode == "OPAQUE") @@ -494,10 +498,21 @@ bool Asset::prep() { if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) { - LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; - mUnsupportedExtensions.push_back(extension); + if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end()) + { + LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; + mUnsupportedExtensions.push_back(extension); + } + else + { + mIgnoredExtensions.push_back(extension); + } } } + if (mUnsupportedExtensions.size() > 0) + { + return false; + } // do buffers first as other resources depend on them for (auto& buffer : mBuffers) diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index a22de4d59d..b9554d753c 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -397,6 +397,7 @@ namespace LL bool mLoadIntoVRAM = false; std::vector mUnsupportedExtensions; + std::vector mIgnoredExtensions; // prepare for first time use bool prep(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2461a878fb..5cdd7f09e0 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -111,8 +111,6 @@ LLGLTFLoader::~LLGLTFLoader() {} bool LLGLTFLoader::OpenFile(const std::string &filename) { tinygltf::TinyGLTF loader; - std::string error_msg; - std::string warn_msg; std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); @@ -120,28 +118,11 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) if (!mGltfLoaded) { - if (!warn_msg.empty()) - LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL; - if (!error_msg.empty()) - LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL; + notifyUnsupportedExtension(true); return false; } - if (mGLTFAsset.mUnsupportedExtensions.size() > 0) - { - LLSD args; - args["Message"] = "UnsupportedExtension"; - std::string del; - std::string ext; - for (auto& extension : mGLTFAsset.mUnsupportedExtensions) - { - ext += del; - ext += extension; - del = ","; - } - args["EXT"] = ext; - mWarningsArray.append(args); - } + notifyUnsupportedExtension(false); mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); @@ -1347,3 +1328,23 @@ LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) return LLUUID::null; } +void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) +{ + std::vector extensions = unsupported ? mGLTFAsset.mUnsupportedExtensions : mGLTFAsset.mIgnoredExtensions; + if (extensions.size() > 0) + { + LLSD args; + args["Message"] = unsupported ? "UnsupportedExtension" : "IgnoredExtension"; + std::string del; + std::string ext; + for (auto& extension : extensions) + { + ext += del; + ext += extension; + del = ","; + } + args["EXT"] = ext; + mWarningsArray.append(args); + } +} + diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index d0f761df2c..6e0fe2b32c 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -174,6 +174,8 @@ private: 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 notifyUnsupportedExtension(bool unsupported); + // bool mPreprocessGLTF; /* Below inherited from dae loader - unknown if/how useful here 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 c0cddf0984..6231abf9a5 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -62,7 +62,8 @@ Document has no visual_scene Unable to process mesh without position data. Invalid model. Invalid geometry: GLTF files must contain triangulated meshes only. - Model uses unsupported extension, related material properties are ignored: [EXT] + Model uses unsupported extension: [EXT], related material properties are ignored. + Unable to load a model, unsupported extension: [EXT] Date: Fri, 30 May 2025 17:37:51 +0300 Subject: #4191 Crash opening a gltf model --- indra/newview/gltf/buffer_util.h | 5 +++++ indra/newview/llfilepicker.cpp | 2 ++ indra/newview/llfloatermodelpreview.cpp | 2 -- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index 2632a0f263..be36c5e90b 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -375,6 +375,11 @@ namespace LL template inline void copy(Asset& asset, Accessor& accessor, LLStrider& dst) { + if (accessor.mBufferView == INVALID_INDEX) + { + LL_WARNS("GLTF") << "Invalid buffer" << LL_ENDL; + return; + } const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView]; const Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset; diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 150b5481ca..41e954b7fa 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -217,6 +217,8 @@ bool LLFilePicker::setupFilter(ELoadFilter filter) break; case FFLOAD_MODEL: mOFN.lpstrFilter = MODEL_FILTER \ + COLLADA_FILTER \ + MATERIAL_FILTER \ L"\0"; break; case FFLOAD_MATERIAL: diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 96f9ddbf8a..f962217480 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -625,11 +625,9 @@ void LLFloaterModelPreview::onJointListSelection() LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); LLScrollListCtrl *joints_list = panel->getChild("joints_list"); LLScrollListCtrl *joints_pos = panel->getChild("pos_overrides_list"); - LLScrollListCtrl *joints_scale = panel->getChild("scale_overrides_list"); LLTextBox *joint_pos_descr = panel->getChild("pos_overrides_descr"); joints_pos->deleteAllItems(); - joints_scale->deleteAllItems(); LLScrollListItem *selected = joints_list->getFirstSelected(); if (selected) -- cgit v1.2.3 From 7aea88fb84e6cb5412662dd4de4e7589a6801ba3 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 29 May 2025 20:56:30 +0300 Subject: Use awk to make PV channel name --- .github/workflows/build.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 50b0cf02bc..a9807e1dba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -218,8 +218,10 @@ jobs: prefix=${ba[0]} if [ "$prefix" == "project" ]; then IFS='_' read -ra prj <<< "${ba[1]}" + prj_str="${prj[*]}" # uppercase first letter of each word - export viewer_channel="Second Life Project ${prj[*]^}" + capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}') + export viewer_channel="Second Life Project $capitalized" elif [[ "$prefix" == "release" || "$prefix" == "main" ]]; then export viewer_channel="Second Life Release" -- cgit v1.2.3 From 4ae9b7b24564a5d50360781d2b63f73a6aa2141c Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 2 Jun 2025 19:43:48 +0300 Subject: #4203 show valid log info about 16 bit limit --- indra/newview/gltf/llgltfloader.cpp | 2 +- indra/newview/skins/default/xui/en/floater_model_preview.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5cdd7f09e0..800b986b59 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -648,7 +648,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else { LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; LLSD args; - args["Message"] = "ParsingErrorBadElement"; + args["Message"] = "ErrorIndexLimit"; mWarningsArray.append(args); return false; } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 6231abf9a5..1c4a857dab 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -64,6 +64,7 @@ Invalid geometry: GLTF files must contain triangulated meshes only. Model uses unsupported extension: [EXT], related material properties are ignored. Unable to load a model, unsupported extension: [EXT] + Unable to process mesh due to 16-bit index limits Date: Tue, 3 Jun 2025 13:25:02 +0300 Subject: #4097 Fix crash in LLGLTFLoader::populateModelFromMesh() --- indra/newview/gltf/llgltfloader.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 800b986b59..4d705279a5 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -450,9 +450,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& GLTFVertex vert; vert.position = glm::vec3(transformed_pos); - // Use pre-computed normal_transform - glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.normal = glm::normalize(normal_transform * normal_vec); + if (!prim.mNormals.empty()) + { + // Use pre-computed normal_transform + glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.normal = glm::normalize(normal_transform * normal_vec); + } + else + { + // Use default normal (pointing up in model space) + vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); + LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; + } vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); -- cgit v1.2.3 From acd4de66589e960da24cd39fe44083f80a293b05 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 3 Jun 2025 20:27:25 +0300 Subject: #4080 Rigged mesh support #7 --- indra/newview/gltf/llgltfloader.cpp | 210 ++++++++++++++++++++++++------------ indra/newview/gltf/llgltfloader.h | 14 ++- 2 files changed, 151 insertions(+), 73 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 4d705279a5..c99d7c2993 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -153,9 +153,9 @@ bool LLGLTFLoader::parseMeshes() // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. - for (auto& skin : mGLTFAsset.mSkins) + for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) { - populateJointFromSkin(skin); + populateJointFromSkin(i); } // Track how many times each mesh name has been used @@ -293,6 +293,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& base_name = "mesh_" + std::to_string(mesh_index); } + LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; + if (instance_count > 0) { pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); @@ -672,16 +674,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; - size_t jointCnt = gltf_skin.mJoints.size(); - if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size()) - { - LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; - LLSD args; - args["Message"] = "InvBindCountMismatch"; - mWarningsArray.append(args); - } - std::vector gltfindex_to_joitindex_map; + size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; @@ -711,44 +705,24 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); - if (i < gltf_skin.mInverseBindMatricesData.size()) - { - // Use pre-computed coord_system_rotation instead of recreating it - LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - - glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); - glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); - - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); - skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); - - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + // In scope of same skin multiple meshes reuse same bind matrices + skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[skinIdx][i]); - // For alternate bind matrix, use the ORIGINAL joint transform (before rotation) - // Get the original joint node and use its matrix directly - S32 joint = gltf_skin.mJoints[i]; - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJoint(joint, gltf_skin); // skeleton can have multiple real roots - if (root_joint == joint) - { - // This is very likely incomplete in some way. - // Root shouldn't be the only one to need full coordinate fix - joint_mat = coord_system_rotation * joint_mat; - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); - - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); - } - else + // For alternate bind matrix, use the ORIGINAL joint transform (before rotation) + // Get the original joint node and use its matrix directly + // Todo: this seems blatantly wrong, it should have been rotated + glm::mat4 joint_mat = jointNode.mMatrix; + S32 root_joint = findValidRootJointNode(joint, gltf_skin); // skeleton can have multiple real roots + if (root_joint == joint) { - // For gltf mInverseBindMatrices are optional, but not for viewer - // todo: get a model that triggers this - skin_info.mInvBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); // might need to be an 'identity' - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); + // This is very likely incomplete in some way. + // Root shouldn't be the only one to need full coordinate fix + joint_mat = coord_system_rotation * joint_mat; } + LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } // Remap indices for pModel->mSkinWeights @@ -764,23 +738,71 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& return true; } -void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) +void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { - LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL; + const LL::GLTF::Skin& skin = mGLTFAsset.mSkins[skin_idx]; - for (auto joint : skin.mJoints) + LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing skin " << skin_idx << " with " << skin.mJoints.size() << " joints" << LL_ENDL; + + if (skin.mInverseBindMatrices > 0 && skin.mJoints.size() != skin.mInverseBindMatricesData.size()) { - auto jointNode = mGLTFAsset.mNodes[joint]; + LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; + LLSD args; + args["Message"] = "InvBindCountMismatch"; + mWarningsArray.append(args); + } + S32 joint_count = (S32)skin.mJoints.size(); + S32 inverse_count = (S32)skin.mInverseBindMatricesData.size(); + if (mInverseBindMatrices.size() <= skin_idx) + { + mInverseBindMatrices.resize(skin_idx + 1); + } + + for (S32 i = 0; i < joint_count; i++) + { + S32 joint = skin.mJoints[i]; + LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); + bool legal_joint = false; if (mJointMap.find(legal_name) != mJointMap.end()) { legal_name = mJointMap[legal_name]; + legal_joint = true; + } + + if (!legal_joint) + { + // add placeholder to not break index + LLMatrix4 gltf_transform; + gltf_transform.setIdentity(); + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + else if (inverse_count > i) + { + LL::GLTF::mat4 gltf_mat = skin.mInverseBindMatricesData[i]; + + // Todo: there should be a simpler way + glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); + glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; + glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); + + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { - // ignore unrecognized joint - LL_DEBUGS("GLTF") << "Ignoring joint: " << legal_name << LL_ENDL; + LLMatrix4 gltf_transform; + gltf_transform.setIdentity(); + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + // todo: prepare mAlternateBindMatrix here + + if (!legal_joint) + { + LL_DEBUGS("GLTF") << "Ignoring unrecognized joint: " << legal_name << LL_ENDL; continue; } @@ -812,14 +834,45 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) } } -S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const + +S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const { - S32 root_joint = 0; - S32 found_joint = source_joint; + S32 source_joint_node = gltf_skin.mJoints[source_joint]; + S32 root_node = source_joint_node; + S32 found_node = source_joint_node; S32 size = (S32)gltf_skin.mJoints.size(); do { - root_joint = found_joint; + root_node = found_node; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); + if (it != jointNode.mChildren.end()) + { + // Found node's parent + found_node = joint; + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + return i; + } + break; + } + } + } while (root_node != found_node); + + return -1; +} + +S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_node = 0; + S32 found_node = source_joint_node; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_node = found_node; for (S32 i = 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; @@ -827,44 +880,61 @@ S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& glt if (mJointMap.find(jointNode.mName) != mJointMap.end()) { - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); if (it != jointNode.mChildren.end()) { - found_joint = joint; + // Found node's parent + found_node = joint; break; } } } - } while (root_joint != found_joint); + } while (root_node != found_node); - return root_joint; + return root_node; } -S32 LLGLTFLoader::findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const +S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const { - S32 root_joint = 0; - S32 found_joint = 0; + S32 root_node = 0; + S32 found_node = 0; S32 size = (S32)gltf_skin.mJoints.size(); do { - root_joint = found_joint; + root_node = found_node; for (S32 i = 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); if (it != jointNode.mChildren.end()) { - found_joint = joint; + // Found node's parent + found_node = joint; break; } } - } while (root_joint != found_joint); + } while (root_node != found_node); LL_INFOS("GLTF_DEBUG") << "mJointList name: "; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_joint]; - LL_CONT << jointNode.mName << " index: " << root_joint << LL_ENDL; - return root_joint; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node]; + LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL; + return root_node; +} + +S32 LLGLTFLoader::findParentNode(S32 node) const +{ + S32 size = (S32)mGLTFAsset.mNodes.size(); + for (S32 i = 0; i < size; i++) + { + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node); + if (it != jointNode.mChildren.end()) + { + return i; + } + } + return -1; } bool LLGLTFLoader::parseMaterials() diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 6e0fe2b32c..a9572a5bfc 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -121,6 +121,7 @@ class LLGLTFLoader : public LLModelLoader { public: typedef std::map material_map; + typedef std::map joint_pos_map_t; LLGLTFLoader(std::string filename, S32 lod, @@ -162,6 +163,11 @@ protected: std::vector mImages; std::vector mSamplers; + + // vector of vectors because of a posibility of having more than one skin + typedef std::vector bind_matrices_t; + bind_matrices_t mInverseBindMatrices; + private: bool parseMeshes(); void uploadMeshes(); @@ -169,9 +175,11 @@ private: void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); - void populateJointFromSkin(const LL::GLTF::Skin& skin); - S32 findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; - S32 findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint + void populateJointFromSkin(S32 skin_idx); + S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; + S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; + S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint + S32 findParentNode(S32 node) const; LLUUID imageBufferToTextureUUID(const gltf_texture& tex); void notifyUnsupportedExtension(bool unsupported); -- cgit v1.2.3 From 11ece6840b034973a33626e98bd2412f0577ac5a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 18:00:14 +0300 Subject: #4214 Support mesh splitting for meshes with more than 8 materials --- indra/newview/gltf/llgltfloader.cpp | 107 +++++++++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 6 ++ 2 files changed, 105 insertions(+), 8 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c99d7c2993..e330c1d611 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -102,7 +102,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, maxJointsPerMesh ), //mPreprocessGLTF(preprocess), mMeshesLoaded(false), - mMaterialsLoaded(false) + mMaterialsLoaded(false), + mGeneratedModelLimit(modelLimit) { } @@ -135,6 +136,99 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return (mMeshesLoaded); } +void LLGLTFLoader::addModelToScene( + LLModel* pModel, + U32 submodel_limit, + const LLMatrix4& transformation, + const LLVolumeParams& volume_params) +{ + U32 volume_faces = pModel->getNumVolumeFaces(); + + // Side-steps all manner of issues when splitting models + // and matching lower LOD materials to base models + // + pModel->sortVolumeFacesByMaterialName(); + + int submodelID = 0; + + // remove all faces that definitely won't fit into one model and submodel limit + U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES; + if (face_limit < volume_faces) + { + pModel->setNumVolumeFaces(face_limit); + } + + LLVolume::face_list_t remainder; + std::vector ready_models; + LLModel* current_model = pModel; + do + { + current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); + + // remove unused/redundant vertices after normalizing + current_model->remapVolumeFaces(); + + volume_faces = static_cast(remainder.size()); + + // Don't add to scene yet because weights and materials aren't ready. + // Just save it + ready_models.push_back(current_model); + + // If we have left-over volume faces, create another model + // to absorb them. + if (volume_faces) + { + LLModel* next = new LLModel(volume_params, 0.f); + next->ClearFacesAndMaterials(); + next->mSubmodelID = ++submodelID; + next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; + next->getVolumeFaces() = remainder; + next->mNormalizedScale = current_model->mNormalizedScale; + next->mNormalizedTranslation = current_model->mNormalizedTranslation; + next->mSkinWeights = current_model->mSkinWeights; + next->mPosition = current_model->mPosition; + + const LLMeshSkinInfo& current_skin_info = current_model->mSkinInfo; + LLMeshSkinInfo& next_skin_info = next->mSkinInfo; + next_skin_info.mJointNames = current_skin_info.mJointNames; + next_skin_info.mJointNums = current_skin_info.mJointNums; + next_skin_info.mBindShapeMatrix = current_skin_info.mBindShapeMatrix; + next_skin_info.mInvBindMatrix = current_skin_info.mInvBindMatrix; + next_skin_info.mAlternateBindMatrix = current_skin_info.mAlternateBindMatrix; + next_skin_info.mPelvisOffset = current_skin_info.mPelvisOffset; + + + if (current_model->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES) + { + next->mMaterialList.assign(current_model->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, current_model->mMaterialList.end()); + current_model->mMaterialList.resize(LL_SCULPT_MESH_MAX_FACES); + } + + current_model = next; + } + + remainder.clear(); + + } while (volume_faces); + + for (auto model : ready_models) + { + // remove unused/redundant vertices + current_model->remapVolumeFaces(); + // Todo: go over skin weights, joints, matrices and remove unused ones + + mModelList.push_back(model); + + std::map materials; + for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) + { + materials[model->mMaterialList[i]] = LLImportMaterial(); + } + mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); + stretch_extents(model, transformation); + } +} + bool LLGLTFLoader::parseMeshes() { if (!mGltfLoaded) return false; @@ -160,6 +254,7 @@ bool LLGLTFLoader::parseMeshes() // Track how many times each mesh name has been used std::map mesh_name_counts; + U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; // Process each node for (auto& node : mGLTFAsset.mNodes) @@ -188,8 +283,6 @@ bool LLGLTFLoader::parseMeshes() (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { - mModelList.push_back(pModel); - mTransform.setIdentity(); transformation = mTransform; @@ -234,8 +327,7 @@ bool LLGLTFLoader::parseMeshes() mWarningsArray.append(args); } - mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); - stretch_extents(pModel, transformation); + addModelToScene(pModel, submodel_limit, transformation, volume_params); } else { @@ -347,8 +439,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - auto prims = mesh.mPrimitives; - for (auto prim : prims) + for (const LL::GLTF::Primitive& prim : mesh.mPrimitives) { // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. if (prim.getVertexCount() < USHRT_MAX) @@ -471,7 +562,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { vert.weights = glm::vec4(prim.mWeights[i]); - auto accessorIdx = prim.mAttributes["JOINTS_0"]; + auto accessorIdx = prim.mAttributes.at("JOINTS_0"); LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; if (accessorIdx >= 0) { diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index a9572a5bfc..a3ee8d91df 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -155,6 +155,7 @@ protected: bool mGltfLoaded; bool mMeshesLoaded; bool mMaterialsLoaded; + U32 mGeneratedModelLimit; std::vector mMeshes; std::vector mMaterials; @@ -176,6 +177,11 @@ private: void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointFromSkin(S32 skin_idx); + void addModelToScene( + LLModel* pModel, + U32 submodel_limit, + const LLMatrix4& transformation, + const LLVolumeParams& volume_params); S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint -- cgit v1.2.3 From 08f6f5c697fce4ccbfba357ab9ce5af915dd0574 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 20:57:10 +0300 Subject: #4214 Weights and Joints remap --- indra/llprimitive/llmodel.cpp | 87 +++++++++++++++++++++++++++++++++++++ indra/llprimitive/llmodel.h | 1 + indra/newview/gltf/llgltfloader.cpp | 6 +-- 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index c67dac2733..bc9f62b6c0 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -112,6 +112,93 @@ void LLModel::remapVolumeFaces() } } +void LLModel::remapSkinWeightsAndJoints() +{ + if (mSkinWeights.empty()) + { + return; + } + + mPosition.clear(); + + // Make a list of positions + std::set positions; + for (S32 i = 0; i < getNumVolumeFaces(); ++i) + { + for (S32 j = 0; j < mVolumeFaces[i].mNumVertices; ++j) + { + positions.emplace(mVolumeFaces[i].mPositions[j].getF32ptr()); + } + } + + // Build new list of weights and record used joints + weight_map replacement_weights; + std::vector joint_index_use_count; + size_t joint_count = mSkinInfo.mJointNames.size(); + joint_index_use_count.resize(joint_count, 0); + for (const LLVector3& pos : positions) + { + mPosition.push_back(pos); + auto found = mSkinWeights.find(pos); + if (found != mSkinWeights.end()) + { + replacement_weights[pos] = found->second; + + for (auto& weight : found->second) + { + if (joint_count > weight.mJointIdx) + { + joint_index_use_count[weight.mJointIdx]++; + } + } + } + } + + // go over joint data and remap joints + // prepare joint map + std::vector replacement_joint_names; + std::vector replacement_joint_nums; + LLMeshSkinInfo::matrix_list_t replacement_inv_bind; + LLMeshSkinInfo::matrix_list_t replacement_alt_bind; + std::vector index_map; + index_map.resize(joint_count); + S32 replacement_index = 0; + + for (S32 i = 0; i < joint_count; i++) + { + if (joint_index_use_count[i] > 0) + { + replacement_joint_names.push_back(mSkinInfo.mJointNames[i]); + replacement_joint_nums.push_back(mSkinInfo.mJointNums[i]); + replacement_inv_bind.push_back(mSkinInfo.mInvBindMatrix[i]); + replacement_alt_bind.push_back(mSkinInfo.mAlternateBindMatrix[i]); + index_map[i] = replacement_index++; + } + } + + // Apply new data + mSkinInfo.mJointNames.clear(); + mSkinInfo.mJointNames = replacement_joint_names; + mSkinInfo.mJointNums.clear(); + mSkinInfo.mJointNums = replacement_joint_nums; + mSkinInfo.mInvBindMatrix.clear(); + mSkinInfo.mInvBindMatrix = replacement_inv_bind; + mSkinInfo.mAlternateBindMatrix.clear(); + mSkinInfo.mAlternateBindMatrix = replacement_alt_bind; + + // remap weights + for (auto& weights : replacement_weights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = index_map[weight.mJointIdx]; + } + } + + mSkinWeights.clear(); + mSkinWeights = replacement_weights; +} + void LLModel::optimizeVolumeFaces() { for (S32 i = 0; i < getNumVolumeFaces(); ++i) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 5c6d0a55d2..7fa3a00ee2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -205,6 +205,7 @@ public: void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); + void remapSkinWeightsAndJoints(); void optimizeVolumeFaces(); void offsetMesh( const LLVector3& pivotPoint ); void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index e330c1d611..60c6832058 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -165,9 +165,6 @@ void LLGLTFLoader::addModelToScene( { current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); - // remove unused/redundant vertices after normalizing - current_model->remapVolumeFaces(); - volume_faces = static_cast(remainder.size()); // Don't add to scene yet because weights and materials aren't ready. @@ -215,7 +212,8 @@ void LLGLTFLoader::addModelToScene( { // remove unused/redundant vertices current_model->remapVolumeFaces(); - // Todo: go over skin weights, joints, matrices and remove unused ones + // remove unused/redundant weights and joints + current_model->remapSkinWeightsAndJoints(); mModelList.push_back(model); -- cgit v1.2.3 From 74d990872c8d8f59083b868a7ae8df5e90df62c2 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 5 Jun 2025 21:07:19 +0300 Subject: #4214 Fix material upload --- indra/newview/gltf/llgltfloader.cpp | 20 +++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 3 ++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 60c6832058..1d7b44e566 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -140,7 +140,8 @@ void LLGLTFLoader::addModelToScene( LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, - const LLVolumeParams& volume_params) + const LLVolumeParams& volume_params, + const material_map& mats) { U32 volume_faces = pModel->getNumVolumeFaces(); @@ -220,7 +221,15 @@ void LLGLTFLoader::addModelToScene( std::map materials; for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) { - materials[model->mMaterialList[i]] = LLImportMaterial(); + material_map::const_iterator found = mats.find(model->mMaterialList[i]); + if (found != mats.end()) + { + materials[model->mMaterialList[i]] = found->second; + } + else + { + materials[model->mMaterialList[i]] = LLImportMaterial(); + } } mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); stretch_extents(model, transformation); @@ -325,7 +334,8 @@ bool LLGLTFLoader::parseMeshes() mWarningsArray.append(args); } - addModelToScene(pModel, submodel_limit, transformation, volume_params); + addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + mats.clear(); } else { @@ -815,6 +825,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } // Remap indices for pModel->mSkinWeights + // Todo: this is now partially redundant due to + // remapSkinWeightsAndJoints being called later. + // Consider storing all joints now as is and let it + // ramap later due to missing weights. for (auto& weights : pModel->mSkinWeights) { for (auto& weight : weights.second) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index a3ee8d91df..5b9eb78c63 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -181,7 +181,8 @@ private: LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, - const LLVolumeParams& volume_params); + const LLVolumeParams& volume_params, + const material_map& mats); S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint -- cgit v1.2.3 From 35398524e560f2c89e59672cefd5c9ff60c9aa37 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 15:32:42 +0300 Subject: Fix split model vertex/joint remapping to use correct model variable --- indra/newview/gltf/llgltfloader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1d7b44e566..528b932dd3 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -212,9 +212,9 @@ void LLGLTFLoader::addModelToScene( for (auto model : ready_models) { // remove unused/redundant vertices - current_model->remapVolumeFaces(); + model->remapVolumeFaces(); // remove unused/redundant weights and joints - current_model->remapSkinWeightsAndJoints(); + model->remapSkinWeightsAndJoints(); mModelList.push_back(model); -- cgit v1.2.3 From 689b829b67204c3097d1ac6abf69916c45526b43 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 16:10:55 +0300 Subject: #4170 Fix GLTF import missing mesh parts from transform tools Process entire node hierarchy instead of only nodes with meshes. GLTF transform tools often create intermediate transform nodes without meshes that were being skipped, causing child meshes to be omitted from import. --- indra/newview/gltf/llgltfloader.cpp | 186 +++++++++++++++++++++++------------- indra/newview/gltf/llgltfloader.h | 8 +- 2 files changed, 120 insertions(+), 74 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 528b932dd3..edcc716298 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -263,91 +263,141 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Process each node - for (auto& node : mGLTFAsset.mNodes) - { - LLMatrix4 transformation; - material_map mats; - auto meshidx = node.mMesh; + // Mark which nodes have been processed to avoid duplicates + std::vector node_processed(mGLTFAsset.mNodes.size(), false); - if (meshidx >= 0) + // First, find root nodes (nodes without parents) and process their hierarchies + for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + { + if (!node_processed[node_idx]) { - if (mGLTFAsset.mMeshes.size() > meshidx) + // Check if this node has a parent + bool has_parent = false; + for (const auto& potential_parent : mGLTFAsset.mNodes) { - LLModel* pModel = new LLModel(volume_params, 0.f); - auto mesh = mGLTFAsset.mMeshes[meshidx]; - - // Get base mesh name and track usage - std::string base_name = mesh.mName; - if (base_name.empty()) + if (std::find(potential_parent.mChildren.begin(), + potential_parent.mChildren.end(), + static_cast(node_idx)) != potential_parent.mChildren.end()) { - base_name = "mesh_" + std::to_string(meshidx); + has_parent = true; + break; } + } - S32 instance_count = mesh_name_counts[base_name]++; + // If no parent, this is a root node - process its hierarchy + if (!has_parent) + { + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + } + } + } - if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && - (LLModel::NO_ERRORS == pModel->getStatus()) && - validate_model(pModel)) - { - mTransform.setIdentity(); - transformation = mTransform; + // Process any remaining unprocessed nodes (disconnected nodes) + for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + { + if (!node_processed[node_idx]) + { + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + } + } - // adjust the transformation to compensate for mesh normalization - LLVector3 mesh_scale_vector; - LLVector3 mesh_translation_vector; - pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + return true; +} - LLMatrix4 mesh_translation; - mesh_translation.setTranslation(mesh_translation_vector); - mesh_translation *= transformation; - transformation = mesh_translation; +void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed) +{ + if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) + return; - LLMatrix4 mesh_scale; - mesh_scale.initScale(mesh_scale_vector); - mesh_scale *= transformation; - transformation = mesh_scale; + if (node_processed[node_idx]) + return; - if (node.mSkin >= 0) - { - // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh - // into the coordinate space of the joints. - // In GLTF, this matrix is omitted, and it is assumed that this transform is either - // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. - // - // TODO: There appears to be missing rotation when joints rotate the model - // or inverted bind matrices are missing inherited rotation - // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly - // prior to skinning) - - pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); - LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; - } + node_processed[node_idx] = true; - if (transformation.determinant() < 0) - { // negative scales are not supported - LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " - << pModel->mLabel << LL_ENDL; - LLSD args; - args["Message"] = "NegativeScaleNormTrans"; - args["LABEL"] = pModel->mLabel; - mWarningsArray.append(args); - } + auto& node = mGLTFAsset.mNodes[node_idx]; - addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); - mats.clear(); - } - else - { - setLoadState(ERROR_MODEL + pModel->getStatus()); - delete pModel; - return false; - } + // Process this node's mesh if it has one + if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) + { + LLMatrix4 transformation; + material_map mats; + + LLModel* pModel = new LLModel(volume_params, 0.f); + auto& mesh = mGLTFAsset.mMeshes[node.mMesh]; + + // Get base mesh name and track usage + std::string base_name = mesh.mName; + if (base_name.empty()) + { + base_name = "mesh_" + std::to_string(node.mMesh); + } + + S32 instance_count = mesh_name_counts[base_name]++; + + if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && + (LLModel::NO_ERRORS == pModel->getStatus()) && + validate_model(pModel)) + { + mTransform.setIdentity(); + transformation = mTransform; + + // adjust the transformation to compensate for mesh normalization + LLVector3 mesh_scale_vector; + LLVector3 mesh_translation_vector; + pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transformation; + transformation = mesh_translation; + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transformation; + transformation = mesh_scale; + + if (node.mSkin >= 0) + { + // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh + // into the coordinate space of the joints. + // In GLTF, this matrix is omitted, and it is assumed that this transform is either + // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. + // + // TODO: There appears to be missing rotation when joints rotate the model + // or inverted bind matrices are missing inherited rotation + // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly + // prior to skinning) + + pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); + LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; } + + if (transformation.determinant() < 0) + { // negative scales are not supported + LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + << pModel->mLabel << LL_ENDL; + LLSD args; + args["Message"] = "NegativeScaleNormTrans"; + args["LABEL"] = pModel->mLabel; + mWarningsArray.append(args); + } + + addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + mats.clear(); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete pModel; + return; } } - return true; + // Process all children + for (S32 child_idx : node.mChildren) + { + processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params, node_processed); + } } void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 5b9eb78c63..4802c8e093 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -175,14 +175,10 @@ private: bool parseMaterials(); void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; + void processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointFromSkin(S32 skin_idx); - void addModelToScene( - LLModel* pModel, - U32 submodel_limit, - const LLMatrix4& transformation, - const LLVolumeParams& volume_params, - const material_map& mats); + void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint -- cgit v1.2.3 From 6924862d2ef1ef6af6a04c013aebeecbb5717bde Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 17:22:02 +0300 Subject: #4170 Follow-up: Optimize GLTF node hierarchy traversal --- indra/newview/gltf/llgltfloader.cpp | 48 +++++++++++++------------------------ indra/newview/gltf/llgltfloader.h | 2 +- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index edcc716298..17de6eb829 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -263,57 +263,41 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Mark which nodes have been processed to avoid duplicates - std::vector node_processed(mGLTFAsset.mNodes.size(), false); + // Build parent mapping for efficient traversal + std::vector node_parents(mGLTFAsset.mNodes.size(), -1); + std::vector is_root(mGLTFAsset.mNodes.size(), true); - // First, find root nodes (nodes without parents) and process their hierarchies - for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + // Build parent relationships + for (size_t parent_idx = 0; parent_idx < mGLTFAsset.mNodes.size(); parent_idx++) { - if (!node_processed[node_idx]) + const auto& parent_node = mGLTFAsset.mNodes[parent_idx]; + for (S32 child_idx : parent_node.mChildren) { - // Check if this node has a parent - bool has_parent = false; - for (const auto& potential_parent : mGLTFAsset.mNodes) - { - if (std::find(potential_parent.mChildren.begin(), - potential_parent.mChildren.end(), - static_cast(node_idx)) != potential_parent.mChildren.end()) - { - has_parent = true; - break; - } - } - - // If no parent, this is a root node - process its hierarchy - if (!has_parent) + if (child_idx >= 0 && child_idx < static_cast(mGLTFAsset.mNodes.size())) { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + node_parents[child_idx] = static_cast(parent_idx); + is_root[child_idx] = false; } } } - // Process any remaining unprocessed nodes (disconnected nodes) + // Process all root nodes and their hierarchies for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) { - if (!node_processed[node_idx]) + if (is_root[node_idx]) { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params); } } return true; } -void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed) +void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params) { if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) return; - if (node_processed[node_idx]) - return; - - node_processed[node_idx] = true; - auto& node = mGLTFAsset.mNodes[node_idx]; // Process this node's mesh if it has one @@ -393,10 +377,10 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map } } - // Process all children + // Process all children recursively for (S32 child_idx : node.mChildren) { - processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params, node_processed); + processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params); } } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 4802c8e093..19337c24aa 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -175,7 +175,7 @@ private: bool parseMaterials(); void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; - void processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed); + void processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointFromSkin(S32 skin_idx); void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); -- cgit v1.2.3 From b20d10c0cc96cfcd93468b8e31e47ab1977a9555 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 16:39:51 +0300 Subject: #4148 Skeleton Translation --- indra/llappearance/llavatarappearance.cpp | 47 ++++++++++++++++- indra/llappearance/llavatarappearance.h | 4 +- indra/newview/gltf/llgltfloader.cpp | 87 +++++++++++++++++++++++++++---- indra/newview/gltf/llgltfloader.h | 11 +++- indra/newview/llmodelpreview.cpp | 5 +- 5 files changed, 139 insertions(+), 15 deletions(-) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 3d66809ed6..34d6b4db83 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -34,11 +34,11 @@ #include "llpolymorph.h" #include "llpolymesh.h" #include "llpolyskeletaldistortion.h" -#include "llstl.h" #include "lltexglobalcolor.h" #include "llwearabledata.h" #include "boost/bind.hpp" #include "boost/tokenizer.hpp" +#include "v4math.h" using namespace LLAvatarAppearanceDefines; @@ -71,6 +71,7 @@ public: mChildren.clear(); } bool parseXml(LLXmlTreeNode* node); + glm::mat4 getJointMatrix(); private: std::string mName; @@ -105,11 +106,14 @@ public: S32 getNumBones() const { return mNumBones; } S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; } +private: + typedef std::vector bone_info_list_t; + static void getJointRestMatrices(const bone_info_list_t& bone_list, LLAvatarAppearance::joint_rest_map_t& result, const glm::mat4 &parent_mat); + private: S32 mNumBones; S32 mNumCollisionVolumes; LLAvatarAppearance::joint_alias_map_t mJointAliasMap; - typedef std::vector bone_info_list_t; bone_info_list_t mBoneInfoList; }; @@ -1623,6 +1627,21 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node) return true; } + +glm::mat4 LLAvatarBoneInfo::getJointMatrix() +{ + glm::mat4 mat(1.0f); + // 1. Scaling + mat = glm::scale(mat, glm::vec3(mScale[0], mScale[1], mScale[2])); + // 2. Rotation (Euler angles rad) + mat = glm::rotate(mat, mRot[0], glm::vec3(1, 0, 0)); + mat = glm::rotate(mat, mRot[1], glm::vec3(0, 1, 0)); + mat = glm::rotate(mat, mRot[2], glm::vec3(0, 0, 1)); + // 3. Position + mat = glm::translate(mat, glm::vec3(mPos[0], mPos[1], mPos[2])); + return mat; +} + //----------------------------------------------------------------------------- // LLAvatarSkeletonInfo::parseXml() //----------------------------------------------------------------------------- @@ -1653,6 +1672,22 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node) return true; } +void LLAvatarSkeletonInfo::getJointRestMatrices( + const bone_info_list_t& bone_list, + LLAvatarAppearance::joint_rest_map_t& result, + const glm::mat4& parent_mat) +{ + for (LLAvatarBoneInfo* bone_info : bone_list) + { + if (bone_info->mIsJoint) + { + glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); + result[bone_info->mName] = rest_mat; + getJointRestMatrices(bone_info->mChildren, result, rest_mat); + } + } +} + //Make aliases for joint and push to map. void LLAvatarAppearance::makeJointAliases(LLAvatarBoneInfo *bone_info) { @@ -1714,6 +1749,14 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases return mJointAliasMap; } +LLAvatarAppearance::joint_rest_map_t LLAvatarAppearance:: getJointRestMatrices() const +{ + LLAvatarAppearance::joint_rest_map_t result; + glm::mat4 identity(1.f); + LLAvatarSkeletonInfo::getJointRestMatrices(sAvatarSkeletonInfo->mBoneInfoList, result, identity); + return result; +} + //----------------------------------------------------------------------------- // parseXmlSkeletonNode(): parses nodes from XML tree diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 13e504e639..cea2a837cd 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -34,6 +34,7 @@ #include "lltexlayer.h" #include "llviewervisualparam.h" #include "llxmltree.h" +#include "v4math.h" class LLTexLayerSet; class LLTexGlobalColor; @@ -153,7 +154,8 @@ public: const avatar_joint_list_t& getSkeleton() { return mSkeleton; } typedef std::map joint_alias_map_t; const joint_alias_map_t& getJointAliases(); - + typedef std::map joint_rest_map_t; + joint_rest_map_t getJointRestMatrices() const; protected: static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 17de6eb829..7cf26941ad 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -87,7 +87,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, JointNameSet & jointsFromNodes, std::map &jointAliasMap, U32 maxJointsPerMesh, - U32 modelLimit) //, + U32 modelLimit, + joint_rest_map_t jointRestMatrices) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -103,7 +104,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, //mPreprocessGLTF(preprocess), mMeshesLoaded(false), mMaterialsLoaded(false), - mGeneratedModelLimit(modelLimit) + mGeneratedModelLimit(modelLimit), + mJointRestMatrices(jointRestMatrices) { } @@ -917,20 +919,24 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } else if (inverse_count > i) { - LL::GLTF::mat4 gltf_mat = skin.mInverseBindMatricesData[i]; - - // Todo: there should be a simpler way - glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); + glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + glm::mat4 tranlated_original = skeleton_transform * rotated_original; + glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { - LLMatrix4 gltf_transform; + glm::mat4 inv_bind(1.0f); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + inv_bind = glm::inverse(skeleton_transform * inv_bind); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); gltf_transform.setIdentity(); LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); @@ -1074,6 +1080,69 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const +{ + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? + + if (joint_node_index < 0 || joint_node_index >= static_cast(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + const auto& node = mGLTFAsset.mNodes[joint_node_index]; + + // Find and apply parent transform if it exists + for (size_t i = 0; i < mGLTFAsset.mNodes.size(); ++i) + { + const auto& potential_parent = mGLTFAsset.mNodes[i]; + auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), joint_node_index); + + if (it != potential_parent.mChildren.end()) + { + // Found parent + if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end()) + { + // parent is a joint - recursively combine transform + // assumes that matrix is already valid + return buildGltfRestMatrix(static_cast(i), gltf_skin) * node.mMatrix; + } + } + } + // Should we return armature or stop earlier? + return node.mMatrix; +} + +// This function computes the transformation matrix needed to convert from GLTF skeleton space +// to viewer skeleton space for a specific joint +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 gltf_joint_index, const std::string& joint_name) const +{ + joint_rest_map_t::const_iterator found = mJointRestMatrices.find(joint_name); + if (found == mJointRestMatrices.end()) + { + // For now assume they are identical and return an identity (for ease of debuging) + // But there should be no joints viewer isn't aware about + // Warn or assert about missing joints + return glm::mat4(1.0f); + } + glm::mat4 viewer_joint_rest_pose = found->second; + + // Get the GLTF joint's rest pose (in GLTF coordinate system) + S32 joint_node_index = gltf_skin.mJoints[gltf_joint_index]; + glm::mat4 gltf_joint_rest_pose = buildGltfRestMatrix(joint_node_index, gltf_skin); + gltf_joint_rest_pose = coord_system_rotation * gltf_joint_rest_pose; + + LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; + + LLMatrix4 transform(glm::value_ptr(gltf_joint_rest_pose)); + + LL_CONT << transform << LL_ENDL; + + // Compute transformation from GLTF space to viewer space + // This assumes both skeletons are in rest pose initially + return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 19337c24aa..41e6a2dd6d 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -121,7 +121,7 @@ class LLGLTFLoader : public LLModelLoader { public: typedef std::map material_map; - typedef std::map joint_pos_map_t; + typedef std::map joint_rest_map_t; LLGLTFLoader(std::string filename, S32 lod, @@ -134,7 +134,8 @@ class LLGLTFLoader : public LLModelLoader JointNameSet & jointsFromNodes, std::map &jointAliasMap, U32 maxJointsPerMesh, - U32 modelLimit); //, + U32 modelLimit, + joint_rest_map_t jointRestMatrices); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -164,6 +165,10 @@ protected: std::vector mImages; std::vector mSamplers; + // GLTF isn't aware of viewer's skeleton and uses it's own, + // so need to take viewer's joints and use them to + // recalculate iverse bind matrices + joint_rest_map_t mJointRestMatrices; // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; @@ -183,6 +188,8 @@ private: S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint S32 findParentNode(S32 node) const; + glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; + glm::mat4 computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 joint_index, const std::string& joint_name) const; LLUUID imageBufferToTextureUUID(const gltf_texture& tex); void notifyUnsupportedExtension(bool unsupported); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 2d568ecb8b..40b6d186b8 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -810,6 +810,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable } else { + LLVOAvatar* av = getPreviewAvatar(); + LLAvatarAppearance::joint_rest_map_t rest_pose = av->getJointRestMatrices(); mModelLoader = new LLGLTFLoader( filename, lod, @@ -822,7 +824,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mJointsFromNode, joint_alias_map, LLSkinningUtil::getMaxJointCount(), - gSavedSettings.getU32("ImporterModelLimit")); + gSavedSettings.getU32("ImporterModelLimit"), + rest_pose); } if (force_disable_slm) -- cgit v1.2.3 From b4fb66c4a2173b2faf717880f0084381f2055cca Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 22:56:43 +0300 Subject: #4170 Use GLTF scene definition for node traversal Process nodes through the scene hierarchy as defined in the GLTF file instead of attempting to reconstruct parent-child relationships. This ensures proper import of models created by GLTF transform tools. --- indra/newview/gltf/llgltfloader.cpp | 47 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7cf26941ad..2dc1aa369b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -265,31 +265,32 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Build parent mapping for efficient traversal - std::vector node_parents(mGLTFAsset.mNodes.size(), -1); - std::vector is_root(mGLTFAsset.mNodes.size(), true); - - // Build parent relationships - for (size_t parent_idx = 0; parent_idx < mGLTFAsset.mNodes.size(); parent_idx++) + // Check if we have scenes defined + if (!mGLTFAsset.mScenes.empty()) { - const auto& parent_node = mGLTFAsset.mNodes[parent_idx]; - for (S32 child_idx : parent_node.mChildren) + // Process the default scene (or first scene if no default) + S32 scene_idx = mGLTFAsset.mScene >= 0 ? mGLTFAsset.mScene : 0; + + if (scene_idx < mGLTFAsset.mScenes.size()) { - if (child_idx >= 0 && child_idx < static_cast(mGLTFAsset.mNodes.size())) + const LL::GLTF::Scene& scene = mGLTFAsset.mScenes[scene_idx]; + + LL_INFOS("GLTF_IMPORT") << "Processing scene " << scene_idx << " with " << scene.mNodes.size() << " root nodes" << LL_ENDL; + + // Process all root nodes defined in the scene + for (S32 root_idx : scene.mNodes) { - node_parents[child_idx] = static_cast(parent_idx); - is_root[child_idx] = false; + if (root_idx >= 0 && root_idx < static_cast(mGLTFAsset.mNodes.size())) + { + processNodeHierarchy(root_idx, mesh_name_counts, submodel_limit, volume_params); + } } } } - - // Process all root nodes and their hierarchies - for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + else { - if (is_root[node_idx]) - { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params); - } + LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + return false; } return true; @@ -302,6 +303,10 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map auto& node = mGLTFAsset.mNodes[node_idx]; + LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" + << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") + << " - children: " << node.mChildren.size() << LL_ENDL; + // Process this node's mesh if it has one if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) { @@ -378,6 +383,12 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map return; } } + else if (node.mMesh >= 0) + { + // Log invalid mesh reference + LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " references invalid mesh " << node.mMesh + << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + } // Process all children recursively for (S32 child_idx : node.mChildren) -- cgit v1.2.3 From 4bbd6319c7db61d12a2a14c5f6f4bc6d6b5e0645 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 10 Jun 2025 14:28:07 +0300 Subject: #4148 Skeleton Translation #2 --- indra/newview/gltf/llgltfloader.cpp | 86 ++++++++++++++++++++++++++++++++++++- indra/newview/gltf/llgltfloader.h | 3 ++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2dc1aa369b..8fe5a8f2fb 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -76,6 +76,13 @@ static const glm::mat4 coord_system_rotation( ); +static const glm::mat4 coord_system_rotationxy( + 0.f, 1.f, 0.f, 0.f, + -1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f +); + LLGLTFLoader::LLGLTFLoader(std::string filename, S32 lod, LLModelLoader::load_callback_t load_cb, @@ -254,6 +261,11 @@ bool LLGLTFLoader::parseMeshes() node.makeMatrixValid(); } + if (mGLTFAsset.mSkins.size() > 0) + { + checkForXYrotation(mGLTFAsset.mSkins[0]); + } + // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) @@ -461,7 +473,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform); // Combine transforms: coordinate rotation applied to hierarchy transform - const glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + if (mApplyXYRotation) + { + final_transform = coord_system_rotationxy * final_transform; + } // Check if we have a negative scale (flipped coordinate system) bool hasNegativeScale = glm::determinant(final_transform) < 0.0f; @@ -864,6 +880,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // This is very likely incomplete in some way. // Root shouldn't be the only one to need full coordinate fix joint_mat = coord_system_rotation * joint_mat; + if (mApplyXYRotation) + { + joint_mat = coord_system_rotationxy * joint_mat; + } } LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); @@ -1151,9 +1171,73 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially + if (mApplyXYRotation) + { + return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); + } return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); } +bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) +{ + glm::mat4 gltf_joint_rest = buildGltfRestMatrix(joint_idx, gltf_skin); + glm::mat4 test_mat = glm::inverse(gltf_joint_rest) * gltf_skin.mInverseBindMatricesData[bind_indx]; + // Normally for shoulders it should be something close to + // {1,0,0,0;0,-1,0,0;0,0,-1,0;0,0,0,1} + // rotated one will look like + // {0,0,0,-1;1,0,0,0;0,-1,0,0;0,0,0,1} + // Todo: This is a cheap hack, + // figure out how rotation is supposed to work + return abs(test_mat[0][0]) < 0.5 && abs(test_mat[1][1]) < 0.5 && abs(test_mat[2][2]) < 0.5; +} + +void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) +{ + // HACK: figure out model's rotation from shoulders' matrix. + // This is wrong on many levels: + // Too limited (only models that have shoulders), + // Will not work well with things that emulate 3 hands in some manner + // Only supports xy 90 degree rotation + // Todo: figure out how to find skeleton's orientation Correctly + // when model is rotated at a triangle level + constexpr char right_shoulder_str[] = "mShoulderRight"; + constexpr char left_shoulder_str[] = "mShoulderLeft"; + + S32 size = (S32)gltf_skin.mJoints.size(); + S32 joints_found = 0; + for (S32 i= 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + auto joint_node = mGLTFAsset.mNodes[joint]; + + // todo: we are doing this search thing everywhere, + // just pre-translate every joint + JointMap::iterator found = mJointMap.find(joint_node.mName); + if (found == mJointMap.end()) + { + // unsupported joint + continue; + } + if (found->second == right_shoulder_str || found->second == left_shoulder_str) + { + if (checkForXYrotation(gltf_skin, joint, i)) + { + joints_found++; + } + else + { + return; + } + } + } + + if (joints_found == 2) + { + // Both joints in a weird position/rotation, assume rotated model + mApplyXYRotation = true; + } +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 41e6a2dd6d..50a63c1cd8 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -156,6 +156,7 @@ protected: bool mGltfLoaded; bool mMeshesLoaded; bool mMaterialsLoaded; + bool mApplyXYRotation = false; U32 mGeneratedModelLimit; std::vector mMeshes; @@ -190,6 +191,8 @@ private: S32 findParentNode(S32 node) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; glm::mat4 computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 joint_index, const std::string& joint_name) const; + bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx); + void checkForXYrotation(const LL::GLTF::Skin& gltf_skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); void notifyUnsupportedExtension(bool unsupported); -- cgit v1.2.3 From b3fd05fa97ee90b68fb6003db68afcafeb2c2b95 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 10 Jun 2025 19:36:17 +0300 Subject: #4114 Improve GLTF mesh uploader log --- indra/newview/gltf/llgltfloader.cpp | 46 ++++++++++++++++++---- .../skins/default/xui/en/floater_model_preview.xml | 13 ++++-- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 8fe5a8f2fb..d0a0a0b541 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -302,6 +302,10 @@ bool LLGLTFLoader::parseMeshes() else { LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + + LLSD args; + args["Message"] = "NoScenesFound"; + mWarningsArray.append(args); return false; } @@ -398,8 +402,16 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map else if (node.mMesh >= 0) { // Log invalid mesh reference - LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " references invalid mesh " << node.mMesh + LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " (" << node.mName + << ") references invalid mesh " << node.mMesh << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + + LLSD args; + args["Message"] = "InvalidMeshReference"; + args["NODE_NAME"] = node.mName; + args["MESH_INDEX"] = node.mMesh; + args["TOTAL_MESHES"] = static_cast(mGLTFAsset.mMeshes.size()); + mWarningsArray.append(args); } // Process all children recursively @@ -510,8 +522,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - for (const LL::GLTF::Primitive& prim : mesh.mPrimitives) + for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) { + const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. if (prim.getVertexCount() < USHRT_MAX) { @@ -566,7 +579,14 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& impMat.mDiffuseMapFilename = filename; impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename + << " for material: " << material->mName << LL_ENDL; + + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; + args["MATERIAL_NAME"] = material->mName; + mWarningsArray.append(args); // If the image has a texture loaded already, use it if (image.mTexture.notNull()) @@ -663,10 +683,15 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (prim.getIndexCount() % 3 != 0) { - LL_WARNS("GLTF_IMPORT") << "Invalid primitive: index count " << prim.getIndexCount() - << " is not divisible by 3. GLTF files must contain triangulated geometry." << LL_ENDL; + LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx + << ": Invalid index count " << prim.getIndexCount() + << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; + LLSD args; args["Message"] = "InvalidGeometryNonTriangulated"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); mWarningsArray.append(args); continue; // Skip this primitive } @@ -818,10 +843,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& pModel->getMaterialList().push_back(materialName); mats[materialName] = impMat; } - else { - LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; + else + { + LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName + << "' primitive " << prim_idx + << " due to 16-bit index limits. Vertex count: " + << prim.getVertexCount() << " exceeds limit: " << USHRT_MAX << LL_ENDL; LLSD args; args["Message"] = "ErrorIndexLimit"; + args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; + args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); + args["LIMIT"] = USHRT_MAX; mWarningsArray.append(args); return false; } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 1c4a857dab..d9a344ba41 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -61,10 +61,15 @@ Document has no root Document has no visual_scene Unable to process mesh without position data. Invalid model. - Invalid geometry: GLTF files must contain triangulated meshes only. - Model uses unsupported extension: [EXT], related material properties are ignored. - Unable to load a model, unsupported extension: [EXT] - Unable to process mesh due to 16-bit index limits + + + No scenes defined in GLTF file + Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES]) + Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated) + Mesh [MESH_NAME]: Vertex count [VERTEX_COUNT] exceeds 16-bit limit of [LIMIT] + Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME] + Model uses unsupported extension: [EXT], related material properties are ignored + Unable to load model, unsupported extension: [EXT] Date: Tue, 10 Jun 2025 22:23:25 +0300 Subject: #4214 Weights and Joints remap #2 --- indra/newview/gltf/llgltfloader.cpp | 52 ++++++++----------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index d0a0a0b541..3273cce70b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -499,13 +499,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Mark unsuported joints with '-1' so that they won't get added into weights // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. - std::vector gltf_joint_index_use_count; + std::vector gltf_joint_index_valid; if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx) { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; size_t jointCnt = gltf_skin.mJoints.size(); - gltf_joint_index_use_count.resize(jointCnt); + gltf_joint_index_valid.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) @@ -517,7 +517,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) == mJointMap.end()) { - gltf_joint_index_use_count[i] = -1; // mark as unsupported + gltf_joint_index_valid[i] = -1; // mark as unsupported } } } @@ -760,29 +760,25 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // don't reindex them yet, more indexes will be removed // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be // 'empty' ones - if (gltf_joint_index_use_count[vertices[i].joints.x] >= 0 + if (gltf_joint_index_valid[vertices[i].joints.x] >= 0 && vertices[i].weights.x > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); - gltf_joint_index_use_count[vertices[i].joints.x]++; } - if (gltf_joint_index_use_count[vertices[i].joints.y] >= 0 + if (gltf_joint_index_valid[vertices[i].joints.y] >= 0 && vertices[i].weights.y > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); - gltf_joint_index_use_count[vertices[i].joints.y]++; } - if (gltf_joint_index_use_count[vertices[i].joints.z] >= 0 + if (gltf_joint_index_valid[vertices[i].joints.z] >= 0 && vertices[i].weights.z > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); - gltf_joint_index_use_count[vertices[i].joints.z]++; } - if (gltf_joint_index_use_count[vertices[i].joints.w] >= 0 + if (gltf_joint_index_valid[vertices[i].joints.w] >= 0 && vertices[i].weights.w > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); - gltf_joint_index_use_count[vertices[i].joints.w]++; } std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); @@ -868,20 +864,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; - std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); - gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) { // Process joint name and idnex S32 joint = gltf_skin.mJoints[i]; - if (gltf_joint_index_use_count[i] <= 0) - { - // Unused (0) or unsupported (-1) joint, drop it - continue; - } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); @@ -889,12 +878,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - else - { - llassert(false); // should have been stopped by gltf_joint_index_use_count[i] == -1 - continue; - } - gltfindex_to_joitindex_map[i] = replacement_index++; + // else thanks to gltf_joint_index_valid any illegal + // joint should have zero uses. + // Add them anyway to preserve order, remapSkinWeightsAndJoints + // will sort them out later skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); @@ -922,19 +909,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } - - // Remap indices for pModel->mSkinWeights - // Todo: this is now partially redundant due to - // remapSkinWeightsAndJoints being called later. - // Consider storing all joints now as is and let it - // ramap later due to missing weights. - for (auto& weights : pModel->mSkinWeights) - { - for (auto& weight : weights.second) - { - weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; - } - } } return true; @@ -1203,10 +1177,6 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - if (mApplyXYRotation) - { - return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); - } return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); } -- cgit v1.2.3 From db957313918d1716aa5087f283fd51d7fe49a357 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 10 Jun 2025 23:49:52 +0300 Subject: #4148 Skeleton Translation #3 'non joints' also need adjustments --- indra/llappearance/llavatarappearance.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 34d6b4db83..a3032325c9 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -1679,12 +1679,9 @@ void LLAvatarSkeletonInfo::getJointRestMatrices( { for (LLAvatarBoneInfo* bone_info : bone_list) { - if (bone_info->mIsJoint) - { - glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); - result[bone_info->mName] = rest_mat; - getJointRestMatrices(bone_info->mChildren, result, rest_mat); - } + glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); + result[bone_info->mName] = rest_mat; + getJointRestMatrices(bone_info->mChildren, result, rest_mat); } } -- cgit v1.2.3 From 98abff90a74536c9fa3a7c6d5078fc4b037c9c67 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 10 Jun 2025 23:14:25 +0300 Subject: #4248 Add safety checks to LLMeshSkinInfo::asLLSD() --- indra/llprimitive/llmodel.cpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index bc9f62b6c0..6e45e9b1bf 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1804,13 +1804,21 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi { ret["joint_names"][i] = mJointNames[i]; - for (U32 j = 0; j < 4; j++) + if (i < mInvBindMatrix.size()) { - for (U32 k = 0; k < 4; k++) + for (U32 j = 0; j < 4; j++) { - ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; + for (U32 k = 0; k < 4; k++) + { + ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; + } } } + else + { + LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size " + << mInvBindMatrix.size() << LL_ENDL; + } } for (U32 i = 0; i < 4; i++) @@ -1821,17 +1829,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } - if ( include_joints && mAlternateBindMatrix.size() > 0 ) + if (include_joints && mAlternateBindMatrix.size() > 0) { for (U32 i = 0; i < mJointNames.size(); ++i) { - for (U32 j = 0; j < 4; j++) + if (i < mAlternateBindMatrix.size()) { - for (U32 k = 0; k < 4; k++) + for (U32 j = 0; j < 4; j++) { - ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; + for (U32 k = 0; k < 4; k++) + { + ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; + } } } + else + { + LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " + << mAlternateBindMatrix.size() << LL_ENDL; + } } if (lock_scale_if_joint_position) -- cgit v1.2.3 From 48eb8a2efecb3c308092ce60dcb0c9975a0c53e1 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 11 Jun 2025 01:21:45 +0300 Subject: #4147 Joint override --- indra/newview/gltf/llgltfloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3273cce70b..3e7b14919b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -898,7 +898,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { // This is very likely incomplete in some way. // Root shouldn't be the only one to need full coordinate fix - joint_mat = coord_system_rotation * joint_mat; + joint_mat = coord_system_rotation; if (mApplyXYRotation) { joint_mat = coord_system_rotationxy * joint_mat; -- cgit v1.2.3 From 1132b19c06aa0b154991256e5063c78ad423bdc3 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 11 Jun 2025 12:31:47 +0300 Subject: #4114 Improve GLTF mesh uploader log 2 --- indra/newview/gltf/llgltfloader.cpp | 20 +++++++++++++++++--- .../skins/default/xui/en/floater_model_preview.xml | 3 ++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3e7b14919b..ffe574f4b1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -309,6 +309,21 @@ bool LLGLTFLoader::parseMeshes() return false; } + // Check total model count against limit + U32 total_models = static_cast(mModelList.size()); + if (total_models > mGeneratedModelLimit) + { + LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models + << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL; + + LLSD args; + args["Message"] = "TooManyMeshParts"; + args["PART_COUNT"] = static_cast(total_models); + args["LIMIT"] = static_cast(mGeneratedModelLimit); + mWarningsArray.append(args); + return false; + } + return true; } @@ -843,13 +858,12 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName << "' primitive " << prim_idx - << " due to 16-bit index limits. Vertex count: " - << prim.getVertexCount() << " exceeds limit: " << USHRT_MAX << LL_ENDL; + << " due to 65,534 vertex limit. Vertex count: " + << prim.getVertexCount() << LL_ENDL; LLSD args; args["Message"] = "ErrorIndexLimit"; args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); - args["LIMIT"] = USHRT_MAX; mWarningsArray.append(args); return false; } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index d9a344ba41..759952b162 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -66,10 +66,11 @@ No scenes defined in GLTF file Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES]) Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated) - Mesh [MESH_NAME]: Vertex count [VERTEX_COUNT] exceeds 16-bit limit of [LIMIT] + Unable to process mesh [MESH_NAME] due to 65,534 vertex limit. Vertex count: [VERTEX_COUNT] Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME] Model uses unsupported extension: [EXT], related material properties are ignored Unable to load model, unsupported extension: [EXT] + Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] Date: Wed, 11 Jun 2025 18:52:41 +0300 Subject: #4147 Move mAlternateBindMatrices For reduced log spam and calculutions and to make further modifications easier. --- indra/newview/gltf/llgltfloader.cpp | 38 ++++++++++++++++++------------------- indra/newview/gltf/llgltfloader.h | 1 + 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index ffe574f4b1..40b9334e63 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -903,25 +903,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // In scope of same skin multiple meshes reuse same bind matrices skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[skinIdx][i]); - // For alternate bind matrix, use the ORIGINAL joint transform (before rotation) - // Get the original joint node and use its matrix directly - // Todo: this seems blatantly wrong, it should have been rotated - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJointNode(joint, gltf_skin); // skeleton can have multiple real roots - if (root_joint == joint) - { - // This is very likely incomplete in some way. - // Root shouldn't be the only one to need full coordinate fix - joint_mat = coord_system_rotation; - if (mApplyXYRotation) - { - joint_mat = coord_system_rotationxy * joint_mat; - } - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); - - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); + skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); } } @@ -992,7 +974,23 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } - // todo: prepare mAlternateBindMatrix here + + // Todo: this seems blatantly wrong + glm::mat4 joint_mat = jointNode.mMatrix; + S32 root_joint = findValidRootJointNode(joint, skin); // skeleton can have multiple real roots and one gltf root to group them + if (root_joint == joint) + { + // This is very likely incomplete in some way. + joint_mat = coord_system_rotation; + if (mApplyXYRotation) + { + joint_mat = coord_system_rotationxy * joint_mat; + } + } + LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; + mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); if (!legal_joint) { diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 50a63c1cd8..6009a8e0c7 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -174,6 +174,7 @@ protected: // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; bind_matrices_t mInverseBindMatrices; + bind_matrices_t mAlternateBindMatrices; private: bool parseMeshes(); -- cgit v1.2.3 From 8322a9a61e951275278fbf80b0a46880f5318107 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 12 Jun 2025 14:21:40 +0300 Subject: #4147 Joint Overrides #2 --- indra/llappearance/llavatarappearance.cpp | 22 ++++-- indra/llappearance/llavatarappearance.h | 3 +- indra/newview/gltf/llgltfloader.cpp | 126 ++++++++++++++++-------------- indra/newview/gltf/llgltfloader.h | 12 ++- indra/newview/llmodelpreview.cpp | 7 +- 5 files changed, 97 insertions(+), 73 deletions(-) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index a3032325c9..f3e474dba7 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -108,7 +108,12 @@ public: private: typedef std::vector bone_info_list_t; - static void getJointRestMatrices(const bone_info_list_t& bone_list, LLAvatarAppearance::joint_rest_map_t& result, const glm::mat4 &parent_mat); + static void getJointRestMatrices( + const bone_info_list_t& bone_list, + const std::string &parent_name, + LLAvatarAppearance::joint_rest_map_t& rest, + LLAvatarAppearance::joint_parent_map_t& parent_map, + const glm::mat4& parent_mat); private: S32 mNumBones; @@ -1674,14 +1679,17 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node) void LLAvatarSkeletonInfo::getJointRestMatrices( const bone_info_list_t& bone_list, - LLAvatarAppearance::joint_rest_map_t& result, + const std::string& parent_name, + LLAvatarAppearance::joint_rest_map_t& rest, + LLAvatarAppearance::joint_parent_map_t& parent_map, const glm::mat4& parent_mat) { for (LLAvatarBoneInfo* bone_info : bone_list) { glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); - result[bone_info->mName] = rest_mat; - getJointRestMatrices(bone_info->mChildren, result, rest_mat); + rest[bone_info->mName] = rest_mat; + parent_map[bone_info->mName] = parent_name; + getJointRestMatrices(bone_info->mChildren, bone_info->mName, rest, parent_map, rest_mat); } } @@ -1746,12 +1754,10 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases return mJointAliasMap; } -LLAvatarAppearance::joint_rest_map_t LLAvatarAppearance:: getJointRestMatrices() const +void LLAvatarAppearance:: getJointRestMatrices(LLAvatarAppearance::joint_rest_map_t& rest_map, LLAvatarAppearance::joint_parent_map_t& parent_map) const { - LLAvatarAppearance::joint_rest_map_t result; glm::mat4 identity(1.f); - LLAvatarSkeletonInfo::getJointRestMatrices(sAvatarSkeletonInfo->mBoneInfoList, result, identity); - return result; + LLAvatarSkeletonInfo::getJointRestMatrices(sAvatarSkeletonInfo->mBoneInfoList, std::string(), rest_map, parent_map, identity); } diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index cea2a837cd..e48d80b8ce 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -154,8 +154,9 @@ public: const avatar_joint_list_t& getSkeleton() { return mSkeleton; } typedef std::map joint_alias_map_t; const joint_alias_map_t& getJointAliases(); + typedef std::map joint_parent_map_t; // matrix plus parent typedef std::map joint_rest_map_t; - joint_rest_map_t getJointRestMatrices() const; + void getJointRestMatrices(joint_rest_map_t& rest_map, joint_parent_map_t& parent_map) const; protected: static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 40b9334e63..292fd09035 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -95,7 +95,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_rest_map_t jointRestMatrices) //, + joint_viewer_rest_map_t jointRestMatrices, + joint_viewer_parent_map_t jointParentMap) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -107,12 +108,13 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ), + maxJointsPerMesh ) //mPreprocessGLTF(preprocess), - mMeshesLoaded(false), - mMaterialsLoaded(false), - mGeneratedModelLimit(modelLimit), - mJointRestMatrices(jointRestMatrices) + , mMeshesLoaded(false) + , mMaterialsLoaded(false) + , mGeneratedModelLimit(modelLimit) + , mJointViewerRestMatrices(jointRestMatrices) + , mJointViewerParentMap(jointParentMap) { } @@ -929,6 +931,16 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) if (mInverseBindMatrices.size() <= skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); + mAlternateBindMatrices.resize(skin_idx + 1); + } + + // Build gltf rest matrices + // This is very inefficienct, todo: run from root to children, not from children to root + joint_node_mat4_map_t gltf_rest_map; + for (S32 i = 0; i < joint_count; i++) + { + S32 joint = skin.mJoints[i]; + gltf_rest_map[joint] = buildGltfRestMatrix(joint, skin); } for (S32 i = 0; i < joint_count; i++) @@ -943,86 +955,84 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) legal_joint = true; } + // Compute bind matrices + if (!legal_joint) { - // add placeholder to not break index + // Add placeholder to not break index. + // Not going to be used by viewer, will be stripped from skin_info. LLMatrix4 gltf_transform; gltf_transform.setIdentity(); mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else if (inverse_count > i) { + // Transalte existing bind matrix to viewer's skeleton + // todo: probably should be 'to viewer's overriden skeleton' glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); glm::mat4 tranlated_original = skeleton_transform * rotated_original; glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); - - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { + // If bind matrices aren't present (they are optional in gltf), + // assume an identy matrix + // todo: find a model with this, might need to use rotated matrix glm::mat4 inv_bind(1.0f); - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); inv_bind = glm::inverse(skeleton_transform * inv_bind); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); - gltf_transform.setIdentity(); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } - // Todo: this seems blatantly wrong - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJointNode(joint, skin); // skeleton can have multiple real roots and one gltf root to group them - if (root_joint == joint) + // Compute Alternative matrices also known as overrides + + glm::mat4 joint_mat = glm::mat4(1.f); + if (legal_joint) { - // This is very likely incomplete in some way. - joint_mat = coord_system_rotation; - if (mApplyXYRotation) + joint_viewer_parent_map_t::const_iterator found = mJointViewerParentMap.find(legal_name); + if (found != mJointViewerParentMap.end() && !found->second.empty()) { - joint_mat = coord_system_rotationxy * joint_mat; + glm::mat4 gltf_joint_rest_pose = coord_system_rotation * buildGltfRestMatrix(joint, skin); + if (mApplyXYRotation) + { + gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; + } + // Compute viewer's override by moving joint to viewer's base + // This might be overused or somewhat incorect for regular cases + // But this logic should be solid for cases where model lacks + // parent joints that viewer has. + // ex: like boots that have only knees and feet, but no pelvis, + // or anything else, in which case we take viewer's pelvis + glm::mat4 viewer_rest = mJointViewerRestMatrices[found->second]; + joint_mat = glm::inverse(viewer_rest) * gltf_joint_rest_pose; } } LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + // Viewer seems to care only about translation part, + // but for parity with collada taking original value + LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); + newInverse.setTranslation(original_joint_transform.getTranslation()); + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); - if (!legal_joint) - { - LL_DEBUGS("GLTF") << "Ignoring unrecognized joint: " << legal_name << LL_ENDL; - continue; - } - - // Debug: Log original joint matrix - glm::mat4 gltf_joint_matrix = jointNode.mMatrix; - LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' original matrix:" << LL_ENDL; - for(int i = 0; i < 4; i++) - { - LL_INFOS("GLTF_DEBUG") << " [" << gltf_joint_matrix[i][0] << ", " << gltf_joint_matrix[i][1] - << ", " << gltf_joint_matrix[i][2] << ", " << gltf_joint_matrix[i][3] << "]" << LL_ENDL; - } - - // Apply coordinate system rotation to joint transform - glm::mat4 rotated_joint_matrix = coord_system_rotation * gltf_joint_matrix; - - // Debug: Log rotated joint matrix - LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' rotated matrix:" << LL_ENDL; - for(int i = 0; i < 4; i++) + if (legal_joint) { - LL_INFOS("GLTF_DEBUG") << " [" << rotated_joint_matrix[i][0] << ", " << rotated_joint_matrix[i][1] - << ", " << rotated_joint_matrix[i][2] << ", " << rotated_joint_matrix[i][3] << "]" << LL_ENDL; + // Might be needed for uploader UI to correctly identify overriden joints + // but going to be incorrect if multiple skins are present + mJointList[legal_name] = newInverse; + mJointsFromNode.push_front(legal_name); } - - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_joint_matrix)); - mJointList[legal_name] = gltf_transform; - mJointsFromNode.push_front(legal_name); - - LL_INFOS("GLTF_DEBUG") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; } } @@ -1164,10 +1174,11 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF // This function computes the transformation matrix needed to convert from GLTF skeleton space // to viewer skeleton space for a specific joint -glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 gltf_joint_index, const std::string& joint_name) const + +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t& gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) const { - joint_rest_map_t::const_iterator found = mJointRestMatrices.find(joint_name); - if (found == mJointRestMatrices.end()) + joint_viewer_rest_map_t::const_iterator found = mJointViewerRestMatrices.find(joint_name); + if (found == mJointViewerRestMatrices.end()) { // For now assume they are identical and return an identity (for ease of debuging) // But there should be no joints viewer isn't aware about @@ -1177,19 +1188,18 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski glm::mat4 viewer_joint_rest_pose = found->second; // Get the GLTF joint's rest pose (in GLTF coordinate system) - S32 joint_node_index = gltf_skin.mJoints[gltf_joint_index]; - glm::mat4 gltf_joint_rest_pose = buildGltfRestMatrix(joint_node_index, gltf_skin); - gltf_joint_rest_pose = coord_system_rotation * gltf_joint_rest_pose; + const glm::mat4 &gltf_joint_rest_pose = gltf_rest_map.at(gltf_node_index); + glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; - LLMatrix4 transform(glm::value_ptr(gltf_joint_rest_pose)); + LLMatrix4 transform(glm::value_ptr(rest_pose)); LL_CONT << transform << LL_ENDL; // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); + return viewer_joint_rest_pose * glm::inverse(rest_pose); } bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 6009a8e0c7..bf5f830d47 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -121,7 +121,9 @@ class LLGLTFLoader : public LLModelLoader { public: typedef std::map material_map; - typedef std::map joint_rest_map_t; + typedef std::map joint_viewer_parent_map_t; + typedef std::map joint_viewer_rest_map_t; + typedef std::map joint_node_mat4_map_t; LLGLTFLoader(std::string filename, S32 lod, @@ -135,7 +137,8 @@ class LLGLTFLoader : public LLModelLoader std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_rest_map_t jointRestMatrices); //, + joint_viewer_rest_map_t jointRestMatrices, + joint_viewer_parent_map_t jointParentPap); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -169,7 +172,8 @@ protected: // GLTF isn't aware of viewer's skeleton and uses it's own, // so need to take viewer's joints and use them to // recalculate iverse bind matrices - joint_rest_map_t mJointRestMatrices; + joint_viewer_rest_map_t mJointViewerRestMatrices; + joint_viewer_parent_map_t mJointViewerParentMap; // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; @@ -191,7 +195,7 @@ private: S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint S32 findParentNode(S32 node) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; - glm::mat4 computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 joint_index, const std::string& joint_name) const; + glm::mat4 computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t &gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) const; bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx); void checkForXYrotation(const LL::GLTF::Skin& gltf_skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 40b6d186b8..d6b438a6e2 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -811,7 +811,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable else { LLVOAvatar* av = getPreviewAvatar(); - LLAvatarAppearance::joint_rest_map_t rest_pose = av->getJointRestMatrices(); + LLAvatarAppearance::joint_rest_map_t rest_pose; + LLAvatarAppearance::joint_parent_map_t rest_parent_map; + av->getJointRestMatrices(rest_pose, rest_parent_map); mModelLoader = new LLGLTFLoader( filename, lod, @@ -825,7 +827,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), - rest_pose); + rest_pose, + rest_parent_map); } if (force_disable_slm) -- cgit v1.2.3 From 54660c8931593ceb465605acf872d5227e1d2d63 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 13 Jun 2025 18:33:49 +0300 Subject: #4147 Joint Overrides #3 Remande skeleton translation from default skeleton to overriden skeleton --- indra/llappearance/CMakeLists.txt | 1 + indra/llappearance/llavatarappearance.cpp | 36 +++---- indra/llappearance/llavatarappearance.h | 3 +- indra/llappearance/lljointdata.h | 44 ++++++++ indra/newview/gltf/llgltfloader.cpp | 162 ++++++++++++++++++++++-------- indra/newview/gltf/llgltfloader.h | 39 ++++++- indra/newview/llmodelpreview.cpp | 9 +- 7 files changed, 223 insertions(+), 71 deletions(-) create mode 100644 indra/llappearance/lljointdata.h diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index c3be8bc78e..6744c8d8a4 100644 --- a/indra/llappearance/CMakeLists.txt +++ b/indra/llappearance/CMakeLists.txt @@ -14,6 +14,7 @@ set(llappearance_SOURCE_FILES llavatarjoint.cpp llavatarjointmesh.cpp lldriverparam.cpp + lljointdata.h lllocaltextureobject.cpp llpolyskeletaldistortion.cpp llpolymesh.cpp diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index f3e474dba7..13bea1e5ea 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -29,6 +29,7 @@ #include "llavatarappearance.h" #include "llavatarappearancedefines.h" #include "llavatarjointmesh.h" +#include "lljointdata.h" #include "llstl.h" #include "lldir.h" #include "llpolymorph.h" @@ -108,11 +109,9 @@ public: private: typedef std::vector bone_info_list_t; - static void getJointRestMatrices( - const bone_info_list_t& bone_list, - const std::string &parent_name, - LLAvatarAppearance::joint_rest_map_t& rest, - LLAvatarAppearance::joint_parent_map_t& parent_map, + static void getJointMatricesAndHierarhy( + LLAvatarBoneInfo* bone_info, + LLJointData& data, const glm::mat4& parent_mat); private: @@ -1677,19 +1676,18 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node) return true; } -void LLAvatarSkeletonInfo::getJointRestMatrices( - const bone_info_list_t& bone_list, - const std::string& parent_name, - LLAvatarAppearance::joint_rest_map_t& rest, - LLAvatarAppearance::joint_parent_map_t& parent_map, +void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy( + LLAvatarBoneInfo* bone_info, + LLJointData& data, const glm::mat4& parent_mat) { - for (LLAvatarBoneInfo* bone_info : bone_list) + data.mName = bone_info->mName; + data.mJointMatrix = bone_info->getJointMatrix(); + data.mRestMatrix = parent_mat * data.mJointMatrix; + for (LLAvatarBoneInfo* child_info : bone_info->mChildren) { - glm::mat4 rest_mat = parent_mat * bone_info->getJointMatrix(); - rest[bone_info->mName] = rest_mat; - parent_map[bone_info->mName] = parent_name; - getJointRestMatrices(bone_info->mChildren, bone_info->mName, rest, parent_map, rest_mat); + LLJointData& child_data = data.mChildren.emplace_back(); + getJointMatricesAndHierarhy(child_info, child_data, data.mRestMatrix); } } @@ -1754,10 +1752,14 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases return mJointAliasMap; } -void LLAvatarAppearance:: getJointRestMatrices(LLAvatarAppearance::joint_rest_map_t& rest_map, LLAvatarAppearance::joint_parent_map_t& parent_map) const +void LLAvatarAppearance::getJointMatricesAndHierarhy(std::vector &data) const { glm::mat4 identity(1.f); - LLAvatarSkeletonInfo::getJointRestMatrices(sAvatarSkeletonInfo->mBoneInfoList, std::string(), rest_map, parent_map, identity); + for (LLAvatarBoneInfo* bone_info : sAvatarSkeletonInfo->mBoneInfoList) + { + LLJointData& child_data = data.emplace_back(); + LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(bone_info, child_data, identity); + } } diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index e48d80b8ce..2748da9a1d 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -42,6 +42,7 @@ class LLTexGlobalColorInfo; class LLWearableData; class LLAvatarBoneInfo; class LLAvatarSkeletonInfo; +class LLJointData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // LLAvatarAppearance @@ -156,7 +157,7 @@ public: const joint_alias_map_t& getJointAliases(); typedef std::map joint_parent_map_t; // matrix plus parent typedef std::map joint_rest_map_t; - void getJointRestMatrices(joint_rest_map_t& rest_map, joint_parent_map_t& parent_map) const; + void getJointMatricesAndHierarhy(std::vector &data) const; protected: static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree); diff --git a/indra/llappearance/lljointdata.h b/indra/llappearance/lljointdata.h new file mode 100644 index 0000000000..549f4af041 --- /dev/null +++ b/indra/llappearance/lljointdata.h @@ -0,0 +1,44 @@ +/** + * @file lljointdata.h + * @brief LLJointData class for holding individual joint data and skeleton + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLJOINTDATA_H +#define LL_LLJOINTDATA_H + +#include "v4math.h" + +// may be just move LLAvatarBoneInfo +class LLJointData +{ +public: + std::string mName; + glm::mat4 mJointMatrix; + glm::mat4 mRestMatrix; + + typedef std::vector bones_t; + bones_t mChildren; +}; + +#endif //LL_LLJOINTDATA_H diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 292fd09035..c6d2bec238 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -95,8 +95,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_viewer_rest_map_t jointRestMatrices, - joint_viewer_parent_map_t jointParentMap) //, + std::vector viewer_skeleton) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -113,8 +112,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, , mMeshesLoaded(false) , mMaterialsLoaded(false) , mGeneratedModelLimit(modelLimit) - , mJointViewerRestMatrices(jointRestMatrices) - , mJointViewerParentMap(jointParentMap) + , mViewerJointData(viewer_skeleton) { } @@ -934,13 +932,45 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) mAlternateBindMatrices.resize(skin_idx + 1); } - // Build gltf rest matrices - // This is very inefficienct, todo: run from root to children, not from children to root - joint_node_mat4_map_t gltf_rest_map; + // fill up joints related data + joints_data_map_t joints_data; + joints_name_to_node_map_t names_to_nodes; for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; - gltf_rest_map[joint] = buildGltfRestMatrix(joint, skin); + LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + JointNodeData& data = joints_data[joint]; + data.mNodeIdx = joint; + data.mJointListIdx = i; + data.mGltfRestMatrix = buildGltfRestMatrix(joint, skin); + data.mGltfMatrix = jointNode.mMatrix; + data.mOverrideMatrix = glm::mat4(1.f); + + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + data.mName = mJointMap[jointNode.mName]; + data.mIsValidViewerJoint = true; + } + else + { + data.mName = jointNode.mName; + data.mIsValidViewerJoint = false; + } + names_to_nodes[data.mName] = joint; + + for (S32 child : jointNode.mChildren) + { + JointNodeData& child_data = joints_data[child]; + child_data.mParentNodeIdx = joint; + child_data.mIsParentValidViewerJoint = data.mIsValidViewerJoint; + } + } + + // Go over viewer joints and build overrides + glm::mat4 ident(1.0); + for (auto &viewer_data : mViewerJointData) + { + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); } for (S32 i = 0; i < joint_count; i++) @@ -971,7 +1001,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) // todo: probably should be 'to viewer's overriden skeleton' glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); glm::mat4 tranlated_original = skeleton_transform * rotated_original; glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); @@ -985,7 +1015,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) // assume an identy matrix // todo: find a model with this, might need to use rotated matrix glm::mat4 inv_bind(1.0f); - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); inv_bind = glm::inverse(skeleton_transform * inv_bind); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); @@ -994,37 +1024,15 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } // Compute Alternative matrices also known as overrides - - glm::mat4 joint_mat = glm::mat4(1.f); - if (legal_joint) - { - joint_viewer_parent_map_t::const_iterator found = mJointViewerParentMap.find(legal_name); - if (found != mJointViewerParentMap.end() && !found->second.empty()) - { - glm::mat4 gltf_joint_rest_pose = coord_system_rotation * buildGltfRestMatrix(joint, skin); - if (mApplyXYRotation) - { - gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; - } - // Compute viewer's override by moving joint to viewer's base - // This might be overused or somewhat incorect for regular cases - // But this logic should be solid for cases where model lacks - // parent joints that viewer has. - // ex: like boots that have only knees and feet, but no pelvis, - // or anything else, in which case we take viewer's pelvis - glm::mat4 viewer_rest = mJointViewerRestMatrices[found->second]; - joint_mat = glm::inverse(viewer_rest) * gltf_joint_rest_pose; - } - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + LLMatrix4 original_joint_transform(glm::value_ptr(joints_data[joint].mOverrideMatrix)); // Viewer seems to care only about translation part, // but for parity with collada taking original value LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); newInverse.setTranslation(original_joint_transform.getTranslation()); - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; - mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; + mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); if (legal_joint) { @@ -1139,6 +1147,57 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& leftover) const +{ + glm::mat4 new_lefover(1.f); + glm::mat4 rest(1.f); + joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName); + if (found_node != names_to_nodes.end()) + { + S32 gltf_node_idx = found_node->second; + JointNodeData& node = gltf_nodes[gltf_node_idx]; + node.mIsOverrideValid = true; + + glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; + if (mApplyXYRotation) + { + gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; + } + node.mOverrideMatrix = glm::inverse(parent_rest) * gltf_joint_rest_pose; + + glm::vec3 override; + glm::vec3 skew; + glm::vec3 scale; + glm::vec4 perspective; + glm::quat rotation; + glm::decompose(node.mOverrideMatrix, scale, rotation, override, skew, perspective); + glm::vec3 translate; + glm::decompose(viewer_data.mJointMatrix, scale, rotation, translate, skew, perspective); + glm::mat4 viewer_joint = glm::recompose(scale, rotation, override, skew, perspective); + + node.mOverrideMatrix = viewer_joint; + rest = parent_rest * viewer_joint; + node.mOverrideRestMatrix = rest; + if (viewer_data.mName == "mPelvis") + { + // Todo: This is wrong, but this is a temporary + // solution for parts staying behind. + // Something is still missing with override mechanics + node.mOverrideMatrix = glm::mat4(1.f); + } + } + else + { + // No override for this joint + new_lefover = leftover * viewer_data.mJointMatrix; + rest = parent_rest * viewer_data.mJointMatrix; + } + for (LLJointData& child_data : viewer_data.mChildren) + { + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, new_lefover); + } +} + glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const { // This is inefficient since we are recalculating some joints multiple times over @@ -1172,23 +1231,40 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF return node.mMatrix; } +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const +{ + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? + + if (joint_node_index < 0 || joint_node_index >= static_cast(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + auto& data = joint_data.at(joint_node_index); + + if (data.mParentNodeIdx >=0) + { + return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix; + } + // Should we return armature or stop earlier? + return data.mGltfMatrix; +} + // This function computes the transformation matrix needed to convert from GLTF skeleton space // to viewer skeleton space for a specific joint -glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t& gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) const +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const { - joint_viewer_rest_map_t::const_iterator found = mJointViewerRestMatrices.find(joint_name); - if (found == mJointViewerRestMatrices.end()) + const JointNodeData& node_data = joints_data_map.at(gltf_node_index); + if (!node_data.mIsOverrideValid) { // For now assume they are identical and return an identity (for ease of debuging) - // But there should be no joints viewer isn't aware about - // Warn or assert about missing joints return glm::mat4(1.0f); } - glm::mat4 viewer_joint_rest_pose = found->second; // Get the GLTF joint's rest pose (in GLTF coordinate system) - const glm::mat4 &gltf_joint_rest_pose = gltf_rest_map.at(gltf_node_index); + const glm::mat4 &gltf_joint_rest_pose = node_data.mGltfRestMatrix; glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; @@ -1199,7 +1275,7 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_ma // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - return viewer_joint_rest_pose * glm::inverse(rest_pose); + return node_data.mOverrideRestMatrix * glm::inverse(rest_pose); } bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index bf5f830d47..408f9243c7 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -32,6 +32,7 @@ #include "asset.h" #include "llglheaders.h" +#include "lljointdata.h" #include "llmodelloader.h" // gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD @@ -125,6 +126,34 @@ class LLGLTFLoader : public LLModelLoader typedef std::map joint_viewer_rest_map_t; typedef std::map joint_node_mat4_map_t; + struct JointNodeData + { + JointNodeData() + : mJointListIdx(-1) + , mNodeIdx(-1) + , mParentNodeIdx(-1) + , mIsValidViewerJoint(false) + , mIsParentValidViewerJoint(false) + , mIsOverrideValid(false) + { + + } + S32 mJointListIdx; + S32 mNodeIdx; + S32 mParentNodeIdx; + glm::mat4 mGltfRestMatrix; + glm::mat4 mViewerRestMatrix; + glm::mat4 mOverrideRestMatrix; + glm::mat4 mGltfMatrix; + glm::mat4 mOverrideMatrix; + std::string mName; + bool mIsValidViewerJoint; + bool mIsParentValidViewerJoint; + bool mIsOverrideValid; + }; + typedef std::map joints_data_map_t; + typedef std::map joints_name_to_node_map_t; + LLGLTFLoader(std::string filename, S32 lod, LLModelLoader::load_callback_t load_cb, @@ -137,8 +166,7 @@ class LLGLTFLoader : public LLModelLoader std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_viewer_rest_map_t jointRestMatrices, - joint_viewer_parent_map_t jointParentPap); //, + std::vector viewer_skeleton); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -172,8 +200,7 @@ protected: // GLTF isn't aware of viewer's skeleton and uses it's own, // so need to take viewer's joints and use them to // recalculate iverse bind matrices - joint_viewer_rest_map_t mJointViewerRestMatrices; - joint_viewer_parent_map_t mJointViewerParentMap; + std::vector mViewerJointData; // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; @@ -194,8 +221,10 @@ private: S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint S32 findParentNode(S32 node) const; + void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& leftover) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; - glm::mat4 computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t &gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) const; + glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const; + glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const; bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx); void checkForXYrotation(const LL::GLTF::Skin& gltf_skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index d6b438a6e2..fc0a3ec58f 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -40,6 +40,7 @@ #include "lldrawable.h" #include "llface.h" #include "lliconctrl.h" +#include "lljointdata.h" #include "llmatrix4a.h" #include "llmeshrepository.h" #include "llmeshoptimizer.h" @@ -811,9 +812,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable else { LLVOAvatar* av = getPreviewAvatar(); - LLAvatarAppearance::joint_rest_map_t rest_pose; - LLAvatarAppearance::joint_parent_map_t rest_parent_map; - av->getJointRestMatrices(rest_pose, rest_parent_map); + std::vector viewer_skeleton; + av->getJointMatricesAndHierarhy(viewer_skeleton); mModelLoader = new LLGLTFLoader( filename, lod, @@ -827,8 +827,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), - rest_pose, - rest_parent_map); + viewer_skeleton); } if (force_disable_slm) -- cgit v1.2.3 From 46aeaf48034fead1dd69060f07494d53ade1ae80 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 17 Jun 2025 22:39:58 +0300 Subject: #4242 Support splitting of gltf faces that are over 16bit limit --- indra/newview/gltf/llgltfloader.cpp | 623 ++++++++++++++++++++---------------- indra/newview/gltf/llgltfloader.h | 2 +- 2 files changed, 353 insertions(+), 272 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c6d2bec238..33639cf93c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -514,13 +514,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Mark unsuported joints with '-1' so that they won't get added into weights // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. - std::vector gltf_joint_index_valid; + std::vector gltf_joint_index_use; if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx) { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; size_t jointCnt = gltf_skin.mJoints.size(); - gltf_joint_index_valid.resize(jointCnt); + gltf_joint_index_use.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) @@ -532,7 +532,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) == mJointMap.end()) { - gltf_joint_index_valid[i] = -1; // mark as unsupported + // This might need to hold a substitute index + gltf_joint_index_use[i] = -1; // mark as unsupported } } } @@ -540,332 +541,420 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) { const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; - // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. - if (prim.getVertexCount() < USHRT_MAX) - { - // So primitives already have all of the data we need for a given face in SL land. - // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call - // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 - LLVolumeFace face; - std::vector vertices; - std::vector indices; - LLImportMaterial impMat; - impMat.mDiffuseColor = LLColor4::white; // Default color + // So primitives already have all of the data we need for a given face in SL land. + // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call + // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 + LLVolumeFace face; + std::vector vertices; + + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color - // Process material if available - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + // Process material if available + if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + + // Set diffuse color from base color factor + impMat.mDiffuseColor = LLColor4( + material->mPbrMetallicRoughness.mBaseColorFactor[0], + material->mPbrMetallicRoughness.mBaseColorFactor[1], + material->mPbrMetallicRoughness.mBaseColorFactor[2], + material->mPbrMetallicRoughness.mBaseColorFactor[3] + ); + + // Process base color texture if it exists + if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - - // Set diffuse color from base color factor - impMat.mDiffuseColor = LLColor4( - material->mPbrMetallicRoughness.mBaseColorFactor[0], - material->mPbrMetallicRoughness.mBaseColorFactor[1], - material->mPbrMetallicRoughness.mBaseColorFactor[2], - material->mPbrMetallicRoughness.mBaseColorFactor[3] - ); - - // Process base color texture if it exists - if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) + S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; + if (texIndex < mGLTFAsset.mTextures.size()) { - S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; - if (texIndex < mGLTFAsset.mTextures.size()) + S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; + if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) { - S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; - if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + + // Use URI as texture file name + if (!image.mUri.empty()) { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + // URI might be a remote URL or a local path + std::string filename = image.mUri; - // Use URI as texture file name - if (!image.mUri.empty()) + // Extract just the filename from the URI + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) { - // URI might be a remote URL or a local path - std::string filename = image.mUri; - - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) - { - filename = filename.substr(pos + 1); - } + filename = filename.substr(pos + 1); + } - // Store the texture filename - impMat.mDiffuseMapFilename = filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; + // Store the texture filename + impMat.mDiffuseMapFilename = filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename - << " for material: " << material->mName << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename + << " for material: " << material->mName << LL_ENDL; - LLSD args; - args["Message"] = "TextureFound"; - args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; - args["MATERIAL_NAME"] = material->mName; - mWarningsArray.append(args); + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; + args["MATERIAL_NAME"] = material->mName; + mWarningsArray.append(args); - // If the image has a texture loaded already, use it - if (image.mTexture.notNull()) - { - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; - } - else - { - // Let the model preview know we need to load this texture - mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL; - } - } - else if (image.mTexture.notNull()) + // If the image has a texture loaded already, use it + if (image.mTexture.notNull()) { - // No URI but we have a texture, use it directly impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; } - else if (image.mBufferView >= 0) + else { - // For embedded textures (no URI but has buffer data) - // Create a pseudo filename for the embedded texture - std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; - impMat.mDiffuseMapFilename = pseudo_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; - - // Mark for loading + // Let the model preview know we need to load this texture mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL; } } + else if (image.mTexture.notNull()) + { + // No URI but we have a texture, use it directly + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + } + else if (image.mBufferView >= 0) + { + // For embedded textures (no URI but has buffer data) + // Create a pseudo filename for the embedded texture + std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; + impMat.mDiffuseMapFilename = pseudo_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; + + // Mark for loading + mNumOfFetchingTextures++; + LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + } } } } + } - // Apply the global scale and center offset to all vertices - for (U32 i = 0; i < prim.getVertexCount(); i++) - { - // Use pre-computed final_transform - glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); - glm::vec4 transformed_pos = final_transform * pos; - - GLTFVertex vert; - vert.position = glm::vec3(transformed_pos); - - if (!prim.mNormals.empty()) - { - // Use pre-computed normal_transform - glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.normal = glm::normalize(normal_transform * normal_vec); - } - else - { - // Use default normal (pointing up in model space) - vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); - LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; - } + if (prim.getIndexCount() % 3 != 0) + { + LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx + << ": Invalid index count " << prim.getIndexCount() + << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + LLSD args; + args["Message"] = "InvalidGeometryNonTriangulated"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); + mWarningsArray.append(args); + return false; // Skip this primitive + } - if (skinIdx >= 0) - { - vert.weights = glm::vec4(prim.mWeights[i]); + // Apply the global scale and center offset to all vertices + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + // Use pre-computed final_transform + glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); + glm::vec4 transformed_pos = final_transform * pos; - auto accessorIdx = prim.mAttributes.at("JOINTS_0"); - LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; - if (accessorIdx >= 0) - { - auto accessor = mGLTFAsset.mAccessors[accessorIdx]; - componentType = accessor.mComponentType; - } + GLTFVertex vert; + vert.position = glm::vec3(transformed_pos); - // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. - // Detect and unpack accordingly. - if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) - { - auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); - vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); - } - else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) - { - vert.joints = glm::unpackUint4x16(prim.mJoints[i]); - } - else - { - vert.joints = glm::zero(); - vert.weights = glm::zero(); - } - } - vertices.push_back(vert); + if (!prim.mNormals.empty()) + { + // Use pre-computed normal_transform + glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.normal = glm::normalize(normal_transform * normal_vec); } - - if (prim.getIndexCount() % 3 != 0) + else { - LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx - << ": Invalid index count " << prim.getIndexCount() - << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; - - LLSD args; - args["Message"] = "InvalidGeometryNonTriangulated"; - args["MESH_NAME"] = mesh.mName; - args["PRIMITIVE_INDEX"] = static_cast(prim_idx); - args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); - mWarningsArray.append(args); - continue; // Skip this primitive + // Use default normal (pointing up in model space) + vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); + LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; } - // When processing indices, flip winding order if needed - for (U32 i = 0; i < prim.getIndexCount(); i += 3) + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + + if (skinIdx >= 0) { - if (hasNegativeScale) + vert.weights = glm::vec4(prim.mWeights[i]); + + auto accessorIdx = prim.mAttributes.at("JOINTS_0"); + LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; + if (accessorIdx >= 0) { - // Flip winding order for negative scale - indices.push_back(prim.mIndexArray[i]); - indices.push_back(prim.mIndexArray[i + 2]); // Swap these two - indices.push_back(prim.mIndexArray[i + 1]); + auto accessor = mGLTFAsset.mAccessors[accessorIdx]; + componentType = accessor.mComponentType; + } + + // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. + // Detect and unpack accordingly. + if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) + { + auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); + vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); + } + else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) + { + vert.joints = glm::unpackUint4x16(prim.mJoints[i]); } else { - indices.push_back(prim.mIndexArray[i]); - indices.push_back(prim.mIndexArray[i + 1]); - indices.push_back(prim.mIndexArray[i + 2]); + vert.joints = glm::zero(); + vert.weights = glm::zero(); } } + vertices.push_back(vert); + } - // Check for empty vertex array before processing - if (vertices.empty()) + // Check for empty vertex array before processing + if (vertices.empty()) + { + LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive " << prim_idx << " in model " << mesh.mName << LL_ENDL; + LLSD args; + args["Message"] = "EmptyVertexArray"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); + mWarningsArray.append(args); + return false; // Skip this primitive + } + + std::vector faceVertices; + glm::vec3 min = glm::vec3(FLT_MAX); + glm::vec3 max = glm::vec3(-FLT_MAX); + + for (U32 i = 0; i < vertices.size(); i++) + { + LLVolumeFace::VertexData vert; + + // Update min/max bounds + if (i == 0) { - LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL; - continue; // Skip this primitive + min = max = vertices[i].position; + } + else + { + min.x = std::min(min.x, vertices[i].position.x); + min.y = std::min(min.y, vertices[i].position.y); + min.z = std::min(min.z, vertices[i].position.z); + max.x = std::max(max.x, vertices[i].position.x); + max.y = std::max(max.y, vertices[i].position.y); + max.z = std::max(max.z, vertices[i].position.z); } - std::vector faceVertices; - glm::vec3 min = glm::vec3(FLT_MAX); - glm::vec3 max = glm::vec3(-FLT_MAX); + LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); + LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); + vert.setPosition(position); + vert.setNormal(normal); + vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); + faceVertices.push_back(vert); - for (U32 i = 0; i < vertices.size(); i++) + if (skinIdx >= 0) { - LLVolumeFace::VertexData vert; - - // Update min/max bounds - if (i == 0) + // create list of weights that influence this vertex + LLModel::weight_list weight_list; + + // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count) + // don't reindex them yet, more indexes will be removed + // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be + // 'empty' ones + if (gltf_joint_index_use[vertices[i].joints.x] >= 0 + && vertices[i].weights.x > 0.f) { - min = max = vertices[i].position; + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); } - else + if (gltf_joint_index_use[vertices[i].joints.y] >= 0 + && vertices[i].weights.y > 0.f) { - min.x = std::min(min.x, vertices[i].position.x); - min.y = std::min(min.y, vertices[i].position.y); - min.z = std::min(min.z, vertices[i].position.z); - max.x = std::max(max.x, vertices[i].position.x); - max.y = std::max(max.y, vertices[i].position.y); - max.z = std::max(max.z, vertices[i].position.z); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); } - - LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); - LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); - vert.setPosition(position); - vert.setNormal(normal); - vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); - faceVertices.push_back(vert); - - if (skinIdx >= 0) + if (gltf_joint_index_use[vertices[i].joints.z] >= 0 + && vertices[i].weights.z > 0.f) { - // create list of weights that influence this vertex - LLModel::weight_list weight_list; - - // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count) - // don't reindex them yet, more indexes will be removed - // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be - // 'empty' ones - if (gltf_joint_index_valid[vertices[i].joints.x] >= 0 - && vertices[i].weights.x > 0.f) - { - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); - } - if (gltf_joint_index_valid[vertices[i].joints.y] >= 0 - && vertices[i].weights.y > 0.f) - { - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); - } - if (gltf_joint_index_valid[vertices[i].joints.z] >= 0 - && vertices[i].weights.z > 0.f) - { - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); - } - if (gltf_joint_index_valid[vertices[i].joints.w] >= 0 - && vertices[i].weights.w > 0.f) - { - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); - } + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + } + if (gltf_joint_index_use[vertices[i].joints.w] >= 0 + && vertices[i].weights.w > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + } - std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - std::vector wght; - F32 total = 0.f; + std::vector wght; + F32 total = 0.f; - for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j) - { - // take up to 4 most significant weights - // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. - wght.push_back(weight_list[j]); - total += weight_list[j].mWeight; - } + for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j) + { + // take up to 4 most significant weights + // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. + wght.push_back(weight_list[j]); + total += weight_list[j].mWeight; + } - if (total != 0.f) - { - F32 scale = 1.f / total; - if (scale != 1.f) - { // normalize weights - for (U32 j = 0; j < wght.size(); ++j) - { - wght[j].mWeight *= scale; - } + if (total != 0.f) + { + F32 scale = 1.f / total; + if (scale != 1.f) + { // normalize weights + for (U32 j = 0; j < wght.size(); ++j) + { + wght[j].mWeight *= scale; } } + } - if (wght.size() > 0) - { - pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; - } + if (wght.size() > 0) + { + pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } } + } - face.fillFromLegacyData(faceVertices, indices); - face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); - face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + // Create a unique material name for this primitive + std::string materialName; + if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + materialName = material->mName; - pModel->getVolumeFaces().push_back(face); + if (materialName.empty()) + { + materialName = "mat" + std::to_string(prim.mMaterial); + } + } + else + { + materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + } + mats[materialName] = impMat; + + // Indices handling + if (faceVertices.size() >= USHRT_MAX) + { + // Will have to remap 32 bit indices into 16 bit indices + // For the sake of simplicity build vector of 32 bit indices first + std::vector indices_32; + for (U32 i = 0; i < prim.getIndexCount(); i += 3) + { + // When processing indices, flip winding order if needed + if (hasNegativeScale) + { + // Flip winding order for negative scale + indices_32.push_back(prim.mIndexArray[i]); + indices_32.push_back(prim.mIndexArray[i + 2]); // Swap these two + indices_32.push_back(prim.mIndexArray[i + 1]); + } + else + { + indices_32.push_back(prim.mIndexArray[i]); + indices_32.push_back(prim.mIndexArray[i + 1]); + indices_32.push_back(prim.mIndexArray[i + 2]); + } + } - // Create a unique material name for this primitive - std::string materialName; - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + // remap 32 bit into multiple 16 bit ones + std::vector indices_16; + std::vector vertices_remap; // should it be a point map? + vertices_remap.resize(faceVertices.size(), -1); + std::vector face_verts; + min = glm::vec3(FLT_MAX); + max = glm::vec3(-FLT_MAX); + for (size_t idx = 0; idx < indices_32.size(); idx++) { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - materialName = material->mName; + size_t vert_index = indices_32[idx]; + if (vertices_remap[vert_index] == -1) + { + // First encounter, add it + size_t new_vert_idx = face_verts.size(); + vertices_remap[vert_index] = (S64)new_vert_idx; + face_verts.push_back(faceVertices[vert_index]); + vert_index = new_vert_idx; + + // Update min/max bounds + const LLVector4a& vec = face_verts[new_vert_idx].getPosition(); + if (new_vert_idx == 0) + { + min.x = vec[0]; + min.y = vec[1]; + min.z = vec[2]; + max = min; + } + else + { + min.x = std::min(min.x, vec[0]); + min.y = std::min(min.y, vec[1]); + min.z = std::min(min.z, vec[2]); + max.x = std::max(max.x, vec[0]); + max.y = std::max(max.y, vec[1]); + max.z = std::max(max.z, vec[2]); + } + } + else + { + // already in vector, get position + vert_index = (size_t)vertices_remap[vert_index]; + } + indices_16.push_back((U16)vert_index); - if (materialName.empty()) + if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532) { - materialName = "mat" + std::to_string(prim.mMaterial); + LLVolumeFace face; + face.fillFromLegacyData(face_verts, indices_16); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); + + std::fill(vertices_remap.begin(), vertices_remap.end(), -1); + indices_16.clear(); + face_verts.clear(); + + min = glm::vec3(FLT_MAX); + max = glm::vec3(-FLT_MAX); } } - else + if (indices_16.size() > 0 && face_verts.size() > 0) { - materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + LLVolumeFace face; + face.fillFromLegacyData(face_verts, indices_16); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); } - - pModel->getMaterialList().push_back(materialName); - mats[materialName] = impMat; } else { - LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName - << "' primitive " << prim_idx - << " due to 65,534 vertex limit. Vertex count: " - << prim.getVertexCount() << LL_ENDL; - LLSD args; - args["Message"] = "ErrorIndexLimit"; - args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; - args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); - mWarningsArray.append(args); - return false; + // can use indices directly + std::vector indices; + for (U32 i = 0; i < prim.getIndexCount(); i += 3) + { + // When processing indices, flip winding order if needed + if (hasNegativeScale) + { + // Flip winding order for negative scale + indices.push_back(prim.mIndexArray[i]); + indices.push_back(prim.mIndexArray[i + 2]); // Swap these two + indices.push_back(prim.mIndexArray[i + 1]); + } + else + { + indices.push_back(prim.mIndexArray[i]); + indices.push_back(prim.mIndexArray[i + 1]); + indices.push_back(prim.mIndexArray[i + 2]); + } + } + + face.fillFromLegacyData(faceVertices, indices); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); } } @@ -892,7 +981,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - // else thanks to gltf_joint_index_valid any illegal + // else thanks to gltf_joint_index_usage any illegal // joint should have zero uses. // Add them anyway to preserve order, remapSkinWeightsAndJoints // will sort them out later @@ -970,7 +1059,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) { - buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident); } for (S32 i = 0; i < joint_count; i++) @@ -1147,7 +1236,7 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } -void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& leftover) const +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest) const { glm::mat4 new_lefover(1.f); glm::mat4 rest(1.f); @@ -1176,25 +1265,17 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map glm::mat4 viewer_joint = glm::recompose(scale, rotation, override, skew, perspective); node.mOverrideMatrix = viewer_joint; - rest = parent_rest * viewer_joint; + rest = parent_rest * node.mOverrideMatrix; node.mOverrideRestMatrix = rest; - if (viewer_data.mName == "mPelvis") - { - // Todo: This is wrong, but this is a temporary - // solution for parts staying behind. - // Something is still missing with override mechanics - node.mOverrideMatrix = glm::mat4(1.f); - } } else { // No override for this joint - new_lefover = leftover * viewer_data.mJointMatrix; rest = parent_rest * viewer_data.mJointMatrix; } for (LLJointData& child_data : viewer_data.mChildren) { - buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, new_lefover); + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest); } } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 408f9243c7..aa77c39030 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -221,7 +221,7 @@ private: S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint S32 findParentNode(S32 node) const; - void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& leftover) const; + void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const; glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const; -- cgit v1.2.3 From bb45bfae0d5f3a7c4d588a2727a86172b12e7c40 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 18 Jun 2025 15:57:11 +0300 Subject: #4204 Fix GLTF texture loading to match DAE loader behavior --- indra/newview/gltf/llgltfloader.cpp | 72 +++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 33639cf93c..1f65435728 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -52,11 +52,14 @@ #include "llsdserialize.h" #include "lljoint.h" +#include "llbase64.h" +#include "lldir.h" #include "llmatrix4a.h" #include #include +#include static const std::string lod_suffix[LLModel::NUM_LODS] = { @@ -609,9 +612,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } else { - // Let the model preview know we need to load this texture - mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL; + // Texture will be loaded later through the callback system + LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; } } else if (image.mTexture.notNull()) @@ -623,14 +625,62 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else if (image.mBufferView >= 0) { // For embedded textures (no URI but has buffer data) - // Create a pseudo filename for the embedded texture - std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; - impMat.mDiffuseMapFilename = pseudo_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; - - // Mark for loading - mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + // Extract the texture to a temporary file immediately + if (image.mBufferView < mGLTFAsset.mBufferViews.size()) + { + const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; + if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) + { + const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + + if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) + { + // Extract image data + const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; + U32 data_size = buffer_view.mByteLength; + + // Determine the file extension + std::string extension = ".png"; // Default + if (!image.mMimeType.empty()) + { + if (image.mMimeType == "image/jpeg") + extension = ".jpg"; + else if (image.mMimeType == "image/png") + extension = ".png"; + } + else if (data_size >= 4) + { + if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) + extension = ".jpg"; // JPEG magic bytes + else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) + extension = ".png"; // PNG magic bytes + } + + // Create a temporary file + std::string temp_dir = gDirUtilp->getTempDir(); + std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + + "gltf_embedded_" + std::to_string(sourceIndex) + extension; + + // Write the image data to the temporary file + std::ofstream temp_file(temp_filename, std::ios::binary); + if (temp_file.is_open()) + { + temp_file.write(reinterpret_cast(data_ptr), data_size); + temp_file.close(); + + // Use the temp file as the texture filename + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; + + LL_INFOS("GLTF_IMPORT") << "Extracted embedded texture to: " << temp_filename << LL_ENDL; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; + } + } + } + } } } } -- cgit v1.2.3 From 68dc0919b3645724f70ca6e609a3f893cba49a69 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 18 Jun 2025 16:50:17 +0300 Subject: #4204 Don't show material editor with model loader --- indra/newview/llfloatermodelpreview.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index f962217480..84b9cb18f8 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -114,11 +114,6 @@ void LLMeshFilePicker::notify(const std::vector& filenames) if (filenames.size() > 0) { mMP->loadModel(filenames[0], mLOD); - - if (filenames[0].substr(filenames[0].length() - 4) == ".glb" || filenames[0].substr(filenames[0].length() - 5) == ".gltf") - { - LLMaterialEditor::loadMaterialFromFile(filenames[0], -1); - } } else { -- cgit v1.2.3 From 5099401a5364ebfe4ec908fc0481ed3568778651 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 18 Jun 2025 17:18:48 +0300 Subject: #4204 Log embedded texture extraction failure --- indra/newview/gltf/llgltfloader.cpp | 6 ++++++ indra/newview/skins/default/xui/en/floater_model_preview.xml | 1 + 2 files changed, 7 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1f65435728..50ce075d65 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -677,6 +677,12 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else { LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; + + LLSD args; + args["Message"] = "FailedToCreateTempFile"; + args["TEXTURE_INDEX"] = sourceIndex; + args["TEMP_FILE"] = temp_filename; + mWarningsArray.append(args); } } } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 759952b162..6c177e5008 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -71,6 +71,7 @@ Model uses unsupported extension: [EXT], related material properties are ignored Unable to load model, unsupported extension: [EXT] Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] + Failed to create temporary file for embedded texture [TEXTURE_INDEX]: [TEMP_FILE] Date: Wed, 18 Jun 2025 21:55:57 +0300 Subject: #4250 Crash uploading a dae model --- indra/llprimitive/llmodel.cpp | 48 ++++++++++++++++++++++------------------ indra/newview/llskinningutil.cpp | 3 ++- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 6e45e9b1bf..e47c0d8d07 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1804,21 +1804,23 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi { ret["joint_names"][i] = mJointNames[i]; - if (i < mInvBindMatrix.size()) - { - for (U32 j = 0; j < 4; j++) - { - for (U32 k = 0; k < 4; k++) - { - ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; - } - } - } - else + // For model to work at all there must be a matching bind matrix, + // so supply an indentity one if it isn't true + // Note: can build an actual bind matrix from joints + const LLMatrix4a& inv_bind = mInvBindMatrix.size() > i ? mInvBindMatrix[i] : LLMatrix4a::identity(); + if (i >= mInvBindMatrix.size()) { LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size " << mInvBindMatrix.size() << LL_ENDL; } + + for (U32 j = 0; j < 4; j++) + { + for (U32 k = 0; k < 4; k++) + { + ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k]; + } + } } for (U32 i = 0; i < 4; i++) @@ -1829,25 +1831,27 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } + // optional 'joint overrides' if (include_joints && mAlternateBindMatrix.size() > 0) { for (U32 i = 0; i < mJointNames.size(); ++i) { - if (i < mAlternateBindMatrix.size()) - { - for (U32 j = 0; j < 4; j++) - { - for (U32 k = 0; k < 4; k++) - { - ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; - } - } - } - else + // If there is not enough to match mJointNames, + // either supply no alternate matrixes at all or supply + // replacements + const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity(); + if (i >= mAlternateBindMatrix.size()) { LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " << mAlternateBindMatrix.size() << LL_ENDL; } + for (U32 j = 0; j < 4; j++) + { + for (U32 k = 0; k < 4; k++) + { + ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k]; + } + } } if (lock_scale_if_joint_position) diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index 43836a420b..47f58afa00 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -360,7 +360,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a { rig_info_tab[joint_num].setIsRiggedTo(true); - const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index]; + size_t bind_poses_size = skin->mBindPoseMatrix.size(); + const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity(); LLVector4a pos_joint_space; mat.affineTransform(pos, pos_joint_space); -- cgit v1.2.3 From f5320302d4b81825b1037ca0074a65ad9eac14ac Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 19 Jun 2025 19:20:35 +0300 Subject: #4214 Revert and remake "weights remap" This reverts commits fe10a83f69bb26eb581e143fb99c1250c355938b and 08f6f5c697fce4ccbfba357ab9ce5af915dd0574.. --- indra/llprimitive/llmodel.cpp | 87 ---------------------- indra/llprimitive/llmodel.h | 1 - indra/newview/gltf/llgltfloader.cpp | 79 ++++++++++++++++++-- indra/newview/gltf/llgltfloader.h | 3 + .../skins/default/xui/en/floater_model_preview.xml | 2 + 5 files changed, 78 insertions(+), 94 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index e47c0d8d07..3d31cfbb7f 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -112,93 +112,6 @@ void LLModel::remapVolumeFaces() } } -void LLModel::remapSkinWeightsAndJoints() -{ - if (mSkinWeights.empty()) - { - return; - } - - mPosition.clear(); - - // Make a list of positions - std::set positions; - for (S32 i = 0; i < getNumVolumeFaces(); ++i) - { - for (S32 j = 0; j < mVolumeFaces[i].mNumVertices; ++j) - { - positions.emplace(mVolumeFaces[i].mPositions[j].getF32ptr()); - } - } - - // Build new list of weights and record used joints - weight_map replacement_weights; - std::vector joint_index_use_count; - size_t joint_count = mSkinInfo.mJointNames.size(); - joint_index_use_count.resize(joint_count, 0); - for (const LLVector3& pos : positions) - { - mPosition.push_back(pos); - auto found = mSkinWeights.find(pos); - if (found != mSkinWeights.end()) - { - replacement_weights[pos] = found->second; - - for (auto& weight : found->second) - { - if (joint_count > weight.mJointIdx) - { - joint_index_use_count[weight.mJointIdx]++; - } - } - } - } - - // go over joint data and remap joints - // prepare joint map - std::vector replacement_joint_names; - std::vector replacement_joint_nums; - LLMeshSkinInfo::matrix_list_t replacement_inv_bind; - LLMeshSkinInfo::matrix_list_t replacement_alt_bind; - std::vector index_map; - index_map.resize(joint_count); - S32 replacement_index = 0; - - for (S32 i = 0; i < joint_count; i++) - { - if (joint_index_use_count[i] > 0) - { - replacement_joint_names.push_back(mSkinInfo.mJointNames[i]); - replacement_joint_nums.push_back(mSkinInfo.mJointNums[i]); - replacement_inv_bind.push_back(mSkinInfo.mInvBindMatrix[i]); - replacement_alt_bind.push_back(mSkinInfo.mAlternateBindMatrix[i]); - index_map[i] = replacement_index++; - } - } - - // Apply new data - mSkinInfo.mJointNames.clear(); - mSkinInfo.mJointNames = replacement_joint_names; - mSkinInfo.mJointNums.clear(); - mSkinInfo.mJointNums = replacement_joint_nums; - mSkinInfo.mInvBindMatrix.clear(); - mSkinInfo.mInvBindMatrix = replacement_inv_bind; - mSkinInfo.mAlternateBindMatrix.clear(); - mSkinInfo.mAlternateBindMatrix = replacement_alt_bind; - - // remap weights - for (auto& weights : replacement_weights) - { - for (auto& weight : weights.second) - { - weight.mJointIdx = index_map[weight.mJointIdx]; - } - } - - mSkinWeights.clear(); - mSkinWeights = replacement_weights; -} - void LLModel::optimizeVolumeFaces() { for (S32 i = 0; i < getNumVolumeFaces(); ++i) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 7fa3a00ee2..5c6d0a55d2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -205,7 +205,6 @@ public: void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); - void remapSkinWeightsAndJoints(); void optimizeVolumeFaces(); void offsetMesh( const LLVector3& pivotPoint ); void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 50ce075d65..e495abfe75 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -225,8 +225,6 @@ void LLGLTFLoader::addModelToScene( { // remove unused/redundant vertices model->remapVolumeFaces(); - // remove unused/redundant weights and joints - model->remapSkinWeightsAndJoints(); mModelList.push_back(model); @@ -821,21 +819,25 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& && vertices[i].weights.x > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + gltf_joint_index_use[vertices[i].joints.x]++; } if (gltf_joint_index_use[vertices[i].joints.y] >= 0 && vertices[i].weights.y > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + gltf_joint_index_use[vertices[i].joints.y]++; } if (gltf_joint_index_use[vertices[i].joints.z] >= 0 && vertices[i].weights.z > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + gltf_joint_index_use[vertices[i].joints.z]++; } if (gltf_joint_index_use[vertices[i].joints.w] >= 0 && vertices[i].weights.w > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + gltf_joint_index_use[vertices[i].joints.w]++; } std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); @@ -1022,14 +1024,23 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; + S32 valid_joints_count = mValidJointsCount[skinIdx]; + std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); + gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; + S32 stripped_valid_joints = 0; for (size_t i = 0; i < jointCnt; ++i) { // Process joint name and idnex S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] < 0) + { + // unsupported (-1) joint, drop it + continue; + } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); @@ -1037,10 +1048,28 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - // else thanks to gltf_joint_index_usage any illegal - // joint should have zero uses. - // Add them anyway to preserve order, remapSkinWeightsAndJoints - // will sort them out later + else + { + llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 + continue; + } + + if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT + && gltf_joint_index_use[i] == 0 + && legal_name != "mPelvis") + { + // Unused (0) joint + // It's perfectly valid to have more joints than is in use + // Ex: sandals that make your legs digitigrade despite not skining to + // knees or the like. + // But if model is over limid, drop extras sans pelvis. + // Keeping 'pelvis' is a workaround to keep preview whole. + // Todo: consider improving this, either take as much as possible or + // ensure common parents/roots are included + continue; + } + + gltfindex_to_joitindex_map[i] = replacement_index++; skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); @@ -1050,6 +1079,28 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); } + + if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel + << " Count: " << (S32)skin_info.mInvBindMatrix.size() + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + LLSD args; + args["Message"] = "ModelTooManyJoint"; + args["MODEL_NAME"] = pModel->mLabel; + args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + + // Remap indices for pModel->mSkinWeights + for (auto& weights : pModel->mSkinWeights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; + } + } } return true; @@ -1075,6 +1126,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); + mValidJointsCount.resize(skin_idx + 1, 0); } // fill up joints related data @@ -1095,6 +1147,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { data.mName = mJointMap[jointNode.mName]; data.mIsValidViewerJoint = true; + mValidJointsCount[skin_idx]++; } else { @@ -1111,6 +1164,20 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } } + if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" + << " Count: " << mValidJointsCount[skin_idx] + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + + LLSD args; + args["Message"] = "SkinJointsOverLimit"; + args["SKIN_INDEX"] = (S32)skin_idx; + args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + // Go over viewer joints and build overrides glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index aa77c39030..393df8c4ee 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -207,6 +207,9 @@ protected: bind_matrices_t mInverseBindMatrices; bind_matrices_t mAlternateBindMatrices; + // per skin joint count, needs to be tracked for the sake of limits check. + std::vector mValidJointsCount; + private: bool parseMeshes(); void uploadMeshes(); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 6c177e5008..4c60320a94 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -72,6 +72,8 @@ Unable to load model, unsupported extension: [EXT] Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] Failed to create temporary file for embedded texture [TEXTURE_INDEX]: [TEMP_FILE] + Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis. + Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail Date: Thu, 19 Jun 2025 20:57:44 +0300 Subject: #4204 Unused code cleanup (#4278) --- indra/newview/gltf/llgltfloader.cpp | 377 ++++----------------- indra/newview/gltf/llgltfloader.h | 3 +- .../skins/default/xui/en/floater_model_preview.xml | 2 +- 3 files changed, 65 insertions(+), 317 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index e495abfe75..056d3df1a6 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -623,67 +623,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else if (image.mBufferView >= 0) { // For embedded textures (no URI but has buffer data) - // Extract the texture to a temporary file immediately - if (image.mBufferView < mGLTFAsset.mBufferViews.size()) + std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); + if (!temp_filename.empty()) { - const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; - if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) - { - const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; - - if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) - { - // Extract image data - const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; - U32 data_size = buffer_view.mByteLength; - - // Determine the file extension - std::string extension = ".png"; // Default - if (!image.mMimeType.empty()) - { - if (image.mMimeType == "image/jpeg") - extension = ".jpg"; - else if (image.mMimeType == "image/png") - extension = ".png"; - } - else if (data_size >= 4) - { - if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) - extension = ".jpg"; // JPEG magic bytes - else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) - extension = ".png"; // PNG magic bytes - } - - // Create a temporary file - std::string temp_dir = gDirUtilp->getTempDir(); - std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + - "gltf_embedded_" + std::to_string(sourceIndex) + extension; - - // Write the image data to the temporary file - std::ofstream temp_file(temp_filename, std::ios::binary); - if (temp_file.is_open()) - { - temp_file.write(reinterpret_cast(data_ptr), data_size); - temp_file.close(); - - // Use the temp file as the texture filename - impMat.mDiffuseMapFilename = temp_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; - - LL_INFOS("GLTF_IMPORT") << "Extracted embedded texture to: " << temp_filename << LL_ENDL; - } - else - { - LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; - - LLSD args; - args["Message"] = "FailedToCreateTempFile"; - args["TEXTURE_INDEX"] = sourceIndex; - args["TEMP_FILE"] = temp_filename; - mWarningsArray.append(args); - } - } - } + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; } } } @@ -1730,286 +1674,89 @@ void LLGLTFLoader::uploadMeshes() // convert raw image buffers to texture UUIDs & assemble into a render material void LLGLTFLoader::uploadMaterials() { - LL_INFOS("GLTF_IMPORT") << "Uploading materials, count: " << mMaterials.size() << LL_ENDL; - - for (gltf_render_material& mat : mMaterials) - { - LL_INFOS("GLTF_IMPORT") << "Processing material: " << mat.name << LL_ENDL; - - // Process base color texture - if (mat.hasBaseTex && mat.baseColorTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; - if (gtex.imageUuid.isNull()) - { - LL_INFOS("GLTF_IMPORT") << "Loading base color texture for material " << mat.name << LL_ENDL; - gtex.imageUuid = imageBufferToTextureUUID(gtex); - - if (gtex.imageUuid.notNull()) - { - LL_INFOS("GLTF_IMPORT") << "Base color texture loaded, ID: " << gtex.imageUuid.asString() << LL_ENDL; - } - else - { - LL_WARNS("GLTF_IMPORT") << "Failed to load base color texture for material " << mat.name << LL_ENDL; - } - } - } - - // Process other textures similarly - if (mat.hasMRTex && mat.metalRoughTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasNormalTex && mat.normalTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.normalTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasOcclusionTex && mat.occlusionTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasEmissiveTex && mat.emissiveTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - } - - // Update material map for all model instances to ensure textures are properly associated - // mScene is a std::map, not an array, so we need to iterate through it correctly - for (auto& scene_entry : mScene) - { - for (LLModelInstance& instance : scene_entry.second) - { - LLModel* model = instance.mModel; - - if (model) - { - for (size_t i = 0; i < model->getMaterialList().size(); ++i) - { - const std::string& matName = model->getMaterialList()[i]; - if (!matName.empty()) - { - // Ensure this material exists in the instance's material map - if (instance.mMaterial.find(matName) == instance.mMaterial.end()) - { - // Find material in our render materials - for (const auto& renderMat : mMaterials) - { - if (renderMat.name == matName) - { - // Create an import material from the render material - LLImportMaterial impMat; - impMat.mDiffuseColor = renderMat.baseColor; - - // Set diffuse texture if available - if (renderMat.hasBaseTex && renderMat.baseColorTexIdx < mTextures.size()) - { - const gltf_texture& gtex = mTextures[renderMat.baseColorTexIdx]; - if (!gtex.imageUuid.isNull()) - { - impMat.setDiffuseMap(gtex.imageUuid); - LL_INFOS("GLTF_IMPORT") << "Setting texture " << gtex.imageUuid.asString() << " for material " << matName << LL_ENDL; - } - } - - // Add material to instance's material map - instance.mMaterial[matName] = impMat; - LL_INFOS("GLTF_IMPORT") << "Added material " << matName << " to instance" << LL_ENDL; - break; - } - } - } - } - } - } - } - } } - -LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) +std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Invalid texture indices in imageBufferToTextureUUID" << LL_ENDL; - return LLUUID::null; - } - - gltf_image& image = mImages[tex.imageIdx]; - gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) + return ""; - S32 sourceIndex = tex.imageIdx; + S32 sourceIndex = mGLTFAsset.mTextures[textureIndex].mSource; if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) - { - LL_WARNS("GLTF_IMPORT") << "Invalid image index: " << sourceIndex << LL_ENDL; - return LLUUID::null; - } + return ""; - LL::GLTF::Image& source_image = mGLTFAsset.mImages[sourceIndex]; + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - // If the image already has a texture loaded, use it - if (source_image.mTexture.notNull()) + // Handle URI-based textures + if (!image.mUri.empty()) { - LL_INFOS("GLTF_IMPORT") << "Using already loaded texture ID: " << source_image.mTexture->getID().asString() << LL_ENDL; - return source_image.mTexture->getID(); + return image.mUri; // Return URI directly } - // Create an import material to pass to the texture load function - LLImportMaterial material; - - // Try to get the texture filename from the URI - if (!source_image.mUri.empty()) + // Handle embedded textures + if (image.mBufferView >= 0) { - std::string filename = source_image.mUri; - - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) + if (image.mBufferView < mGLTFAsset.mBufferViews.size()) { - filename = filename.substr(pos + 1); - } - - material.mDiffuseMapFilename = filename; - material.mDiffuseMapLabel = filename; - } - else if (source_image.mBufferView >= 0) - { - // For embedded textures, create a pseudo-filename - std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; - material.mDiffuseMapFilename = pseudo_filename; - material.mDiffuseMapLabel = pseudo_filename; - } - else - { - LL_WARNS("GLTF_IMPORT") << "No URI or buffer data for image" << LL_ENDL; - return LLUUID::null; - } - - // Create LLSD container with image and sampler data for texture upload - LLSD texture_data = LLSD::emptyMap(); - - // Image data - texture_data["width"] = LLSD::Integer(image.width); - texture_data["height"] = LLSD::Integer(image.height); - texture_data["components"] = LLSD::Integer(image.numChannels); - texture_data["bytes_per_component"] = LLSD::Integer(image.bytesPerChannel); - texture_data["pixel_type"] = LLSD::Integer(image.pixelType); - - // Sampler data - texture_data["min_filter"] = LLSD::Integer(sampler.minFilter); - texture_data["mag_filter"] = LLSD::Integer(sampler.magFilter); - texture_data["wrap_s"] = LLSD::Integer(sampler.wrapS); - texture_data["wrap_t"] = LLSD::Integer(sampler.wrapT); - - // Add URI for reference - if (!source_image.mUri.empty()) - { - texture_data["uri"] = source_image.mUri; - } - - // Check if we have a buffer view for embedded data - if (source_image.mBufferView >= 0) - { - texture_data["has_embedded_data"] = LLSD::Boolean(true); - texture_data["buffer_view"] = LLSD::Integer(source_image.mBufferView); - - // Extract embedded data for texture loading - if (source_image.mBufferView < mGLTFAsset.mBufferViews.size()) - { - const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[source_image.mBufferView]; + const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) { const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) { - // Add embedded data reference to texture_data - texture_data["buffer_index"] = LLSD::Integer(buffer_view.mBuffer); - texture_data["byte_offset"] = LLSD::Integer(buffer_view.mByteOffset); - texture_data["byte_length"] = LLSD::Integer(buffer_view.mByteLength); + // Extract image data + const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; + U32 data_size = buffer_view.mByteLength; - LL_INFOS("GLTF_IMPORT") << "Found embedded texture data: offset=" << buffer_view.mByteOffset - << " length=" << buffer_view.mByteLength << LL_ENDL; - } - } - } - } - - // Store the texture metadata in the binding field - std::ostringstream ostr; - LLSDSerialize::toXML(texture_data, ostr); - material.mBinding = ostr.str(); - - LL_INFOS("GLTF_IMPORT") << "Loading texture: " << material.mDiffuseMapFilename << LL_ENDL; - - // Flag to track if texture was loaded immediately - bool texture_loaded = false; - - // Call texture loading function with our import material - if (mTextureLoadFunc) - { - // Increment textures to fetch counter BEFORE calling load function - mNumOfFetchingTextures++; - - U32 result = mTextureLoadFunc(material, mOpaqueData); + // Determine the file extension + std::string extension = ".png"; // Default + if (!image.mMimeType.empty()) + { + if (image.mMimeType == "image/jpeg") + extension = ".jpg"; + else if (image.mMimeType == "image/png") + extension = ".png"; + } + else if (data_size >= 4) + { + if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) + extension = ".jpg"; // JPEG magic bytes + else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) + extension = ".png"; // PNG magic bytes + } - // If result is 0, texture is being loaded asynchronously - // If result is >0, texture was loaded immediately - if (result > 0) - { - // Texture was loaded immediately, so decrement counter - mNumOfFetchingTextures--; - texture_loaded = true; + // Create a temporary file + std::string temp_dir = gDirUtilp->getTempDir(); + std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + + "gltf_embedded_" + texture_type + "_" + std::to_string(sourceIndex) + extension; - if (material.getDiffuseMap().notNull()) - { - LL_INFOS("GLTF_IMPORT") << "Texture loaded successfully, ID: " << material.getDiffuseMap().asString() << LL_ENDL; + // Write the image data to the temporary file + std::ofstream temp_file(temp_filename, std::ios::binary); + if (temp_file.is_open()) + { + temp_file.write(reinterpret_cast(data_ptr), data_size); + temp_file.close(); - // Store the texture in the source image for future reference - if (source_image.mTexture.isNull()) - { - // Create and store a texture object using the UUID - source_image.mTexture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap()); + LL_INFOS("GLTF_IMPORT") << "Extracted embedded " << texture_type << " texture to: " << temp_filename << LL_ENDL; + return temp_filename; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for " << texture_type << " texture: " << temp_filename << LL_ENDL; + + LLSD args; + args["Message"] = "FailedToCreateTempFile"; + args["TEXTURE_INDEX"] = sourceIndex; + args["TEXTURE_TYPE"] = texture_type; + args["TEMP_FILE"] = temp_filename; + mWarningsArray.append(args); + } } - - return material.getDiffuseMap(); } } - else if (result == 0) - { - LL_INFOS("GLTF_IMPORT") << "Texture loading queued asynchronously for " << material.mDiffuseMapFilename << LL_ENDL; - } - else // result < 0, indicating error - { - // Texture loading failed, decrement counter - mNumOfFetchingTextures--; - LL_WARNS("GLTF_IMPORT") << "Texture loading failed for " << material.mDiffuseMapFilename << LL_ENDL; - } - } - else - { - LL_WARNS("GLTF_IMPORT") << "No texture loading function available" << LL_ENDL; } - return LLUUID::null; + return ""; } void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 393df8c4ee..e0b3664012 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -230,7 +230,8 @@ private: glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const; bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx); void checkForXYrotation(const LL::GLTF::Skin& gltf_skin); - LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + + std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type); void notifyUnsupportedExtension(bool unsupported); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 4c60320a94..80b6427738 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -71,7 +71,7 @@ Model uses unsupported extension: [EXT], related material properties are ignored Unable to load model, unsupported extension: [EXT] Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] - Failed to create temporary file for embedded texture [TEXTURE_INDEX]: [TEMP_FILE] + Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE] Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis. Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail -- cgit v1.2.3 From 1a6e3286110f9e54611715b44c5f8b47d08dc310 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 19 Jun 2025 22:31:38 +0300 Subject: #4204 Remove more unused code --- indra/newview/gltf/llgltfloader.cpp | 200 +----------------------------------- indra/newview/gltf/llgltfloader.h | 95 ----------------- 2 files changed, 2 insertions(+), 293 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 056d3df1a6..7a1ebf0610 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -111,9 +111,6 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointsFromNodes, jointAliasMap, maxJointsPerMesh ) - //mPreprocessGLTF(preprocess), - , mMeshesLoaded(false) - , mMaterialsLoaded(false) , mGeneratedModelLimit(modelLimit) , mViewerJointData(viewer_skeleton) { @@ -137,15 +134,11 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) notifyUnsupportedExtension(false); - mMeshesLoaded = parseMeshes(); - if (mMeshesLoaded) uploadMeshes(); - - mMaterialsLoaded = parseMaterials(); - if (mMaterialsLoaded) uploadMaterials(); + bool meshesLoaded = parseMeshes(); setLoadState(DONE); - return (mMeshesLoaded); + return meshesLoaded; } void LLGLTFLoader::addModelToScene( @@ -1486,195 +1479,6 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) } } -bool LLGLTFLoader::parseMaterials() -{ - if (!mGltfLoaded) return false; - - // fill local texture data structures - mSamplers.clear(); - for (auto& in_sampler : mGLTFAsset.mSamplers) - { - gltf_sampler sampler; - sampler.magFilter = in_sampler.mMagFilter > 0 ? in_sampler.mMagFilter : GL_LINEAR; - sampler.minFilter = in_sampler.mMinFilter > 0 ? in_sampler.mMinFilter : GL_LINEAR; - sampler.wrapS = in_sampler.mWrapS; - sampler.wrapT = in_sampler.mWrapT; - sampler.name = in_sampler.mName; - mSamplers.push_back(sampler); - } - - mImages.clear(); - for (auto& in_image : mGLTFAsset.mImages) - { - gltf_image image; - image.numChannels = in_image.mComponent; - image.bytesPerChannel = in_image.mBits >> 3; // Convert bits to bytes - image.pixelType = in_image.mPixelType; - image.size = 0; // We'll calculate this below if we have valid dimensions - - // Get dimensions from the texture if available - if (in_image.mTexture && in_image.mTexture->getDiscardLevel() >= 0) - { - image.height = in_image.mTexture->getHeight(); - image.width = in_image.mTexture->getWidth(); - // Since we don't have direct access to the raw data, we'll use the dimensions to calculate size - if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) - { - image.size = static_cast(image.height * image.width * image.numChannels * image.bytesPerChannel); - } - } - else - { - // Fallback to provided dimensions - image.height = in_image.mHeight; - image.width = in_image.mWidth; - if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) - { - image.size = static_cast(image.height * image.width * image.numChannels * image.bytesPerChannel); - } - } - - // If we couldn't determine the size, skip this image - if (image.size == 0) - { - LL_WARNS("GLTF_IMPORT") << "Image size could not be determined" << LL_ENDL; - continue; - } - - // We don't have direct access to the image data, so data pointer remains nullptr - image.data = nullptr; - mImages.push_back(image); - } - - mTextures.clear(); - for (auto& in_tex : mGLTFAsset.mTextures) - { - gltf_texture tex; - tex.imageIdx = in_tex.mSource; - tex.samplerIdx = in_tex.mSampler; - tex.imageUuid.setNull(); - - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; - return false; - } - - mTextures.push_back(tex); - } - - // parse each material - mMaterials.clear(); - for (const auto& gltf_material : mGLTFAsset.mMaterials) - { - gltf_render_material mat; - mat.name = gltf_material.mName; - - // PBR Metallic Roughness properties - mat.hasPBR = true; - - // Base color factor - mat.baseColor = LLColor4( - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[0], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[1], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[2], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[3] - ); - - // Base color texture - mat.hasBaseTex = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0; - mat.baseColorTexIdx = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex; - mat.baseColorTexCoords = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mTexCoord; - - // Metalness and roughness - mat.metalness = gltf_material.mPbrMetallicRoughness.mMetallicFactor; - mat.roughness = gltf_material.mPbrMetallicRoughness.mRoughnessFactor; - - // Metallic-roughness texture - mat.hasMRTex = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex >= 0; - mat.metalRoughTexIdx = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex; - mat.metalRoughTexCoords = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTexCoord; - - // Normal texture - mat.normalScale = gltf_material.mNormalTexture.mScale; - mat.hasNormalTex = gltf_material.mNormalTexture.mIndex >= 0; - mat.normalTexIdx = gltf_material.mNormalTexture.mIndex; - mat.normalTexCoords = gltf_material.mNormalTexture.mTexCoord; - - // Occlusion texture - mat.occlusionScale = gltf_material.mOcclusionTexture.mStrength; - mat.hasOcclusionTex = gltf_material.mOcclusionTexture.mIndex >= 0; - mat.occlusionTexIdx = gltf_material.mOcclusionTexture.mIndex; - mat.occlusionTexCoords = gltf_material.mOcclusionTexture.mTexCoord; - - // Emissive texture and color - mat.emissiveColor = LLColor4( - gltf_material.mEmissiveFactor[0], - gltf_material.mEmissiveFactor[1], - gltf_material.mEmissiveFactor[2], - 1.0f - ); - mat.hasEmissiveTex = gltf_material.mEmissiveTexture.mIndex >= 0; - mat.emissiveTexIdx = gltf_material.mEmissiveTexture.mIndex; - mat.emissiveTexCoords = gltf_material.mEmissiveTexture.mTexCoord; - - // Convert AlphaMode enum to string - switch (gltf_material.mAlphaMode) - { - case LL::GLTF::Material::AlphaMode::OPAQUE: - mat.alphaMode = "OPAQUE"; - break; - case LL::GLTF::Material::AlphaMode::MASK: - mat.alphaMode = "MASK"; - break; - case LL::GLTF::Material::AlphaMode::BLEND: - mat.alphaMode = "BLEND"; - break; - default: - mat.alphaMode = "OPAQUE"; - break; - } - - mat.alphaMask = gltf_material.mAlphaCutoff; - - // Verify that all referenced textures are valid - if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) || - (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) || - (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) || - (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) || - (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size()))) - { - LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL; - return false; - } - - // Verify texture coordinate sets are valid (mesh can have up to 3 sets of UV) - if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || - (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || - (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || - (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || - (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) - { - LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; - return false; - } - - mMaterials.push_back(mat); - } - - return true; -} - -// TODO: convert raw vertex buffers to UUIDs -void LLGLTFLoader::uploadMeshes() -{ - //llassert(0); -} - -// convert raw image buffers to texture UUIDs & assemble into a render material -void LLGLTFLoader::uploadMaterials() -{ -} std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index e0b3664012..19a029d6d4 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -35,89 +35,6 @@ #include "lljointdata.h" #include "llmodelloader.h" -// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD - -class gltf_sampler -{ -public: - // Uses GL enums - S32 minFilter; // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR - S32 magFilter; // GL_NEAREST or GL_LINEAR - S32 wrapS; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT - S32 wrapT; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT - //S32 wrapR; // Found in some sample files, but not part of glTF 2.0 spec. Ignored. - std::string name; // optional, currently unused - // extensions and extras are sampler optional fields that we don't support - at least initially -}; - -class gltf_image -{ -public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL) - U8* data; // ptr to decoded image data - U32 size; // in bytes, regardless of channel width - U32 width; - U32 height; - U32 numChannels; // range 1..4 - U32 bytesPerChannel; // converted from gltf "bits", expects only 8, 16 or 32 as input - U32 pixelType; // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT -}; - -class gltf_texture -{ -public: - U32 imageIdx; - U32 samplerIdx; - LLUUID imageUuid = LLUUID::null; -}; - -class gltf_render_material -{ -public: - std::string name; - - // scalar values - LLColor4 baseColor; // linear encoding. Multiplied with vertex color, if present. - double metalness; - double roughness; - double normalScale; // scale applies only to X,Y components of normal - double occlusionScale; // strength multiplier for occlusion - LLColor4 emissiveColor; // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent) - std::string alphaMode; // "OPAQUE", "MASK" or "BLEND" - double alphaMask; // alpha cut-off - - // textures - U32 baseColorTexIdx; // always sRGB encoded - U32 metalRoughTexIdx; // always linear, roughness in G channel, metalness in B channel - U32 normalTexIdx; // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0) - U32 occlusionTexIdx; // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded - U32 emissiveTexIdx; // always stored as sRGB, in nits (candela / meter^2) - - // texture coordinates - U32 baseColorTexCoords; - U32 metalRoughTexCoords; - U32 normalTexCoords; - U32 occlusionTexCoords; - U32 emissiveTexCoords; - - // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry?? - - bool hasPBR; - bool hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex; - - // This field is populated after upload - LLUUID material_uuid = LLUUID::null; - -}; - -class gltf_mesh -{ -public: - std::string name; - - // TODO add mesh import DJH 2022-04 - -}; - class LLGLTFLoader : public LLModelLoader { public: @@ -185,18 +102,9 @@ protected: LL::GLTF::Asset mGLTFAsset; tinygltf::Model mGltfModel; bool mGltfLoaded; - bool mMeshesLoaded; - bool mMaterialsLoaded; bool mApplyXYRotation = false; U32 mGeneratedModelLimit; - std::vector mMeshes; - std::vector mMaterials; - - std::vector mTextures; - std::vector mImages; - std::vector mSamplers; - // GLTF isn't aware of viewer's skeleton and uses it's own, // so need to take viewer's joints and use them to // recalculate iverse bind matrices @@ -212,9 +120,6 @@ protected: private: bool parseMeshes(); - void uploadMeshes(); - bool parseMaterials(); - void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; void processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); -- cgit v1.2.3 From 9e87757a5b89cda3ee2fd08f7954b7c6796227ab Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 24 Jun 2025 00:39:25 +0300 Subject: #4257 Use filename as model initial name --- indra/newview/gltf/llgltfloader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7a1ebf0610..2631b7fefe 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -465,6 +465,9 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) { + // Set the requested label for the floater display and uploading + pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + // Create unique model name std::string base_name = mesh.mName; if (base_name.empty()) -- cgit v1.2.3 From 781d9fa4814b4c82473bb5bfe5ab87094434a3f5 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sat, 31 May 2025 15:42:08 +0300 Subject: Use latest release for changelog --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a9807e1dba..3a9ed6451f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -457,7 +457,6 @@ jobs: prerelease: true generate_release_notes: true target_commitish: ${{ github.sha }} - previous_tag: release append_body: true fail_on_unmatched_files: true files: | -- cgit v1.2.3 From c404b9375923e0e4260536329b3da7d566a2d259 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 24 Jun 2025 17:07:42 +0300 Subject: #4148 Fix collision bones --- indra/llappearance/llavatarappearance.cpp | 3 ++ indra/llappearance/lljointdata.h | 21 +++++++++++++ indra/llprimitive/llmodelloader.cpp | 52 +++++++++++++++++++++++++++++++ indra/llprimitive/llmodelloader.h | 1 + indra/newview/gltf/llgltfloader.cpp | 49 ++++++++++++++++++++++------- indra/newview/gltf/llgltfloader.h | 2 +- 6 files changed, 116 insertions(+), 12 deletions(-) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 13bea1e5ea..3c573d7227 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -1683,7 +1683,10 @@ void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy( { data.mName = bone_info->mName; data.mJointMatrix = bone_info->getJointMatrix(); + data.mScale = glm::vec3(bone_info->mScale[0], bone_info->mScale[1], bone_info->mScale[2]); + data.mRotation = bone_info->mRot; data.mRestMatrix = parent_mat * data.mJointMatrix; + data.mIsJoint = bone_info->mIsJoint; for (LLAvatarBoneInfo* child_info : bone_info->mChildren) { LLJointData& child_data = data.mChildren.emplace_back(); diff --git a/indra/llappearance/lljointdata.h b/indra/llappearance/lljointdata.h index 549f4af041..0b5eff2ae7 100644 --- a/indra/llappearance/lljointdata.h +++ b/indra/llappearance/lljointdata.h @@ -36,9 +36,30 @@ public: std::string mName; glm::mat4 mJointMatrix; glm::mat4 mRestMatrix; + glm::vec3 mScale; + LLVector3 mRotation; typedef std::vector bones_t; bones_t mChildren; + + bool mIsJoint; // if not, collision_volume + enum SupportCategory + { + SUPPORT_BASE, + SUPPORT_EXTENDED + }; + SupportCategory mSupport; + void setSupport(const std::string& support) + { + if (support == "extended") + { + mSupport = SUPPORT_EXTENDED; + } + else + { + mSupport = SUPPORT_BASE; + } + } }; #endif //LL_LLJOINTDATA_H diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 8f86b90b69..f97ac16a83 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -468,6 +468,58 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector inv_bind; + std::map alt_bind; + for (LLPointer& mdl : mModelList) + { + + file << "Model name: " << mdl->mLabel << "\n"; + const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; + file << "Shape Bind matrix: " << skin_info.mBindShapeMatrix << "\n"; + file << "Skin Weights count: " << (S32)mdl->mSkinWeights.size() << "\n"; + + // some objects might have individual bind matrices, + // but for now it isn't accounted for + size_t joint_count = skin_info.mJointNames.size(); + for (size_t i = 0; i< joint_count;i++) + { + const std::string& joint = skin_info.mJointNames[i]; + if (skin_info.mInvBindMatrix.size() > i) + { + inv_bind[joint] = skin_info.mInvBindMatrix[i]; + } + if (skin_info.mAlternateBindMatrix.size() > i) + { + alt_bind[joint] = skin_info.mAlternateBindMatrix[i]; + } + } + } + + file << "Inv Bind matrices.\n"; + for (auto& bind : inv_bind) + { + file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; + } + + file << "Alt Bind matrices.\n"; + for (auto& bind : alt_bind) + { + file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; + } +} //called in the main thread void LLModelLoader::loadTextures() diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 73ec0ed1f4..7c808dcae0 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -198,6 +198,7 @@ public: const LLSD logOut() const { return mWarningsArray; } void clearLog() { mWarningsArray.clear(); } + void dumpDebugData(); protected: diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2631b7fefe..1af53311c6 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1122,7 +1122,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) { - buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident); + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); } for (S32 i = 0; i < joint_count; i++) @@ -1299,7 +1299,7 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } -void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest) const +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const { glm::mat4 new_lefover(1.f); glm::mat4 rest(1.f); @@ -1309,26 +1309,42 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map S32 gltf_node_idx = found_node->second; JointNodeData& node = gltf_nodes[gltf_node_idx]; node.mIsOverrideValid = true; + node.mViewerRestMatrix = viewer_data.mRestMatrix; glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; if (mApplyXYRotation) { gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; } - node.mOverrideMatrix = glm::inverse(parent_rest) * gltf_joint_rest_pose; - glm::vec3 override; + glm::mat4 translated_joint; + // Example: + // Viewer has pelvis->spine1->spine2->torso. + // gltf example model has pelvis->torso + // By doing glm::inverse(transalted_rest_spine2) * gltf_rest_torso + // We get what torso would have looked like if gltf had a spine2 + if (viewer_data.mIsJoint) + { + translated_joint = glm::inverse(parent_rest) * gltf_joint_rest_pose; + } + else + { + translated_joint = glm::inverse(parent_support_rest) * gltf_joint_rest_pose; + } + + glm::vec3 translation_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); + glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + + node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); + + glm::mat4 override_joint = node.mOverrideMatrix; + override_joint = glm::scale(override_joint, viewer_data.mScale); - node.mOverrideMatrix = viewer_joint; - rest = parent_rest * node.mOverrideMatrix; + rest = parent_rest * override_joint; node.mOverrideRestMatrix = rest; } else @@ -1336,9 +1352,20 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map // No override for this joint rest = parent_rest * viewer_data.mJointMatrix; } + + glm::mat4 support_rest(1.f); + if (viewer_data.mSupport == LLJointData::SUPPORT_BASE) + { + support_rest = rest; + } + else + { + support_rest = parent_support_rest; + } + for (LLJointData& child_data : viewer_data.mChildren) { - buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest); + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, support_rest); } } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 19a029d6d4..f3d5dadbb9 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -129,7 +129,7 @@ private: S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const; S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint S32 findParentNode(S32 node) const; - void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest) const; + void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_rest) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const; glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const; -- cgit v1.2.3 From 4a40eaa87f97ed6b95b8919bf533ae3bd1245c98 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 24 Jun 2025 23:57:36 +0300 Subject: #4142 Add missed message --- indra/newview/skins/default/xui/en/floater_model_preview.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 80b6427738..ab5ba91d5c 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -66,6 +66,7 @@ No scenes defined in GLTF file Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES]) Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated) + Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Empty vertex array Unable to process mesh [MESH_NAME] due to 65,534 vertex limit. Vertex count: [VERTEX_COUNT] Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME] Model uses unsupported extension: [EXT], related material properties are ignored @@ -1720,7 +1721,6 @@ Analysed: height="408"/> Date: Wed, 25 Jun 2025 20:19:23 +0300 Subject: #4142 Joint grouping and stripping Viewer only supports 110 joints at a time, when model has more bones than that importer has to strip some, use groups for stripping. --- indra/llappearance/llavatarappearance.cpp | 11 ++ indra/llappearance/lljointdata.h | 1 + indra/newview/gltf/llgltfloader.cpp | 177 ++++++++++++++++++++++-------- indra/newview/gltf/llgltfloader.h | 17 ++- 4 files changed, 157 insertions(+), 49 deletions(-) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 3c573d7227..dab18c240d 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -78,6 +78,7 @@ private: std::string mName; std::string mSupport; std::string mAliases; + std::string mGroup; bool mIsJoint; LLVector3 mPos; LLVector3 mEnd; @@ -1606,6 +1607,15 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node) mSupport = "base"; } + // Skeleton has 133 bones, but shader only allows 110 (LL_MAX_JOINTS_PER_MESH_OBJECT) + // Groups can be used by importer to cut out unused groups of joints + static LLStdStringHandle group_string = LLXmlTree::addAttributeString("group"); + if (!node->getFastAttributeString(group_string, mGroup)) + { + LL_WARNS() << "Bone without group " << mName << LL_ENDL; + mGroup = "global"; + } + if (mIsJoint) { static LLStdStringHandle pivot_string = LLXmlTree::addAttributeString("pivot"); @@ -1687,6 +1697,7 @@ void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy( data.mRotation = bone_info->mRot; data.mRestMatrix = parent_mat * data.mJointMatrix; data.mIsJoint = bone_info->mIsJoint; + data.mGroup = bone_info->mGroup; for (LLAvatarBoneInfo* child_info : bone_info->mChildren) { LLJointData& child_data = data.mChildren.emplace_back(); diff --git a/indra/llappearance/lljointdata.h b/indra/llappearance/lljointdata.h index 0b5eff2ae7..2fc26198ee 100644 --- a/indra/llappearance/lljointdata.h +++ b/indra/llappearance/lljointdata.h @@ -34,6 +34,7 @@ class LLJointData { public: std::string mName; + std::string mGroup; glm::mat4 mJointMatrix; glm::mat4 mRestMatrix; glm::vec3 mScale; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1af53311c6..039c0b561e 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -258,13 +258,14 @@ bool LLGLTFLoader::parseMeshes() if (mGLTFAsset.mSkins.size() > 0) { checkForXYrotation(mGLTFAsset.mSkins[0]); + populateJointGroups(); } // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) { - populateJointFromSkin(i); + populateJointsFromSkin(i); } // Track how many times each mesh name has been used @@ -463,6 +464,24 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 } } +bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const +{ + const std::string& legal_name = mJointNames[gltf_skin_idx][gltf_joint_idx]; + if (legal_name.empty()) + { + llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 + return false; + } + skin_info.mJointNames.push_back(legal_name); + skin_info.mJointNums.push_back(-1); + + // In scope of same skin multiple meshes reuse same bind matrices + skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[gltf_skin_idx][gltf_joint_idx]); + skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[gltf_skin_idx][gltf_joint_idx]); + + return true; +} + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) { // Set the requested label for the floater display and uploading @@ -519,15 +538,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& size_t jointCnt = gltf_skin.mJoints.size(); gltf_joint_index_use.resize(jointCnt); - S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) { - // Process joint name and idnex - S32 joint = gltf_skin.mJoints[i]; - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - - std::string legal_name(jointNode.mName); - if (mJointMap.find(legal_name) == mJointMap.end()) + if (mJointNames[i].empty()) { // This might need to hold a substitute index gltf_joint_index_use[i] = -1; // mark as unsupported @@ -959,65 +972,106 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Call normalizeVolumeFacesAndWeights to compute proper extents pModel->normalizeVolumeFacesAndWeights(); - // Fill joint names, bind matrices and prepare to remap weight indices + // Fill joint names, bind matrices and remap weight indices if (skinIdx >= 0) { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; S32 valid_joints_count = mValidJointsCount[skinIdx]; + S32 replacement_index = 0; std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt); - S32 replacement_index = 0; - S32 stripped_valid_joints = 0; - for (size_t i = 0; i < jointCnt; ++i) + if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) { - // Process joint name and idnex - S32 joint = gltf_skin.mJoints[i]; - if (gltf_joint_index_use[i] < 0) + std::map goup_use_count; + // Assume that 'Torso' group is always in use since that's what everything else is attached to + goup_use_count["Torso"] = 1; + // Note that Collisions and Extra groups are all over the place, might want to include them from the start + // or add individual when parents are added + + // Check which groups are in use + for (size_t i = 0; i < jointCnt; ++i) { - // unsupported (-1) joint, drop it - continue; + std::string& joint_name = mJointNames[skinIdx][i]; + if (!joint_name.empty()) + { + if (gltf_joint_index_use[i] > 0) + { + const JointGroups &group = mJointGroups[joint_name]; + // Joint in use, increment it's groups + goup_use_count[group.mGroup]++; + goup_use_count[group.mParentGroup]++; + } + } } - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::string legal_name(jointNode.mName); - if (mJointMap.find(legal_name) != mJointMap.end()) + // 1. add joints that are in use directly + for (size_t i = 0; i < jointCnt; ++i) { - legal_name = mJointMap[legal_name]; - } - else - { - llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 - continue; + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] <= 0) + { + // unsupported (-1) joint, drop it + // unused (0) joint, drop it + continue; + } + + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } } - if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT - && gltf_joint_index_use[i] == 0 - && legal_name != "mPelvis") + // 2. add joints from groups that this model's joints belong to + // 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. + // Todo: sort and add by usecount + for (size_t i = 0; i < jointCnt; ++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; + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] != 0) + { + // this step needs only joints that have zero uses + continue; + } + if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + break; + } + const std::string& legal_name = mJointNames[skinIdx][i]; + std::string group_name = mJointGroups[legal_name].mGroup; + if (goup_use_count[group_name] > 0) + { + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } + } } + } + else + { + // Less than 110, just add every valid joint + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] < 0) + { + // unsupported (-1) joint, drop it + continue; + } - gltfindex_to_joitindex_map[i] = replacement_index++; - - skin_info.mJointNames.push_back(legal_name); - skin_info.mJointNums.push_back(-1); - - // In scope of same skin multiple meshes reuse same bind matrices - skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[skinIdx][i]); - - skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } + } } if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) @@ -1046,7 +1100,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& return true; } -void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) +void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) { const LL::GLTF::Skin& skin = mGLTFAsset.mSkins[skin_idx]; @@ -1066,6 +1120,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); + mJointNames.resize(skin_idx + 1); mValidJointsCount.resize(skin_idx + 1, 0); } @@ -1135,6 +1190,11 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { legal_name = mJointMap[legal_name]; legal_joint = true; + mJointNames[skin_idx].push_back(legal_name); + } + else + { + mJointNames[skin_idx].emplace_back(); } // Compute bind matrices @@ -1196,6 +1256,15 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } } +void LLGLTFLoader::populateJointGroups() +{ + std::string parent; + for (auto& viewer_data : mViewerJointData) + { + buildJointGroup(viewer_data, parent); + } +} + S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const { @@ -1299,6 +1368,18 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } +void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group) +{ + JointGroups& jount_group_data = mJointGroups[viewer_data.mName]; + jount_group_data.mGroup = viewer_data.mGroup; + jount_group_data.mParentGroup = parent_group; + + for (LLJointData& child_data : viewer_data.mChildren) + { + buildJointGroup(child_data, viewer_data.mGroup); + } +} + void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const { glm::mat4 new_lefover(1.f); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index f3d5dadbb9..048f7df7f5 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -112,8 +112,20 @@ protected: // vector of vectors because of a posibility of having more than one skin typedef std::vector bind_matrices_t; + typedef std::vector > joint_names_t; bind_matrices_t mInverseBindMatrices; bind_matrices_t mAlternateBindMatrices; + joint_names_t mJointNames; // empty string when no legal name for a given idx + + // what group a joint belongs to. + // For purpose of stripping unused groups when joints are over limit. + struct JointGroups + { + std::string mGroup; + std::string mParentGroup; + }; + typedef std::map joint_to_group_map_t; + joint_to_group_map_t mJointGroups; // per skin joint count, needs to be tracked for the sake of limits check. std::vector mValidJointsCount; @@ -122,13 +134,16 @@ private: bool parseMeshes(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; void processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params); + bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const; bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); - void populateJointFromSkin(S32 skin_idx); + void populateJointsFromSkin(S32 skin_idx); + void populateJointGroups(); 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 buildJointGroup(LLJointData& viewer_data, const std::string& parent_group); void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_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; -- cgit v1.2.3 From 895db142ee9224e1b8e4d2fc6348dc1801bea0dc Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 26 Jun 2025 20:43:06 +0300 Subject: #4291 Uploading without a .bin silently fails --- indra/newview/gltf/llgltfloader.cpp | 14 ++++++++++++++ .../newview/skins/default/xui/en/floater_model_preview.xml | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 039c0b561e..7ae255e054 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -129,6 +129,20 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) if (!mGltfLoaded) { notifyUnsupportedExtension(true); + + for (const auto& buffer : mGLTFAsset.mBuffers) + { + if (buffer.mByteLength > 0 && buffer.mData.empty()) + { + bool bin_file = buffer.mUri.ends_with(".bin"); + LLSD args; + args["Message"] = bin_file ? "ParsingErrorMissingBufferBin" : "ParsingErrorMissingBuffer"; + args["BUFFER_NAME"] = buffer.mName; + args["BUFFER_URI"] = buffer.mUri; + mWarningsArray.append(args); + } + } + setLoadState(ERROR_PARSING); return false; } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index ab5ba91d5c..f8543ba1aa 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -14,7 +14,7 @@ legacy_header_height="25"> - Error: Dae parsing issue - see log for details. + Error: Model parsing issue - see log for details. Warning: bind shape matrix is not in standard X-forward orientation. Error: Material of model is not a subset of reference model. Loading... @@ -75,6 +75,8 @@ Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE] Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis. Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail + Buffer is either missing or empty [BUFFER_NAME]. + Buffer is either missing or empty. Check presence of [BUFFER_URI] file. Date: Fri, 27 Jun 2025 19:23:48 -0400 Subject: Restore glTF mesh modifications. --- indra/llappearance/llavatarappearance.h | 4 ++-- indra/llcharacter/llbvhloader.cpp | 6 +++--- indra/llcharacter/llbvhloader.h | 2 +- indra/llprimitive/lldaeloader.cpp | 2 +- indra/llprimitive/lldaeloader.h | 26 +++++++++++++------------- indra/llprimitive/llmodelloader.h | 2 +- indra/newview/llfloaterbvhpreview.cpp | 4 ++-- indra/newview/llfloaterbvhpreview.h | 2 +- indra/newview/llfloatermodelpreview.cpp | 4 ++-- indra/newview/llmodelpreview.cpp | 2 +- indra/newview/llvoavatar.cpp | 8 ++++---- indra/newview/llvoavatar.h | 2 +- indra/newview/llvoavatarself.cpp | 6 +++--- indra/newview/llvoavatarself.h | 2 +- 14 files changed, 36 insertions(+), 36 deletions(-) diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 84cb42056a..2748da9a1d 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -140,7 +140,7 @@ public: LLVector3 mHeadOffset{}; // current head position LLAvatarJoint* mRoot{ nullptr }; - typedef std::map> joint_map_t; + typedef std::map joint_map_t; joint_map_t mJointMap; typedef std::map joint_state_map_t; @@ -153,7 +153,7 @@ public: public: typedef std::vector avatar_joint_list_t; const avatar_joint_list_t& getSkeleton() { return mSkeleton; } - typedef std::map> joint_alias_map_t; + typedef std::map joint_alias_map_t; const joint_alias_map_t& getJointAliases(); typedef std::map joint_parent_map_t; // matrix plus parent typedef std::map joint_rest_map_t; diff --git a/indra/llcharacter/llbvhloader.cpp b/indra/llcharacter/llbvhloader.cpp index 581e9f62d5..9dace08e6f 100644 --- a/indra/llcharacter/llbvhloader.cpp +++ b/indra/llcharacter/llbvhloader.cpp @@ -131,7 +131,7 @@ LLQuaternion::Order bvhStringToOrder( char *str ) // LLBVHLoader() //----------------------------------------------------------------------------- -LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map>& joint_alias_map ) +LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map& joint_alias_map ) { reset(); errorLine = 0; @@ -156,9 +156,9 @@ LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &error } // Recognize all names we've been told are legal. - for (const auto& [alias, joint] : joint_alias_map) + for (std::map::value_type& alias_pair : joint_alias_map) { - makeTranslation(alias, joint); + makeTranslation( alias_pair.first , alias_pair.second ); } char error_text[128]; /* Flawfinder: ignore */ diff --git a/indra/llcharacter/llbvhloader.h b/indra/llcharacter/llbvhloader.h index ae2e347ba1..de31f76dd3 100644 --- a/indra/llcharacter/llbvhloader.h +++ b/indra/llcharacter/llbvhloader.h @@ -227,7 +227,7 @@ class LLBVHLoader friend class LLKeyframeMotion; public: // Constructor - LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map>& joint_alias_map ); + LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map& joint_alias_map ); ~LLBVHLoader(); /* diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index eadb052b38..0759447902 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -880,7 +880,7 @@ LLDAELoader::LLDAELoader( void* opaque_userdata, JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, - std::map>& jointAliasMap, + std::map& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, bool preprocess) diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index dc20feca52..4531e03474 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -47,19 +47,19 @@ public: dae_model_map mModelsMap; LLDAELoader( - std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void* opaque_userdata, - JointTransformMap& jointTransformMap, - JointNameSet& jointsFromNodes, - std::map>& jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - bool preprocess); + std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void* opaque_userdata, + JointTransformMap& jointTransformMap, + JointNameSet& jointsFromNodes, + std::map& jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + bool preprocess); virtual ~LLDAELoader() ; virtual bool OpenFile(const std::string& filename); diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index caffa34676..7c808dcae0 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -36,7 +36,7 @@ class LLJoint; typedef std::map JointTransformMap; typedef std::map::iterator JointTransformMapIt; -typedef std::map> JointMap; +typedef std::map JointMap; typedef std::deque JointNameSet; const S32 SLM_SUPPORTED_VERSION = 3; diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index 5ee93be061..b94c31ec04 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -179,7 +179,7 @@ void LLFloaterBvhPreview::setAnimCallbacks() getChild("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1)); } -std::map> LLFloaterBvhPreview::getJointAliases() +std::map LLFloaterBvhPreview::getJointAliases() { LLPointer av = (LLVOAvatar*)mAnimPreview->getDummyAvatar(); return av->getJointAliases(); @@ -252,7 +252,7 @@ bool LLFloaterBvhPreview::postBuild() ELoadStatus load_status = E_ST_OK; S32 line_number = 0; - auto joint_alias_map = getJointAliases(); + std::map joint_alias_map = getJointAliases(); loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map); std::string status = getString(STATUS[load_status]); diff --git a/indra/newview/llfloaterbvhpreview.h b/indra/newview/llfloaterbvhpreview.h index bb69ab65ef..ae64521492 100644 --- a/indra/newview/llfloaterbvhpreview.h +++ b/indra/newview/llfloaterbvhpreview.h @@ -108,7 +108,7 @@ public: S32 status, LLExtStat ext_status); private: void setAnimCallbacks() ; - std::map> getJointAliases(); + std::map getJointAliases(); protected: diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 5cb5f8b961..84b9cb18f8 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -756,7 +756,7 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) mModelPreview->onLODMeshOptimizerParamCommit(lod, enforce_tri_limit, mode); break; default: - LL_ERRS() << "Only supposed to be called to generate models, val: " << mode << LL_ENDL; + LL_ERRS() << "Only supposed to be called to generate models" << LL_ENDL; break; } @@ -1487,7 +1487,7 @@ void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides) { // Populate table - std::map> joint_alias_map; + std::map joint_alias_map; mModelPreview->getJointAliases(joint_alias_map); S32 conflicts = 0; diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 6843f7375d..fc0a3ec58f 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -781,7 +781,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mLODFile[lod] = filename; - std::map> joint_alias_map; + std::map joint_alias_map; getJointAliases(joint_alias_map); LLHandle preview_handle = getHandle(); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index dcba891f9f..d9a3ec3004 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -6306,13 +6306,13 @@ const LLUUID& LLVOAvatar::getID() const // getJoint() //----------------------------------------------------------------------------- // RN: avatar joints are multi-rooted to include screen-based attachments -LLJoint* LLVOAvatar::getJoint(std::string_view name) +LLJoint *LLVOAvatar::getJoint( const std::string &name ) { joint_map_t::iterator iter = mJointMap.find(name); - LLJoint* jointp = nullptr; + LLJoint* jointp = NULL; - if (iter == mJointMap.end() || iter->second == nullptr) + if (iter == mJointMap.end() || iter->second == NULL) { //search for joint and cache found joint in lookup table if (mJointAliasMap.empty()) { @@ -6329,7 +6329,7 @@ LLJoint* LLVOAvatar::getJoint(std::string_view name) canonical_name = name; } jointp = mRoot->findJoint(canonical_name); - mJointMap[std::string(name)] = jointp; + mJointMap[name] = jointp; } else { //return cached pointer diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 9eb8d3f880..ab27c5752d 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -202,7 +202,7 @@ public: void startDefaultMotions(); void dumpAnimationState(); - virtual LLJoint* getJoint(std::string_view name); + virtual LLJoint* getJoint(const std::string &name); LLJoint* getJoint(S32 num); void initAllJoints(); diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index ebba9ba291..90ff4067f2 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -697,17 +697,17 @@ void LLVOAvatarSelf::idleUpdate(LLAgent &agent, const F64 &time) } // virtual -LLJoint* LLVOAvatarSelf::getJoint(std::string_view name) +LLJoint *LLVOAvatarSelf::getJoint(const std::string &name) { std::lock_guard lock(mJointMapMutex); - LLJoint* jointp = nullptr; + LLJoint *jointp = NULL; jointp = LLVOAvatar::getJoint(name); if (!jointp && mScreenp) { jointp = mScreenp->findJoint(name); if (jointp) { - mJointMap[std::string(name)] = jointp; + mJointMap[name] = jointp; } } if (jointp && jointp != mScreenp && jointp != mRoot) diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index f7cd974ab0..f9bea41b1d 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -90,7 +90,7 @@ public: /*virtual*/ bool hasMotionFromSource(const LLUUID& source_id); /*virtual*/ void stopMotionFromSource(const LLUUID& source_id); /*virtual*/ void requestStopMotion(LLMotion* motion); - /*virtual*/ LLJoint* getJoint(std::string_view name); + /*virtual*/ LLJoint* getJoint(const std::string &name); /*virtual*/ void renderJoints(); -- cgit v1.2.3 From a50a0e1277386de3456f478127f774fa01cdb578 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 27 Jun 2025 19:48:30 -0400 Subject: Fix LLCharacter base class constness. --- indra/llcharacter/llcharacter.cpp | 7 ++++--- indra/llcharacter/llcharacter.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp index 8efcd9dd29..ecbcdb3bf5 100644 --- a/indra/llcharacter/llcharacter.cpp +++ b/indra/llcharacter/llcharacter.cpp @@ -77,11 +77,12 @@ LLCharacter::~LLCharacter() //----------------------------------------------------------------------------- // getJoint() //----------------------------------------------------------------------------- -LLJoint* LLCharacter::getJoint(std::string_view name) +LLJoint *LLCharacter::getJoint( const std::string &name ) { - LLJoint* joint = nullptr; + LLJoint* joint = NULL; - if (LLJoint* root = getRootJoint()) + LLJoint *root = getRootJoint(); + if (root) { joint = root->findJoint(name); } diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 0046c5da7e..6143ec8cd1 100644 --- a/indra/llcharacter/llcharacter.h +++ b/indra/llcharacter/llcharacter.h @@ -76,7 +76,7 @@ public: // get the specified joint // default implementation does recursive search, // subclasses may optimize/cache results. - virtual LLJoint* getJoint(std::string_view name); + virtual LLJoint *getJoint( const std::string &name ); // get the position of the character virtual LLVector3 getCharacterPosition() = 0; -- cgit v1.2.3