From 6b251fb7a4308b7e2dbf995238866ff46ad6c780 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 17 Jun 2024 13:56:46 +0300 Subject: viewer#1781 More detailed model upload errors for textures Show filename of a specific texture --- indra/llprimitive/llmodel.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'indra/llprimitive') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 236cef9c3f..9c49f64452 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -66,7 +66,12 @@ LLModel::~LLModel() { if (mDecompID >= 0) { - LLConvexDecomposition::getInstance()->deleteDecomposition(mDecompID); + // can be null on shutdown + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); + if (decomp) + { + decomp->deleteDecomposition(mDecompID); + } } mPhysics.mMesh.clear(); } -- cgit v1.3 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 (limited to 'indra/llprimitive') 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.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(-) (limited to 'indra/llprimitive') 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.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(+) (limited to 'indra/llprimitive') 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.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(-) (limited to 'indra/llprimitive') 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.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(-) (limited to 'indra/llprimitive') 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.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(-) (limited to 'indra/llprimitive') 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.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(-) (limited to 'indra/llprimitive') 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.3 From 5bc92322e974c6c1a0fcc7db0ebb6479ed486a0b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev 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(-) (limited to 'indra/llprimitive') 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.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(-) (limited to 'indra/llprimitive') 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: 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(-) (limited to 'indra/llprimitive') 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.3 From 75db5e8b6b911e312ebdb9bc10a55d048a70e0be Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 28 Jun 2025 19:31:02 +0200 Subject: Revert "Fix LLCharacter base class constness." and restore improvements from 10a324a1034c177b95545ac7ffaa6aa6abed65ff instead --- indra/llappearance/llavatarappearance.h | 4 ++-- indra/llcharacter/llbvhloader.cpp | 6 +++--- indra/llcharacter/llbvhloader.h | 2 +- indra/llcharacter/llcharacter.cpp | 7 +++---- indra/llcharacter/llcharacter.h | 2 +- indra/llprimitive/lldaeloader.cpp | 2 +- indra/llprimitive/lldaeloader.h | 26 +++++++++++++------------- indra/llprimitive/llmodelloader.h | 2 +- indra/newview/gltf/llgltfloader.cpp | 28 ++++++++++++++-------------- indra/newview/gltf/llgltfloader.h | 26 +++++++++++++------------- indra/newview/llfloaterbvhpreview.cpp | 4 ++-- indra/newview/llfloaterbvhpreview.h | 2 +- indra/newview/llfloatermodelpreview.cpp | 2 +- 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 +- 18 files changed, 66 insertions(+), 67 deletions(-) (limited to 'indra/llprimitive') diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 2748da9a1d..84cb42056a 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 9dace08e6f..581e9f62d5 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 (std::map::value_type& alias_pair : joint_alias_map) + for (const auto& [alias, joint] : joint_alias_map) { - makeTranslation( alias_pair.first , alias_pair.second ); + makeTranslation(alias, joint); } char error_text[128]; /* Flawfinder: ignore */ diff --git a/indra/llcharacter/llbvhloader.h b/indra/llcharacter/llbvhloader.h index de31f76dd3..ae2e347ba1 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/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp index ecbcdb3bf5..8efcd9dd29 100644 --- a/indra/llcharacter/llcharacter.cpp +++ b/indra/llcharacter/llcharacter.cpp @@ -77,12 +77,11 @@ LLCharacter::~LLCharacter() //----------------------------------------------------------------------------- // getJoint() //----------------------------------------------------------------------------- -LLJoint *LLCharacter::getJoint( const std::string &name ) +LLJoint* LLCharacter::getJoint(std::string_view name) { - LLJoint* joint = NULL; + LLJoint* joint = nullptr; - LLJoint *root = getRootJoint(); - if (root) + if (LLJoint* root = getRootJoint()) { joint = root->findJoint(name); } diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 6143ec8cd1..0046c5da7e 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( const std::string &name ); + virtual LLJoint* getJoint(std::string_view name); // get the position of the character virtual LLVector3 getCharacterPosition() = 0; diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index 0759447902..eadb052b38 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 4531e03474..dc20feca52 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 7c808dcae0..caffa34676 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/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7ae255e054..bd3c1a3833 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -86,20 +86,20 @@ static const glm::mat4 coord_system_rotationxy( 0.f, 0.f, 0.f, 1.f ); -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, - std::vector viewer_skeleton) //, - //bool preprocess) +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, + std::vector viewer_skeleton) //, + //bool preprocess) : LLModelLoader( filename, lod, load_cb, diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 048f7df7f5..b53258ab8b 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -72,19 +72,19 @@ class LLGLTFLoader : public LLModelLoader typedef std::map joints_name_to_node_map_t; 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, - std::vector viewer_skeleton); //, - //bool preprocess ); + 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, + std::vector viewer_skeleton); //, + //bool preprocess ); virtual ~LLGLTFLoader(); virtual bool OpenFile(const std::string &filename); diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index b94c31ec04..5ee93be061 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; - std::map joint_alias_map = getJointAliases(); + auto 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 ae64521492..bb69ab65ef 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 84b9cb18f8..bd33ab2dbe 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -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 fc0a3ec58f..6843f7375d 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 d9a3ec3004..dcba891f9f 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( const std::string &name ) +LLJoint* LLVOAvatar::getJoint(std::string_view name) { joint_map_t::iterator iter = mJointMap.find(name); - LLJoint* jointp = NULL; + LLJoint* jointp = nullptr; - if (iter == mJointMap.end() || iter->second == NULL) + if (iter == mJointMap.end() || iter->second == nullptr) { //search for joint and cache found joint in lookup table if (mJointAliasMap.empty()) { @@ -6329,7 +6329,7 @@ LLJoint *LLVOAvatar::getJoint( const std::string &name ) canonical_name = name; } jointp = mRoot->findJoint(canonical_name); - mJointMap[name] = jointp; + mJointMap[std::string(name)] = jointp; } else { //return cached pointer diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index ab27c5752d..9eb8d3f880 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -202,7 +202,7 @@ public: void startDefaultMotions(); void dumpAnimationState(); - virtual LLJoint* getJoint(const std::string &name); + virtual LLJoint* getJoint(std::string_view name); LLJoint* getJoint(S32 num); void initAllJoints(); diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 90ff4067f2..ebba9ba291 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(const std::string &name) +LLJoint* LLVOAvatarSelf::getJoint(std::string_view name) { std::lock_guard lock(mJointMapMutex); - LLJoint *jointp = NULL; + LLJoint* jointp = nullptr; jointp = LLVOAvatar::getJoint(name); if (!jointp && mScreenp) { jointp = mScreenp->findJoint(name); if (jointp) { - mJointMap[name] = jointp; + mJointMap[std::string(name)] = jointp; } } if (jointp && jointp != mScreenp && jointp != mRoot) diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index f9bea41b1d..f7cd974ab0 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(const std::string &name); + /*virtual*/ LLJoint* getJoint(std::string_view name); /*virtual*/ void renderJoints(); -- cgit v1.3 From 76dd9385f9354fd661d1c7cc1e7c3d9c39355675 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Jul 2025 23:14:24 +0300 Subject: #4315 Crash in GLTF uploader Properly handle importer's crashes in general --- indra/llprimitive/llmodelloader.cpp | 37 +++++++++++++++++++++- indra/newview/gltf/llgltfloader.cpp | 27 +++++++++++++++- indra/newview/llappviewer.cpp | 21 ++++++++++++ indra/newview/llappviewer.h | 1 + indra/newview/llviewermenu.cpp | 16 ++++++++++ .../skins/default/xui/en/floater_model_preview.xml | 2 ++ indra/newview/skins/default/xui/en/menu_viewer.xml | 10 ++++-- 7 files changed, 110 insertions(+), 4 deletions(-) (limited to 'indra/llprimitive') diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index f97ac16a83..a5b5d595a2 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -149,7 +149,42 @@ LLModelLoader::~LLModelLoader() void LLModelLoader::run() { mWarningsArray.clear(); - doLoadModel(); + try + { + doLoadModel(); + } + // Model loader isn't mission critical, so we just log all exceptions + catch (const LLException& e) + { + LL_WARNS("THREAD") << "LLException in model loader: " << e.what() << "" << LL_ENDL; + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL; + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("LLModelLoader"); + LLSD args; + args["Message"] = "UnknownException"; + args["FILENAME"] = mFilename; + args["EXCEPTION"] = "Unknown exception"; + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + } + // 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 71bfc021d3..bb6a0a590c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -124,7 +124,32 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); - mGltfLoaded = mGLTFAsset.load(filename, false); + try + { + mGltfLoaded = mGLTFAsset.load(filename, false); + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorException"; + args["FILENAME"] = filename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + return false; + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("LLGLTFLoader"); + LLSD args; + args["Message"] = "ParsingErrorException"; + args["FILENAME"] = filename; + args["EXCEPTION"] = "Unknown exception"; + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + return false; + } if (!mGltfLoaded) { diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index aea4492223..9872233e98 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5679,6 +5679,27 @@ void LLAppViewer::forceErrorThreadCrash() thread->start(); } +void LLAppViewer::forceExceptionThreadCrash() +{ + class LLCrashTestThread : public LLThread + { + public: + + LLCrashTestThread() : LLThread("Crash logging test thread") + { + } + + void run() + { + throw std::exception(); + } + }; + + LL_WARNS() << "This is a deliberate exception in a thread" << LL_ENDL; + LLCrashTestThread* thread = new LLCrashTestThread(); + thread->start(); +} + void LLAppViewer::initMainloopTimeout(std::string_view state, F32 secs) { if (!mMainloopTimeout) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index b4b8e5bac3..bafe952659 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -175,6 +175,7 @@ public: virtual void forceErrorCoroprocedureCrash(); virtual void forceErrorWorkQueueCrash(); virtual void forceErrorThreadCrash(); + virtual void forceExceptionThreadCrash(); // The list is found in app_settings/settings_files.xml // but since they are used explicitly in code, diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 1c9a892a4f..44157d2d2d 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -289,6 +289,7 @@ void force_error_coroutine_crash(); void force_error_coroprocedure_crash(); void force_error_work_queue_crash(); void force_error_thread_crash(); +void force_exception_thread_crash(); void handle_force_delete(); void print_object_info(); @@ -2663,6 +2664,15 @@ class LLAdvancedForceErrorThreadCrash : public view_listener_t } }; +class LLAdvancedForceExceptionThreadCrash : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_exception_thread_crash(); + return true; + } +}; + class LLAdvancedForceErrorDisconnectViewer : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -8696,6 +8706,11 @@ void force_error_thread_crash() LLAppViewer::instance()->forceErrorThreadCrash(); } +void force_exception_thread_crash() +{ + LLAppViewer::instance()->forceExceptionThreadCrash(); +} + class LLToolsUseSelectionForGrid : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -9898,6 +9913,7 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedForceErrorCoroprocedureCrash(), "Advanced.ForceErrorCoroprocedureCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorWorkQueueCrash(), "Advanced.ForceErrorWorkQueueCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorThreadCrash(), "Advanced.ForceErrorThreadCrash"); + view_listener_t::addMenu(new LLAdvancedForceExceptionThreadCrash(), "Advanced.ForceExceptionThreadCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorDisconnectViewer(), "Advanced.ForceErrorDisconnectViewer"); // Advanced (toplevel) 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 af6e4e8613..618d0a7fda 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. + Importer crashed while processing [FILENAME], if you encounter this and file is valid, please report the issue to Second Life Support. Exception: [EXCEPTION]. No scenes defined in GLTF file @@ -80,6 +81,7 @@ Model [MODEL_NAME] contains [SUBMODEL_COUNT] generated mesh parts, parts were trimmed to [SUBMODEL_LIMIT] Buffer is either missing or empty [BUFFER_NAME]. Buffer is either missing or empty. Check presence of [BUFFER_URI] file. + Parser failed to process [FILENAME], file might be corrupt, incomplete or protected from reading. Exception: [EXCEPTION]. + label="Force an LLError Crash in a Thread" + name="Force an LLError Crash in a Thread"> + + + -- cgit v1.3 From 5a0bbdc510d3aef452b30aa932588aa7dc630d22 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 2 Jul 2025 23:18:05 +0300 Subject: #4242 Debug dump improvement for better comparison with collada output --- indra/llprimitive/lldaeloader.cpp | 6 ++- indra/llprimitive/lldaeloader.h | 2 +- indra/llprimitive/llmodel.cpp | 12 +++-- indra/llprimitive/llmodel.h | 10 +++- indra/llprimitive/llmodelloader.cpp | 57 ++++++++++++++++++++-- indra/llprimitive/llmodelloader.h | 6 ++- indra/newview/app_settings/settings.xml | 13 ++++- indra/newview/gltf/llgltfloader.cpp | 18 ++++--- indra/newview/gltf/llgltfloader.h | 2 +- indra/newview/llmeshrepository.cpp | 4 +- indra/newview/llmodelpreview.cpp | 6 ++- .../skins/default/xui/en/floater_model_preview.xml | 2 +- 12 files changed, 110 insertions(+), 28 deletions(-) (limited to 'indra/llprimitive') diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index eadb052b38..aeeca9bcfb 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -883,6 +883,7 @@ LLDAELoader::LLDAELoader( std::map>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, bool preprocess) : LLModelLoader( filename, @@ -895,8 +896,9 @@ LLDAELoader::LLDAELoader( jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh), - mGeneratedModelLimit(modelLimit), + maxJointsPerMesh, + modelLimit, + debugMode), mPreprocessDAE(preprocess) { } diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index dc20feca52..400277d902 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -59,6 +59,7 @@ public: std::map>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, bool preprocess); virtual ~LLDAELoader() ; @@ -103,7 +104,6 @@ protected: static std::string preprocessDAE(std::string filename); private: - U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels bool mPreprocessDAE; }; diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 3d31cfbb7f..a33f25307e 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -818,7 +818,7 @@ LLSD LLModel::writeModel( bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - bool nowrite, + EWriteModelMode write_mode, bool as_slm, int submodel_id) { @@ -1097,10 +1097,10 @@ LLSD LLModel::writeModel( } } - return writeModelToStream(ostr, mdl, nowrite, as_slm); + return writeModelToStream(ostr, mdl, write_mode, as_slm); } -LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bool as_slm) +LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, EWriteModelMode write_mode, bool as_slm) { std::string::size_type cur_offset = 0; @@ -1162,7 +1162,11 @@ LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bo } } - if (!nowrite) + if (write_mode == WRITE_HUMAN) + { + ostr << mdl; + } + else if (write_mode == WRITE_BINARY) { LLSDSerialize::toBinary(header, ostr); diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 5c6d0a55d2..6501b3dc50 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -160,6 +160,12 @@ public: bool loadSkinInfo(LLSD& header, std::istream& is); bool loadDecomposition(LLSD& header, std::istream& is); + enum EWriteModelMode + { + WRITE_NO = 0, + WRITE_BINARY, + WRITE_HUMAN, + }; static LLSD writeModel( std::ostream& ostr, LLModel* physics, @@ -171,14 +177,14 @@ public: bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - bool nowrite = false, + EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false, int submodel_id = 0); static LLSD writeModelToStream( std::ostream& ostr, LLSD& mdl, - bool nowrite = false, bool as_slm = false); + EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false); void ClearFacesAndMaterials() { mVolumeFaces.clear(); mMaterialList.clear(); } diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index a5b5d595a2..61e479c9fd 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -113,7 +113,9 @@ LLModelLoader::LLModelLoader( JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, - U32 maxJointsPerMesh) + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode) : mJointList( jointTransformMap ) , mJointsFromNode( jointsFromNodes ) , LLThread("Model Loader") @@ -133,6 +135,8 @@ LLModelLoader::LLModelLoader( , mNoOptimize(false) , mCacheOnlyHitIfRigged(false) , mMaxJointsPerMesh(maxJointsPerMesh) +, mGeneratedModelLimit(modelLimit) +, mDebugMode(debugMode) , mJointMap(legalJointNamesMap) { assert_main_thread(); @@ -238,7 +242,9 @@ bool LLModelLoader::doLoadModel() } } - return OpenFile(mFilename); + bool res = OpenFile(mFilename); + dumpDebugData(); // conditional on mDebugMode + return res; } void LLModelLoader::setLoadState(U32 state) @@ -505,6 +511,11 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector& mdl : mModelList) + { + const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; + size_t joint_count = skin_info.mJointNames.size(); + size_t alt_count = skin_info.mAlternateBindMatrix.size(); + + LLModel::writeModel( + file, + nullptr, + mdl, + nullptr, + nullptr, + nullptr, + mdl->mPhysics, + joint_count > 0, + alt_count > 0, + false, + LLModel::WRITE_HUMAN, + false, + mdl->mSubmodelID); + + file << "\n"; + model_count++; + if (model_count == 5) + { + file << "Too many models, stopping at 5.\n"; + break; + } + } } //called in the main thread diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index caffa34676..06a17f006e 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -129,6 +129,7 @@ public: U32 mMaxJointsPerMesh; + U32 mDebugMode; // see dumDebugData() for details LLModelLoader( std::string filename, @@ -141,7 +142,9 @@ public: JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, - U32 maxJointsPerMesh); + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode); virtual ~LLModelLoader(); virtual void setNoNormalize() { mNoNormalize = true; } @@ -210,6 +213,7 @@ protected: bool mRigValidJointUpload; U32 mLegacyRigFlags; + U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels bool mNoNormalize; bool mNoOptimize; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 447042ccd5..bfb619ad22 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -2,7 +2,7 @@ - ImporterDebug + ImporterDebugVerboseLogging Comment Enable debug output to more precisely identify sources of import errors. Warning: the output can slow down import on many machines. @@ -35,6 +35,17 @@ Value 768 + ImporterDebugMode + + Comment + At 0 does nothing, at 1 dumps skinning data near orifinal file, at 2 dumps skining data and first 5 models as llsd + Persist + 1 + Type + U32 + Value + 0 + ImporterPreprocessDAE Comment diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index be3b9f0c4d..48343aa169 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -98,6 +98,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map> & jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, std::vector viewer_skeleton) //, //bool preprocess) : LLModelLoader( filename, @@ -110,8 +111,9 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ) - , mGeneratedModelLimit(modelLimit) + maxJointsPerMesh, + modelLimit, + debugMode) , mViewerJointData(viewer_skeleton) { } @@ -1037,7 +1039,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt, -1); - if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (valid_joints_count > (S32)mMaxJointsPerMesh) { std::map goup_use_count; // Assume that 'Torso' group is always in use since that's what everything else is attached to @@ -1092,7 +1094,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // this step needs only joints that have zero uses continue; } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { break; } @@ -1127,16 +1129,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { + // mMaxJointsPerMesh ususlly is equal to LL_MAX_JOINTS_PER_MESH_OBJECT + // and is 110. 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; + << " Limit:" << (S32)mMaxJointsPerMesh << LL_ENDL; LLSD args; args["Message"] = "ModelTooManyJoints"; args["MODEL_NAME"] = pModel->mLabel; args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); - args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + args["MAX"] = (S32)mMaxJointsPerMesh; mWarningsArray.append(args); } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 10da5c8651..e250a76982 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -83,6 +83,7 @@ class LLGLTFLoader : public LLModelLoader std::map> & jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, std::vector viewer_skeleton); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -103,7 +104,6 @@ protected: tinygltf::Model mGltfModel; bool mGltfLoaded; bool mApplyXYRotation = false; - U32 mGeneratedModelLimit; // GLTF isn't aware of viewer's skeleton and uses it's own, // so need to take viewer's joints and use them to diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index e7e95034b2..9382ebb00b 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2740,7 +2740,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - false, + LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID); @@ -2898,7 +2898,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - false, + LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 6843f7375d..bbded6f189 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -167,7 +167,7 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) , mLastJointUpdate(false) , mFirstSkinUpdate(true) , mHasDegenerate(false) - , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebug", false)) + , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebugVerboseLogging", false)) { mNeedsUpdate = true; mCameraDistance = 0.f; @@ -692,7 +692,7 @@ void LLModelPreview::saveUploadData(const std::string& filename, save_skinweights, save_joint_positions, lock_scale_if_joint_position, - false, true, instance.mModel->mSubmodelID); + LLModel::WRITE_BINARY, true, instance.mModel->mSubmodelID); data["mesh"][instance.mModel->mLocalID] = str.str(); } @@ -807,6 +807,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getU32("ImporterDebugMode"), gSavedSettings.getBOOL("ImporterPreprocessDAE")); } else @@ -827,6 +828,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getU32("ImporterDebugMode"), viewer_skeleton); } 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 4141191038..a86b9c7da2 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1426,7 +1426,7 @@ word_wrap="true"> Date: Thu, 3 Jul 2025 23:07:08 +0300 Subject: #4242 Debug dump improvement #2 --- indra/llprimitive/llmodelloader.cpp | 109 +++++++++++++++++++++++--------- indra/newview/app_settings/settings.xml | 2 +- 2 files changed, 79 insertions(+), 32 deletions(-) (limited to 'indra/llprimitive') diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 61e479c9fd..db4c4259f3 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -566,41 +566,88 @@ void LLModelLoader::dumpDebugData() file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n"; } - if (mDebugMode < 1) + if (mDebugMode == 2) { - return; - } + S32 model_count = 0; + for (LLPointer& mdl : mModelList) + { + const LLVolume::face_list_t &face_list = mdl->getVolumeFaces(); + for (S32 face = 0; face < face_list.size(); face++) + { + const LLVolumeFace& vf = face_list[face]; + file << "\nModel: " << mdl->mLabel + << " face " << face + << " has " << vf.mNumVertices + << " vertices and " << vf.mNumIndices + << " indices " << "\n"; - file << "\nModel LLSDs\n"; - S32 model_count = 0; - // some files contain too many models, so stop at 5. - for (LLPointer& mdl : mModelList) + file << "\nPositions for model: " << mdl->mLabel << " face " << face << "\n"; + + for (S32 pos = 0; pos < vf.mNumVertices; ++pos) + { + file << vf.mPositions[pos] << " "; + } + + file << "\n\nIndices for model: " << mdl->mLabel << " face " << face << "\n"; + + for (S32 ind = 0; ind < vf.mNumIndices; ++ind) + { + file << vf.mIndices[ind] << " "; + } + } + + file << "\n\nWeights for model: " << mdl->mLabel; + for (auto& weights : mdl->mSkinWeights) + { + file << "\nVertex: " << weights.first << " Weights: "; + for (auto& weight : weights.second) + { + file << weight.mJointIdx << ":" << weight.mWeight << " "; + } + } + + file << "\n"; + model_count++; + if (model_count == 5) + { + file << "Too many models, stopping at 5.\n"; + break; + } + } + } + else if (mDebugMode > 2) { - const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; - size_t joint_count = skin_info.mJointNames.size(); - size_t alt_count = skin_info.mAlternateBindMatrix.size(); - - LLModel::writeModel( - file, - nullptr, - mdl, - nullptr, - nullptr, - nullptr, - mdl->mPhysics, - joint_count > 0, - alt_count > 0, - false, - LLModel::WRITE_HUMAN, - false, - mdl->mSubmodelID); - - file << "\n"; - model_count++; - if (model_count == 5) + file << "\nModel LLSDs\n"; + S32 model_count = 0; + // some files contain too many models, so stop at 5. + for (LLPointer& mdl : mModelList) { - file << "Too many models, stopping at 5.\n"; - break; + const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; + size_t joint_count = skin_info.mJointNames.size(); + size_t alt_count = skin_info.mAlternateBindMatrix.size(); + + LLModel::writeModel( + file, + nullptr, + mdl, + nullptr, + nullptr, + nullptr, + mdl->mPhysics, + joint_count > 0, + alt_count > 0, + false, + LLModel::WRITE_HUMAN, + false, + mdl->mSubmodelID); + + file << "\n"; + model_count++; + if (model_count == 5) + { + file << "Too many models, stopping at 5.\n"; + break; + } } } } diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index bfb619ad22..1cac6e9709 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -38,7 +38,7 @@ ImporterDebugMode Comment - At 0 does nothing, at 1 dumps skinning data near orifinal file, at 2 dumps skining data and first 5 models as llsd + At 0 does nothing, at 1 dumps skinning data near orifinal file, at 2 dumps skining data and positions/weights of first 5 models, at 3 dumps skinning data and models as llsd Persist 1 Type -- cgit v1.3 From 60425f69f7500f55013a6d4f1c02d0b6612a731b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 8 Jul 2025 21:32:07 +0300 Subject: #4314 Fix model suffixes Usecase: Unable to use the same GLB model for physics --- indra/llprimitive/lldaeloader.cpp | 3 +- indra/llprimitive/lldaeloader.h | 2 +- indra/newview/gltf/llgltfloader.cpp | 66 +++++++++++++++++++++++-------------- indra/newview/gltf/llgltfloader.h | 11 +++---- 4 files changed, 50 insertions(+), 32 deletions(-) (limited to 'indra/llprimitive') diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index aeeca9bcfb..a11f9b5ca2 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -1682,6 +1682,7 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do { materials[model->mMaterialList[i]] = LLImportMaterial(); } + // todo: likely a bug here, shouldn't be using suffixed label, see how it gets used in other places. mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); stretch_extents(model, transformation); } @@ -2414,7 +2415,7 @@ std::string LLDAELoader::getElementLabel(daeElement *element) } // static -size_t LLDAELoader::getSuffixPosition(std::string label) +size_t LLDAELoader::getSuffixPosition(const std::string &label) { if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1)) { diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index 400277d902..0335011a56 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -98,7 +98,7 @@ protected: bool loadModelsFromDomMesh(domMesh* mesh, std::vector& models_out, U32 submodel_limit); static std::string getElementLabel(daeElement *element); - static size_t getSuffixPosition(std::string label); + static size_t getSuffixPosition(const std::string& label); static std::string getLodlessLabel(daeElement *element); static std::string preprocessDAE(std::string filename); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c850663579..a211dad5fa 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -188,6 +188,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) void LLGLTFLoader::addModelToScene( LLModel* pModel, + const std::string& model_name, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, @@ -239,7 +240,7 @@ void LLGLTFLoader::addModelToScene( 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->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; next->getVolumeFaces() = remainder; next->mNormalizedScale = current_model->mNormalizedScale; next->mNormalizedTranslation = current_model->mNormalizedTranslation; @@ -289,7 +290,12 @@ void LLGLTFLoader::addModelToScene( materials[model->mMaterialList[i]] = LLImportMaterial(); } } - mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); + std::string base_name = model_name; + if (model->mSubmodelID > 0) + { + base_name += (char)((int)'a' + model->mSubmodelID); + } + mScene[transformation].push_back(LLModelInstance(model, base_name, transformation, materials)); stretch_extents(model, transformation); } } @@ -387,7 +393,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map const LL::GLTF::Mesh& mesh = mGLTFAsset.mMeshes[node.mMesh]; // Get base mesh name and track usage - std::string base_name = mesh.mName; + std::string base_name = getLodlessLabel(mesh); if (base_name.empty()) { base_name = "mesh_" + std::to_string(node.mMesh); @@ -395,7 +401,13 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map S32 instance_count = mesh_name_counts[base_name]++; - if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && + // make name unique + if (instance_count > 0) + { + base_name = base_name + "_copy_" + std::to_string(instance_count); + } + + if (populateModelFromMesh(pModel, base_name, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { @@ -443,7 +455,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map mWarningsArray.append(args); } - addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + addModelToScene(pModel, base_name, submodel_limit, transformation, volume_params, mats); mats.clear(); } else @@ -529,29 +541,16 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ return true; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { // Set the requested label for the floater display and uploading pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + // Set name and suffix. Suffix is nessesary for model matching logic + // because sometimes higher lod can be used as a lower one, so they + // need unique names not just in scope of one lod, but across lods. + pModel->mLabel = base_name + lod_suffix[mLod]; - // 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); - } - - LL_DEBUGS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; - - if (instance_count > 0) - { - pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); - } - else - { - pModel->mLabel = base_name; - } + LL_DEBUGS("GLTF_DEBUG") << "Processing model " << pModel->mLabel << LL_ENDL; pModel->ClearFacesAndMaterials(); @@ -1723,3 +1722,22 @@ void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) } } +size_t LLGLTFLoader::getSuffixPosition(const std::string &label) +{ + if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1)) + { + return label.rfind('_'); + } + return -1; +} + +std::string LLGLTFLoader::getLodlessLabel(const LL::GLTF::Mesh& mesh) +{ + size_t ext_pos = getSuffixPosition(mesh.mName); + if (ext_pos != -1) + { + return mesh.mName.substr(0, ext_pos); + } + return mesh.mName; +} + diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 7486c72a95..3a9531bf7b 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -136,10 +136,10 @@ private: 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); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); + bool populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); 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); + void addModelToScene(LLModel* pModel, const std::string& model_name, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats); 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; @@ -153,6 +153,9 @@ private: void notifyUnsupportedExtension(bool unsupported); + static size_t getSuffixPosition(const std::string& label); + static std::string getLodlessLabel(const LL::GLTF::Mesh& mesh); + // bool mPreprocessGLTF; /* Below inherited from dae loader - unknown if/how useful here @@ -190,10 +193,6 @@ private: // 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); */ -- cgit v1.3 From 8df303ed8506a0c4fe8965130e1ac9df75d156b1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 21 Jul 2025 19:30:05 +0300 Subject: #4399 Crash at load_face_from_dom_triangles Since these offsets are used for idx[i+offset] where i starts from 0, they shouldn't be below 0 to not go out of bounds. --- indra/llprimitive/lldaeloader.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'indra/llprimitive') diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index a11f9b5ca2..bfcd84a43d 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -204,12 +204,15 @@ LLModel::EModelStatus load_face_from_dom_triangles( if (idx_stride <= 0 || (pos_source && pos_offset >= idx_stride) + || (pos_source && pos_offset < 0) || (tc_source && tc_offset >= idx_stride) - || (norm_source && norm_offset >= idx_stride)) + || (tc_source && tc_offset < 0) + || (norm_source && norm_offset >= idx_stride) + || (norm_source && norm_offset < 0)) { // 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; + LL_WARNS() << "Invalid idx_stride " << idx_stride << ", pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL; return LLModel::BAD_ELEMENT; } -- cgit v1.3 From d84897967e836cd2dadf8f609eb98946f500d5b1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 22 Jul 2025 01:54:33 +0300 Subject: #4318 Warn or log when texture gets scaled down for material and model upload --- indra/llprimitive/llmodelloader.cpp | 4 +-- indra/llprimitive/llmodelloader.h | 4 +-- indra/newview/gltf/llgltfloader.cpp | 1 + indra/newview/llfloatermodelpreview.cpp | 8 ++--- indra/newview/llmaterialeditor.cpp | 36 ++++++++++++++++++++ indra/newview/llmodelpreview.cpp | 38 +++++++++++++++++----- indra/newview/llmodelpreview.h | 5 +++ .../skins/default/xui/en/floater_model_preview.xml | 1 + .../newview/skins/default/xui/en/notifications.xml | 14 ++++++++ 9 files changed, 94 insertions(+), 17 deletions(-) (limited to 'indra/llprimitive') diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index db4c4259f3..630c109d8f 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -123,7 +123,6 @@ LLModelLoader::LLModelLoader( , mLod(lod) , mTrySLM(false) , mFirstTransform(true) -, mNumOfFetchingTextures(0) , mLoadCallback(load_cb) , mJointLookupFunc(joint_lookup_func) , mTextureLoadFunc(texture_load_func) @@ -134,6 +133,7 @@ LLModelLoader::LLModelLoader( , mNoNormalize(false) , mNoOptimize(false) , mCacheOnlyHitIfRigged(false) +, mTexturesNeedScaling(false) , mMaxJointsPerMesh(maxJointsPerMesh) , mGeneratedModelLimit(modelLimit) , mDebugMode(debugMode) @@ -669,7 +669,7 @@ void LLModelLoader::loadTextures() if(!material.mDiffuseMapFilename.empty()) { - mNumOfFetchingTextures += mTextureLoadFunc(material, mOpaqueData); + mTextureLoadFunc(material, mOpaqueData); } } } diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 06a17f006e..335d809386 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -109,6 +109,7 @@ public: bool mTrySLM; bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info) + bool mTexturesNeedScaling; 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. @@ -170,9 +171,6 @@ public: void stretch_extents(const LLModel* model, const LLMatrix4& mat); - S32 mNumOfFetchingTextures ; // updated in the main thread - bool areTexturesReady() { return !mNumOfFetchingTextures; } // called in the main thread. - bool verifyCount( int expected, int result ); //Determines the viability of an asset to be used as an avatar rig (w or w/o joint upload caps) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3019a12446..9f6caf4198 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -611,6 +611,7 @@ LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_in LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; if (image.mTexture.notNull()) { + mTexturesNeedScaling |= image.mHeight > LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT || image.mWidth > LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT; impMat.setDiffuseMap(image.mTexture->getID()); LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; } diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index bd33ab2dbe..5aef7f20b4 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -1340,26 +1340,26 @@ void LLFloaterModelPreview::addStringToLog(const std::string& message, const LLS { std::string str; switch (lod) -{ + { case LLModel::LOD_IMPOSTOR: str = "LOD0 "; break; case LLModel::LOD_LOW: str = "LOD1 "; break; case LLModel::LOD_MEDIUM: str = "LOD2 "; break; case LLModel::LOD_PHYSICS: str = "PHYS "; break; case LLModel::LOD_HIGH: str = "LOD3 "; break; default: break; -} + } LLStringUtil::format_map_t args_msg; LLSD::map_const_iterator iter = args.beginMap(); LLSD::map_const_iterator end = args.endMap(); for (; iter != end; ++iter) -{ + { args_msg[iter->first] = iter->second.asString(); } str += sInstance->getString(message, args_msg); sInstance->addStringToLogTab(str, flash); } - } +} // static void LLFloaterModelPreview::addStringToLog(const std::string& str, bool flash) diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 5b3ac53d51..3cab060357 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -2479,6 +2479,42 @@ void LLMaterialEditor::loadMaterial(const tinygltf::Model &model_in, const std:: pack_textures(base_color_img, normal_img, mr_img, emissive_img, occlusion_img, mBaseColorJ2C, mNormalJ2C, mMetallicRoughnessJ2C, mEmissiveJ2C); + if (open_floater) + { + bool textures_scaled = false; + if (mBaseColorFetched && mBaseColorJ2C + && (mBaseColorFetched->getWidth() != mBaseColorJ2C->getWidth() + || mBaseColorFetched->getHeight() != mBaseColorJ2C->getHeight())) + { + textures_scaled = true; + } + else if (mNormalFetched && mNormalJ2C + && (mNormalFetched->getWidth() != mNormalJ2C->getWidth() + || mNormalFetched->getHeight() != mNormalJ2C->getHeight())) + { + textures_scaled = true; + } + else if (mMetallicRoughnessFetched && mMetallicRoughnessJ2C + && (mMetallicRoughnessFetched->getWidth() != mMetallicRoughnessJ2C->getWidth() + || mMetallicRoughnessFetched->getHeight() != mMetallicRoughnessJ2C->getHeight())) + { + textures_scaled = true; + } + else if (mEmissiveFetched && mEmissiveJ2C + && (mEmissiveFetched->getWidth() != mEmissiveJ2C->getWidth() + || mEmissiveFetched->getHeight() != mEmissiveJ2C->getHeight())) + { + textures_scaled = true; + } + + if (textures_scaled) + { + LLSD args; + args["MAX_SIZE"] = LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT; + LLNotificationsUtil::add("MaterialImagesWereScaled", args); + } + } + LLUUID base_color_id; if (mBaseColorFetched.notNull()) { diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index ac697383e2..e819f4dec0 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -169,6 +169,8 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) , mLastJointUpdate(false) , mFirstSkinUpdate(true) , mHasDegenerate(false) + , mNumOfFetchingTextures(0) + , mTexturesNeedScaling(false) , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebugVerboseLogging", false)) { mNeedsUpdate = true; @@ -559,10 +561,7 @@ void LLModelPreview::rebuildUploadData() texture->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, new LLHandle(getHandle()), &mCallbackTextureList, false); texture->forceToSaveRawImage(0, F32_MAX); texture->updateFetch(); - if (mModelLoader) - { - mModelLoader->mNumOfFetchingTextures++; - } + mNumOfFetchingTextures++; } } } @@ -997,7 +996,9 @@ void LLModelPreview::loadModelCallback(S32 loaded_lod) setRigValidForJointPositionUpload(mModelLoader->isRigValidForJointPositionUpload()); setLegacyRigFlags(mModelLoader->getLegacyRigFlags()); + mTexturesNeedScaling |= mModelLoader->mTexturesNeedScaling; mModelLoader->loadTextures(); + warnTextureScaling(); if (loaded_lod == -1) { //populate all LoDs from model loader scene @@ -2510,7 +2511,7 @@ void LLModelPreview::updateStatusMessages() LLMutexLock lock(this); if (mModelLoader) { - if (!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean()) + if (!areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean()) { // Some textures are still loading, prevent upload until they are done mModelNoErrors = false; @@ -3216,6 +3217,7 @@ U32 LLModelPreview::loadTextures(LLImportMaterial& material, LLHandlesetLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, new LLHandle(handle), &preview->mCallbackTextureList, false); tex->forceToSaveRawImage(0, F32_MAX); material.setDiffuseMap(tex->getID()); // record tex ID + preview->mNumOfFetchingTextures++; return 1; } @@ -4060,6 +4062,18 @@ void LLModelPreview::setPreviewLOD(S32 lod) updateStatusMessages(); } +void LLModelPreview::warnTextureScaling() +{ + if (areTexturesReady() && mTexturesNeedScaling) + { + std::ostringstream out; + out << "One or more textures in this model were scaled to be within the allowed limits."; + LL_INFOS() << out.str() << LL_ENDL; + LLSD args; + LLFloaterModelPreview::addStringToLog("ModelTextureScaling", args, true, -1); + } +} + //static void LLModelPreview::textureLoadedCallback( bool success, @@ -4080,11 +4094,19 @@ void LLModelPreview::textureLoadedCallback( LLModelPreview* preview = static_cast(handle->get()); preview->refresh(); - if (final && preview->mModelLoader) + if (final) { - if (preview->mModelLoader->mNumOfFetchingTextures > 0) + if (src_vi + && (src_vi->getOriginalWidth() > LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT + || src_vi->getOriginalHeight() > LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT)) + { + preview->mTexturesNeedScaling = true; + } + + if (preview->mNumOfFetchingTextures > 0) { - preview->mModelLoader->mNumOfFetchingTextures--; + preview->mNumOfFetchingTextures--; + preview->warnTextureScaling(); } } } diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 0873263587..7b3b699b33 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -204,6 +204,7 @@ public: std::vector mLodsQuery; std::vector mLodsWithParsingError; bool mHasDegenerate; + bool areTexturesReady() { return !mNumOfFetchingTextures; } protected: @@ -213,6 +214,7 @@ protected: static LLJoint* lookupJointByName(const std::string&, void* opaque); static U32 loadTextures(LLImportMaterial& material, LLHandle handle); + void warnTextureScaling(); void lookupLODModelFiles(S32 lod); private: @@ -242,6 +244,9 @@ private: /// Not read unless mWarnOfUnmatchedPhyicsMeshes is true. LLPointer mDefaultPhysicsShapeP; + S32 mNumOfFetchingTextures; + bool mTexturesNeedScaling; + typedef enum { MESH_OPTIMIZER_FULL, 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 a86b9c7da2..f11d687840 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -39,6 +39,7 @@ Analyzing... Simplifying... TBD + One or more textures in this model were scaled to be within the allowed limits. Skinning disabled due to too many joints: [JOINTS], maximum: [MAX] diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 82e77119ab..a7e892f6f2 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -7135,6 +7135,20 @@ You don't have permission to view this notecard. fail + +One or more textures in this material were scaled to be within the allowed limits. +Textures must have power of two dimensions and must not exceed [MAX_SIZE]x[MAX_SIZE] pixels. + + confirm + + + Date: Tue, 22 Jul 2025 19:14:18 +0300 Subject: #4393 Handle unknown exceptions in uploader better Output is highly technical, but better than nothing --- indra/llprimitive/llmodelloader.cpp | 3 ++- indra/newview/gltf/llgltfloader.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'indra/llprimitive') diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 630c109d8f..0383659f62 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -33,6 +33,7 @@ #include "llmatrix4a.h" #include +#include std::list LLModelLoader::sActiveLoaderList; @@ -184,7 +185,7 @@ void LLModelLoader::run() LLSD args; args["Message"] = "UnknownException"; args["FILENAME"] = mFilename; - args["EXCEPTION"] = "Unknown exception"; + args["EXCEPTION"] = boost::current_exception_diagnostic_information(); mWarningsArray.append(args); setLoadState(ERROR_PARSING); } diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 9f6caf4198..950e98c96d 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -59,6 +59,7 @@ #include #include +#include #include static const std::string lod_suffix[LLModel::NUM_LODS] = @@ -154,7 +155,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) LLSD args; args["Message"] = "ParsingErrorException"; args["FILENAME"] = filename; - args["EXCEPTION"] = "Unknown exception"; + args["EXCEPTION"] = boost::current_exception_diagnostic_information(); mWarningsArray.append(args); setLoadState(ERROR_PARSING); return false; -- cgit v1.3