From c8499b7f01ac3f46f0764ea8195c30a4a2ec27a8 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 8 Apr 2025 13:51:21 -0400 Subject: GLTF WIP. Still working on getting transforms working proper and need to figure out our indices. --- indra/llprimitive/CMakeLists.txt | 2 - indra/llprimitive/llgltfloader.cpp | 404 --------------------------------- indra/llprimitive/llgltfloader.h | 206 ----------------- indra/newview/CMakeLists.txt | 2 + indra/newview/gltf/asset.cpp | 20 +- indra/newview/gltf/asset.h | 3 + indra/newview/gltf/llgltfloader.cpp | 431 ++++++++++++++++++++++++++++++++++++ indra/newview/gltf/llgltfloader.h | 209 +++++++++++++++++ indra/newview/llfilepicker.cpp | 2 +- indra/newview/llmodelpreview.cpp | 2 +- 10 files changed, 660 insertions(+), 621 deletions(-) delete mode 100644 indra/llprimitive/llgltfloader.cpp delete mode 100644 indra/llprimitive/llgltfloader.h create mode 100644 indra/newview/gltf/llgltfloader.cpp create mode 100644 indra/newview/gltf/llgltfloader.h diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index 3d8e02cb16..e13f0bbd96 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -12,7 +12,6 @@ include(TinyGLTF) set(llprimitive_SOURCE_FILES lldaeloader.cpp - llgltfloader.cpp llgltfmaterial.cpp llmaterialid.cpp llmaterial.cpp @@ -32,7 +31,6 @@ set(llprimitive_SOURCE_FILES set(llprimitive_HEADER_FILES CMakeLists.txt lldaeloader.h - llgltfloader.h llgltfmaterial.h llgltfmaterial_templates.h legacy_object_types.h diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp deleted file mode 100644 index 480012699a..0000000000 --- a/indra/llprimitive/llgltfloader.cpp +++ /dev/null @@ -1,404 +0,0 @@ -/** - * @file LLGLTFLoader.cpp - * @brief LLGLTFLoader class implementation - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llgltfloader.h" - -// Import & define single-header gltf import/export lib -#define TINYGLTF_IMPLEMENTATION -#define TINYGLTF_USE_CPP14 // default is C++ 11 - -// tinygltf by default loads image files using STB -#define STB_IMAGE_IMPLEMENTATION -// to use our own image loading: -// 1. replace this definition with TINYGLTF_NO_STB_IMAGE -// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) - -// tinygltf saves image files using STB -#define STB_IMAGE_WRITE_IMPLEMENTATION -// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) - -// Additionally, disable inclusion of STB header files entirely with -// TINYGLTF_NO_INCLUDE_STB_IMAGE -// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE -#include "tinygltf/tiny_gltf.h" - - -// TODO: includes inherited from dae loader. Validate / prune - -#include "llsdserialize.h" -#include "lljoint.h" - -#include "llmatrix4a.h" - -#include -#include - -static const std::string lod_suffix[LLModel::NUM_LODS] = -{ - "_LOD0", - "_LOD1", - "_LOD2", - "", - "_PHYS", -}; - - -LLGLTFLoader::LLGLTFLoader(std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void * opaque_userdata, - JointTransformMap & jointTransformMap, - JointNameSet & jointsFromNodes, - std::map &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit) //, - //bool preprocess) - : LLModelLoader( filename, - lod, - load_cb, - joint_lookup_func, - texture_load_func, - state_cb, - opaque_userdata, - jointTransformMap, - jointsFromNodes, - jointAliasMap, - maxJointsPerMesh ), - //mPreprocessGLTF(preprocess), - mMeshesLoaded(false), - mMaterialsLoaded(false) -{ -} - -LLGLTFLoader::~LLGLTFLoader() {} - -bool LLGLTFLoader::OpenFile(const std::string &filename) -{ - tinygltf::TinyGLTF loader; - std::string error_msg; - std::string warn_msg; - std::string filename_lc(filename); - LLStringUtil::toLower(filename_lc); - - // Load a tinygltf model fom a file. Assumes that the input filename has already been - // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish. - if (std::string::npos == filename_lc.rfind(".gltf")) - { // file is binary - mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename); - } - else - { // file is ascii - mGltfLoaded = loader.LoadASCIIFromFile(&mGltfModel, &error_msg, &warn_msg, filename); - } - - if (!mGltfLoaded) - { - if (!warn_msg.empty()) - LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL; - if (!error_msg.empty()) - LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL; - return false; - } - - mMeshesLoaded = parseMeshes(); - if (mMeshesLoaded) uploadMeshes(); - - mMaterialsLoaded = parseMaterials(); - if (mMaterialsLoaded) uploadMaterials(); - - return (mMeshesLoaded || mMaterialsLoaded); -} - -bool LLGLTFLoader::parseMeshes() -{ - if (!mGltfLoaded) return false; - - // 2022-04 DJH Volume params from dae example. TODO understand PCODE - LLVolumeParams volume_params; - volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); - - for (tinygltf::Mesh mesh : mGltfModel.meshes) - { - LLModel *pModel = new LLModel(volume_params, 0.f); - - if (populateModelFromMesh(pModel, mesh) && - (LLModel::NO_ERRORS == pModel->getStatus()) && - validate_model(pModel)) - { - mModelList.push_back(pModel); - } - else - { - setLoadState(ERROR_MODEL + pModel->getStatus()); - delete(pModel); - return false; - } - } - return true; -} - -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh) -{ - pModel->mLabel = mesh.name; - int pos_idx; - tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a; - - auto prims = mesh.primitives; - for (auto prim : prims) - { - if (prim.indices >= 0) indices_a = mGltfModel.accessors[prim.indices]; - - pos_idx = (prim.attributes.count("POSITION") > 0) ? prim.attributes.at("POSITION") : -1; - if (pos_idx >= 0) - { - positions_a = mGltfModel.accessors[pos_idx]; - if (TINYGLTF_COMPONENT_TYPE_FLOAT != positions_a.componentType) - continue; - auto positions_bv = mGltfModel.bufferViews[positions_a.bufferView]; - auto positions_buf = mGltfModel.buffers[positions_bv.buffer]; - //auto type = positions_vb. - //if (positions_buf.name - } - -#if 0 - int norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx; - norm_idx = (prim.attributes.count("NORMAL") > 0) ? prim.attributes.at("NORMAL") : -1; - tan_idx = (prim.attributes.count("TANGENT") > 0) ? prim.attributes.at("TANGENT") : -1; - uv0_idx = (prim.attributes.count("TEXCOORDS_0") > 0) ? prim.attributes.at("TEXCOORDS_0") : -1; - uv1_idx = (prim.attributes.count("TEXCOORDS_1") > 0) ? prim.attributes.at("TEXCOORDS_1") : -1; - color0_idx = (prim.attributes.count("COLOR_0") > 0) ? prim.attributes.at("COLOR_0") : -1; - color1_idx = (prim.attributes.count("COLOR_1") > 0) ? prim.attributes.at("COLOR_1") : -1; -#endif - - if (prim.mode == TINYGLTF_MODE_TRIANGLES) - { - //auto pos = mesh. TODO resume here DJH 2022-04 - } - } - - //pModel->addFace() - return false; -} - -bool LLGLTFLoader::parseMaterials() -{ - if (!mGltfLoaded) return false; - - // fill local texture data structures - mSamplers.clear(); - for (auto in_sampler : mGltfModel.samplers) - { - gltf_sampler sampler; - sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR; - sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;; - sampler.wrapS = in_sampler.wrapS; - sampler.wrapT = in_sampler.wrapT; - sampler.name = in_sampler.name; // unused - mSamplers.push_back(sampler); - } - - mImages.clear(); - for (auto in_image : mGltfModel.images) - { - gltf_image image; - image.numChannels = in_image.component; - image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes - image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc - image.size = static_cast(in_image.image.size()); - image.height = in_image.height; - image.width = in_image.width; - image.data = in_image.image.data(); - - if (in_image.as_is) - { - LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; - return false; - } - - if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) - { - LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; - return false; - } - - mImages.push_back(image); - } - - mTextures.clear(); - for (auto in_tex : mGltfModel.textures) - { - gltf_texture tex; - tex.imageIdx = in_tex.source; - tex.samplerIdx = in_tex.sampler; - tex.imageUuid.setNull(); - - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; - return false; - } - - mTextures.push_back(tex); - } - - // parse each material - for (tinygltf::Material gltf_material : mGltfModel.materials) - { - gltf_render_material mat; - mat.name = gltf_material.name; - - tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; - mat.hasPBR = true; // Always true, for now - - mat.baseColor.set(pbr.baseColorFactor.data()); - mat.hasBaseTex = pbr.baseColorTexture.index >= 0; - mat.baseColorTexIdx = pbr.baseColorTexture.index; - mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; - - mat.metalness = pbr.metallicFactor; - mat.roughness = pbr.roughnessFactor; - mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; - mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; - mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; - - mat.normalScale = gltf_material.normalTexture.scale; - mat.hasNormalTex = gltf_material.normalTexture.index >= 0; - mat.normalTexIdx = gltf_material.normalTexture.index; - mat.normalTexCoords = gltf_material.normalTexture.texCoord; - - mat.occlusionScale = gltf_material.occlusionTexture.strength; - mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; - mat.occlusionTexIdx = gltf_material.occlusionTexture.index; - mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; - - mat.emissiveColor.set(gltf_material.emissiveFactor.data()); - mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; - mat.emissiveTexIdx = gltf_material.emissiveTexture.index; - mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; - - mat.alphaMode = gltf_material.alphaMode; - mat.alphaMask = gltf_material.alphaCutoff; - - if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) || - (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) || - (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) || - (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) || - (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size()))) - { - LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL; - return false; - } - - if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV - (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || - (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || - (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || - (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) - { - LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; - return false; - } - - mMaterials.push_back(mat); - } - - return true; -} - -// TODO: convert raw vertex buffers to UUIDs -void LLGLTFLoader::uploadMeshes() -{ - llassert(0); -} - -// convert raw image buffers to texture UUIDs & assemble into a render material -void LLGLTFLoader::uploadMaterials() -{ - for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple - { - if (mat.hasBaseTex) - { - gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasMRTex) - { - gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasNormalTex) - { - gltf_texture& gtex = mTextures[mat.normalTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasOcclusionTex) - { - gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasEmissiveTex) - { - gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - } -} - -LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) -{ - //gltf_image& image = mImages[tex.imageIdx]; - //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; - - // fill an LLSD container with image+sampler data - - // upload texture - - // retrieve UUID - - return LLUUID::null; -} diff --git a/indra/llprimitive/llgltfloader.h b/indra/llprimitive/llgltfloader.h deleted file mode 100644 index 66671d1c5a..0000000000 --- a/indra/llprimitive/llgltfloader.h +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @file LLGLTFLoader.h - * @brief LLGLTFLoader class definition - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLGLTFLoader_H -#define LL_LLGLTFLoader_H - -#include "tinygltf/tiny_gltf.h" - -#include "llglheaders.h" -#include "llmodelloader.h" - -// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD - -class gltf_sampler -{ -public: - // Uses GL enums - S32 minFilter; // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR - S32 magFilter; // GL_NEAREST or GL_LINEAR - S32 wrapS; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT - S32 wrapT; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT - //S32 wrapR; // Found in some sample files, but not part of glTF 2.0 spec. Ignored. - std::string name; // optional, currently unused - // extensions and extras are sampler optional fields that we don't support - at least initially -}; - -class gltf_image -{ -public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL) - U8* data; // ptr to decoded image data - U32 size; // in bytes, regardless of channel width - U32 width; - U32 height; - U32 numChannels; // range 1..4 - U32 bytesPerChannel; // converted from gltf "bits", expects only 8, 16 or 32 as input - U32 pixelType; // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT -}; - -class gltf_texture -{ -public: - U32 imageIdx; - U32 samplerIdx; - LLUUID imageUuid = LLUUID::null; -}; - -class gltf_render_material -{ -public: - std::string name; - - // scalar values - LLColor4 baseColor; // linear encoding. Multiplied with vertex color, if present. - double metalness; - double roughness; - double normalScale; // scale applies only to X,Y components of normal - double occlusionScale; // strength multiplier for occlusion - LLColor4 emissiveColor; // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent) - std::string alphaMode; // "OPAQUE", "MASK" or "BLEND" - double alphaMask; // alpha cut-off - - // textures - U32 baseColorTexIdx; // always sRGB encoded - U32 metalRoughTexIdx; // always linear, roughness in G channel, metalness in B channel - U32 normalTexIdx; // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0) - U32 occlusionTexIdx; // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded - U32 emissiveTexIdx; // always stored as sRGB, in nits (candela / meter^2) - - // texture coordinates - U32 baseColorTexCoords; - U32 metalRoughTexCoords; - U32 normalTexCoords; - U32 occlusionTexCoords; - U32 emissiveTexCoords; - - // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry?? - - bool hasPBR; - bool hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex; - - // This field is populated after upload - LLUUID material_uuid = LLUUID::null; - -}; - -class gltf_mesh -{ -public: - std::string name; - - // TODO add mesh import DJH 2022-04 - -}; - -class LLGLTFLoader : public LLModelLoader -{ - public: - typedef std::map material_map; - - LLGLTFLoader(std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void * opaque_userdata, - JointTransformMap & jointTransformMap, - JointNameSet & jointsFromNodes, - std::map &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit); //, - //bool preprocess ); - virtual ~LLGLTFLoader(); - - virtual bool OpenFile(const std::string &filename); - -protected: - tinygltf::Model mGltfModel; - bool mGltfLoaded; - bool mMeshesLoaded; - bool mMaterialsLoaded; - - std::vector mMeshes; - std::vector mMaterials; - - std::vector mTextures; - std::vector mImages; - std::vector mSamplers; - -private: - bool parseMeshes(); - void uploadMeshes(); - bool parseMaterials(); - void uploadMaterials(); - bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh); - LLUUID imageBufferToTextureUUID(const gltf_texture& tex); - - // bool mPreprocessGLTF; - - /* Below inherited from dae loader - unknown if/how useful here - - void processElement(gltfElement *element, bool &badElement, GLTF *gltf); - void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin); - - material_map getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf); - LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf); - LLColor4 getGltfColor(gltfElement *element); - - gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name); - - bool isNodeAJoint(gltfNode *pNode); - void processJointNode(gltfNode *pNode, std::map &jointTransforms); - void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform); - void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform); - void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform); - void buildJointToNodeMappingFromScene(gltfElement *pRoot); - void processJointToNodeMapping(gltfNode *pNode); - void processChildJoints(gltfNode *pParentNode); - - bool verifyCount(int expected, int result); - - // Verify that a controller matches vertex counts - bool verifyController(gltfController *pController); - - static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg); - static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh); - - static LLModel *loadModelFromGltfMesh(gltfMesh *mesh); - - // Loads a mesh breaking it into one or more models as necessary - // to get around volume face limitations while retaining >8 materials - // - bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector &models_out, U32 submodel_limit); - - static std::string getElementLabel(gltfElement *element); - static size_t getSuffixPosition(std::string label); - static std::string getLodlessLabel(gltfElement *element); - - static std::string preprocessGLTF(std::string filename); - */ - -}; -#endif // LL_LLGLTFLLOADER_H diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index ed29911a43..7dfe01898a 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -76,6 +76,7 @@ set(viewer_SOURCE_FILES gltf/accessor.cpp gltf/primitive.cpp gltf/animation.cpp + gltf/llgltfloader.cpp groupchatlistener.cpp llaccountingcostmanager.cpp llaisapi.cpp @@ -744,6 +745,7 @@ set(viewer_HEADER_FILES gltf/buffer_util.h gltf/primitive.h gltf/animation.h + gltf/llgltfloader.h llaccountingcost.h llaccountingcostmanager.h llaisapi.h diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index c210b9c61d..beccb02bd4 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -472,11 +472,14 @@ void Asset::update() for (auto& image : mImages) { - if (image.mTexture.notNull()) - { // HACK - force texture to be loaded full rez - // TODO: calculate actual vsize - image.mTexture->addTextureStats(2048.f * 2048.f); - image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); + if (image.mLoadIntoTexturePipe) + { + if (image.mTexture.notNull()) + { // HACK - force texture to be loaded full rez + // TODO: calculate actual vsize + image.mTexture->addTextureStats(2048.f * 2048.f); + image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); + } } } } @@ -603,6 +606,7 @@ bool Asset::prep() if (vertex_count[variant] > 0) { U32 mat_idx = mat_id + 1; + #if 0 LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); rd.mBatches[variant][mat_idx].mVertexBuffer = vb; @@ -624,6 +628,7 @@ bool Asset::prep() vb->unmapBuffer(); vb->unbind(); + #endif } } } @@ -634,10 +639,10 @@ bool Asset::prep() { for (auto& primitive : mesh.mPrimitives) { - llassert(primitive.mVertexBuffer.notNull()); + //llassert(primitive.mVertexBuffer.notNull()); } } - + #if 0 // build render batches for (S32 node_id = 0; node_id < mNodes.size(); ++node_id) { @@ -664,6 +669,7 @@ bool Asset::prep() } } } + #endif return true; } diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 27821659db..e70fffa986 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -286,6 +286,7 @@ namespace LL void serialize(boost::json::object& dst) const; }; + // Image is for images that we want to load for the given asset. This acts as an interface into the viewer's texture pipe. class Image { public: @@ -301,6 +302,8 @@ namespace LL S32 mBits = -1; S32 mPixelType = -1; + bool mLoadIntoTexturePipe = false; + LLPointer mTexture; const Image& operator=(const Value& src); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp new file mode 100644 index 0000000000..106c20d4d3 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.cpp @@ -0,0 +1,431 @@ +/** + * @file LLGLTFLoader.cpp + * @brief LLGLTFLoader class implementation + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llgltfloader.h" + +// Import & define single-header gltf import/export lib +#define TINYGLTF_IMPLEMENTATION +#define TINYGLTF_USE_CPP14 // default is C++ 11 + +// tinygltf by default loads image files using STB +#define STB_IMAGE_IMPLEMENTATION +// to use our own image loading: +// 1. replace this definition with TINYGLTF_NO_STB_IMAGE +// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data) + +// tinygltf saves image files using STB +#define STB_IMAGE_WRITE_IMPLEMENTATION +// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data) + +// Additionally, disable inclusion of STB header files entirely with +// TINYGLTF_NO_INCLUDE_STB_IMAGE +// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE +#include "tinygltf/tiny_gltf.h" + + +// TODO: includes inherited from dae loader. Validate / prune + +#include "llsdserialize.h" +#include "lljoint.h" + +#include "llmatrix4a.h" + +#include +#include + +static const std::string lod_suffix[LLModel::NUM_LODS] = +{ + "_LOD0", + "_LOD1", + "_LOD2", + "", + "_PHYS", +}; + + +LLGLTFLoader::LLGLTFLoader(std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void * opaque_userdata, + JointTransformMap & jointTransformMap, + JointNameSet & jointsFromNodes, + std::map &jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit) //, + //bool preprocess) + : LLModelLoader( filename, + lod, + load_cb, + joint_lookup_func, + texture_load_func, + state_cb, + opaque_userdata, + jointTransformMap, + jointsFromNodes, + jointAliasMap, + maxJointsPerMesh ), + //mPreprocessGLTF(preprocess), + mMeshesLoaded(false), + mMaterialsLoaded(false) +{ +} + +LLGLTFLoader::~LLGLTFLoader() {} + +bool LLGLTFLoader::OpenFile(const std::string &filename) +{ + tinygltf::TinyGLTF loader; + std::string error_msg; + std::string warn_msg; + std::string filename_lc(filename); + LLStringUtil::toLower(filename_lc); + + mGltfLoaded = mGLTFAsset.load(filename); + + if (!mGltfLoaded) + { + if (!warn_msg.empty()) + LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL; + if (!error_msg.empty()) + LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL; + return false; + } + + mMeshesLoaded = parseMeshes(); + if (mMeshesLoaded) uploadMeshes(); + + /* + mMaterialsLoaded = parseMaterials(); + if (mMaterialsLoaded) uploadMaterials(); + */ + + setLoadState(DONE); + + return (mMeshesLoaded); +} + +bool LLGLTFLoader::parseMeshes() +{ + if (!mGltfLoaded) return false; + + // 2022-04 DJH Volume params from dae example. TODO understand PCODE + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + + for (auto node : mGLTFAsset.mNodes) + { + LLMatrix4 transform; + material_map mats; + auto meshidx = node.mMesh; + + if (meshidx >= 0) + { + LLModel* pModel = new LLModel(volume_params, 0.f); + auto mesh = mGLTFAsset.mMeshes[meshidx]; + + if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) + { + mModelList.push_back(pModel); + LLVector3 mesh_scale_vector; + LLVector3 mesh_translation_vector; + pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transform; + transform = mesh_translation; + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transform; + transform = mesh_scale; + + + mScene[transform].push_back(LLModelInstance(pModel, node.mName, transform, mats)); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete (pModel); + return false; + } + } + } + + return true; +} + +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map &mats) +{ + pModel->mLabel = mesh.mName; + pModel->ClearFacesAndMaterials(); + + auto prims = mesh.mPrimitives; + for (auto prim : prims) + { + // So primitives already have all of the data we need for a given face in SL land. + // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call count. + // Just go ahead and populate faces direct from the GLTF primitives here. + // -Geenz 2025-04-07 + LLVolumeFace face; + LLVolumeFace::VertexMapData::PointMap point_map; + + std::vector vertices; + std::vector indices; + + LLImportMaterial impMat; + + auto material = mGLTFAsset.mMaterials[prim.mMaterial]; + + impMat.mDiffuseColor = LLColor4::white; + + + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + LLVolumeFace::VertexData vert; + vert.setPosition(prim.mPositions[i]); + vert.setNormal(prim.mNormals[i]); + vert.mTexCoord = prim.mTexCoords0[i]; + vertices.push_back(vert); + } + + for (S32 i = 0; i < prim.mIndexArray.size(); i++) + { + indices.push_back(prim.mIndexArray[i]); + } + + face.fillFromLegacyData(vertices, indices); + + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); + mats["mat" + std::to_string(prim.mMaterial)] = impMat; + } + + return true; +} + +bool LLGLTFLoader::parseMaterials() +{ + return true; + /* + if (!mGltfLoaded) return false; + + // fill local texture data structures + mSamplers.clear(); + for (auto in_sampler : mGltfModel.samplers) + { + gltf_sampler sampler; + sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR; + sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;; + sampler.wrapS = in_sampler.wrapS; + sampler.wrapT = in_sampler.wrapT; + sampler.name = in_sampler.name; // unused + mSamplers.push_back(sampler); + } + + mImages.clear(); + for (auto in_image : mGltfModel.images) + { + gltf_image image; + image.numChannels = in_image.component; + image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes + image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc + image.size = static_cast(in_image.image.size()); + image.height = in_image.height; + image.width = in_image.width; + image.data = in_image.image.data(); + + if (in_image.as_is) + { + LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; + return false; + } + + if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) + { + LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; + return false; + } + + mImages.push_back(image); + } + + mTextures.clear(); + for (auto in_tex : mGltfModel.textures) + { + gltf_texture tex; + tex.imageIdx = in_tex.source; + tex.samplerIdx = in_tex.sampler; + tex.imageUuid.setNull(); + + if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) + { + LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; + return false; + } + + mTextures.push_back(tex); + } + + // parse each material + for (tinygltf::Material gltf_material : mGltfModel.materials) + { + gltf_render_material mat; + mat.name = gltf_material.name; + + tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; + mat.hasPBR = true; // Always true, for now + + mat.baseColor.set(pbr.baseColorFactor.data()); + mat.hasBaseTex = pbr.baseColorTexture.index >= 0; + mat.baseColorTexIdx = pbr.baseColorTexture.index; + mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; + + mat.metalness = pbr.metallicFactor; + mat.roughness = pbr.roughnessFactor; + mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; + mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; + mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; + + mat.normalScale = gltf_material.normalTexture.scale; + mat.hasNormalTex = gltf_material.normalTexture.index >= 0; + mat.normalTexIdx = gltf_material.normalTexture.index; + mat.normalTexCoords = gltf_material.normalTexture.texCoord; + + mat.occlusionScale = gltf_material.occlusionTexture.strength; + mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; + mat.occlusionTexIdx = gltf_material.occlusionTexture.index; + mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; + + mat.emissiveColor.set(gltf_material.emissiveFactor.data()); + mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; + mat.emissiveTexIdx = gltf_material.emissiveTexture.index; + mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; + + mat.alphaMode = gltf_material.alphaMode; + mat.alphaMask = gltf_material.alphaCutoff; + + if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) || + (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) || + (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) || + (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) || + (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size()))) + { + LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL; + return false; + } + + if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV + (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || + (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || + (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || + (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) + { + LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; + return false; + } + + mMaterials.push_back(mat); + } + + return true; + */ +} + +// TODO: convert raw vertex buffers to UUIDs +void LLGLTFLoader::uploadMeshes() +{ + //llassert(0); +} + +// convert raw image buffers to texture UUIDs & assemble into a render material +void LLGLTFLoader::uploadMaterials() +{ + for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple + { + if (mat.hasBaseTex) + { + gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasMRTex) + { + gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasNormalTex) + { + gltf_texture& gtex = mTextures[mat.normalTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasOcclusionTex) + { + gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + + if (mat.hasEmissiveTex) + { + gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; + if (gtex.imageUuid.isNull()) + { + gtex.imageUuid = imageBufferToTextureUUID(gtex); + } + } + } +} + +LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) +{ + //gltf_image& image = mImages[tex.imageIdx]; + //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + + // fill an LLSD container with image+sampler data + + // upload texture + + // retrieve UUID + + return LLUUID::null; +} diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h new file mode 100644 index 0000000000..3b7147f588 --- /dev/null +++ b/indra/newview/gltf/llgltfloader.h @@ -0,0 +1,209 @@ +/** + * @file LLGLTFLoader.h + * @brief LLGLTFLoader class definition + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLGLTFLoader_H +#define LL_LLGLTFLoader_H + +#include "tinygltf/tiny_gltf.h" + +#include "asset.h" + +#include "llglheaders.h" +#include "llmodelloader.h" + +// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD + +class gltf_sampler +{ +public: + // Uses GL enums + S32 minFilter; // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR + S32 magFilter; // GL_NEAREST or GL_LINEAR + S32 wrapS; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT + S32 wrapT; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT + //S32 wrapR; // Found in some sample files, but not part of glTF 2.0 spec. Ignored. + std::string name; // optional, currently unused + // extensions and extras are sampler optional fields that we don't support - at least initially +}; + +class gltf_image +{ +public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL) + U8* data; // ptr to decoded image data + U32 size; // in bytes, regardless of channel width + U32 width; + U32 height; + U32 numChannels; // range 1..4 + U32 bytesPerChannel; // converted from gltf "bits", expects only 8, 16 or 32 as input + U32 pixelType; // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT +}; + +class gltf_texture +{ +public: + U32 imageIdx; + U32 samplerIdx; + LLUUID imageUuid = LLUUID::null; +}; + +class gltf_render_material +{ +public: + std::string name; + + // scalar values + LLColor4 baseColor; // linear encoding. Multiplied with vertex color, if present. + double metalness; + double roughness; + double normalScale; // scale applies only to X,Y components of normal + double occlusionScale; // strength multiplier for occlusion + LLColor4 emissiveColor; // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent) + std::string alphaMode; // "OPAQUE", "MASK" or "BLEND" + double alphaMask; // alpha cut-off + + // textures + U32 baseColorTexIdx; // always sRGB encoded + U32 metalRoughTexIdx; // always linear, roughness in G channel, metalness in B channel + U32 normalTexIdx; // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0) + U32 occlusionTexIdx; // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded + U32 emissiveTexIdx; // always stored as sRGB, in nits (candela / meter^2) + + // texture coordinates + U32 baseColorTexCoords; + U32 metalRoughTexCoords; + U32 normalTexCoords; + U32 occlusionTexCoords; + U32 emissiveTexCoords; + + // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry?? + + bool hasPBR; + bool hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex; + + // This field is populated after upload + LLUUID material_uuid = LLUUID::null; + +}; + +class gltf_mesh +{ +public: + std::string name; + + // TODO add mesh import DJH 2022-04 + +}; + +class LLGLTFLoader : public LLModelLoader +{ + public: + typedef std::map material_map; + + LLGLTFLoader(std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void * opaque_userdata, + JointTransformMap & jointTransformMap, + JointNameSet & jointsFromNodes, + std::map &jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit); //, + //bool preprocess ); + virtual ~LLGLTFLoader(); + + virtual bool OpenFile(const std::string &filename); + +protected: + LL::GLTF::Asset mGLTFAsset; + tinygltf::Model mGltfModel; + bool mGltfLoaded; + bool mMeshesLoaded; + bool mMaterialsLoaded; + + std::vector mMeshes; + std::vector mMaterials; + + std::vector mTextures; + std::vector mImages; + std::vector mSamplers; + +private: + bool parseMeshes(); + void uploadMeshes(); + bool parseMaterials(); + void uploadMaterials(); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map& mats); + LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + + // bool mPreprocessGLTF; + + /* Below inherited from dae loader - unknown if/how useful here + + void processElement(gltfElement *element, bool &badElement, GLTF *gltf); + void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin); + + material_map getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf); + LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf); + LLColor4 getGltfColor(gltfElement *element); + + gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name); + + bool isNodeAJoint(gltfNode *pNode); + void processJointNode(gltfNode *pNode, std::map &jointTransforms); + void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform); + void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform); + void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform); + void buildJointToNodeMappingFromScene(gltfElement *pRoot); + void processJointToNodeMapping(gltfNode *pNode); + void processChildJoints(gltfNode *pParentNode); + + bool verifyCount(int expected, int result); + + // Verify that a controller matches vertex counts + bool verifyController(gltfController *pController); + + static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg); + static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh); + + static LLModel *loadModelFromGltfMesh(gltfMesh *mesh); + + // Loads a mesh breaking it into one or more models as necessary + // to get around volume face limitations while retaining >8 materials + // + bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector &models_out, U32 submodel_limit); + + static std::string getElementLabel(gltfElement *element); + static size_t getSuffixPosition(std::string label); + static std::string getLodlessLabel(gltfElement *element); + + static std::string preprocessGLTF(std::string filename); + */ + +}; +#endif // LL_LLGLTFLLOADER_H diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 716e6cd9e3..09a2206cd7 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -59,7 +59,7 @@ LLFilePicker LLFilePicker::sInstance; #define XML_FILTER L"XML files (*.xml)\0*.xml\0" #define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0" #define RAW_FILTER L"RAW files (*.raw)\0*.raw\0" -#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0" +#define MODEL_FILTER L"Model files (*.dae, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0" #define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0" #define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0" #define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 5a8fd299bf..14d23b73c9 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -30,7 +30,7 @@ #include "llmodelloader.h" #include "lldaeloader.h" -#include "llgltfloader.h" +#include "gltf/llgltfloader.h" #include "llfloatermodelpreview.h" #include "llagent.h" -- cgit v1.2.3 From f6b066073e9a6318dc9ebac4b0137b81e2982f14 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 28 Apr 2025 03:33:03 -0400 Subject: Cursed stuff! --- indra/newview/gltf/llgltfloader.cpp | 278 ++++++++++++++++++++++++++++++++---- indra/newview/gltf/llgltfloader.h | 7 + 2 files changed, 254 insertions(+), 31 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 106c20d4d3..ce87a9594f 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -25,6 +25,7 @@ */ #include "llgltfloader.h" +#include "meshoptimizer.h" // Import & define single-header gltf import/export lib #define TINYGLTF_IMPLEMENTATION @@ -152,20 +153,19 @@ bool LLGLTFLoader::parseMeshes() if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { mModelList.push_back(pModel); - LLVector3 mesh_scale_vector; - LLVector3 mesh_translation_vector; - pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + LLVector3 mesh_scale_vector = LLVector3(node.mScale); + LLVector3 mesh_translation_vector = LLVector3(node.mTranslation); LLMatrix4 mesh_translation; mesh_translation.setTranslation(mesh_translation_vector); mesh_translation *= transform; - transform = mesh_translation; - + transform = LLMatrix4((float*)&node.mAssetMatrix[0][0]); + /* LLMatrix4 mesh_scale; mesh_scale.initScale(mesh_scale_vector); mesh_scale *= transform; transform = mesh_scale; - + */ mScene[transform].push_back(LLModelInstance(pModel, node.mName, transform, mats)); } @@ -189,47 +189,263 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & auto prims = mesh.mPrimitives; for (auto prim : prims) { - // So primitives already have all of the data we need for a given face in SL land. - // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call count. - // Just go ahead and populate faces direct from the GLTF primitives here. - // -Geenz 2025-04-07 - LLVolumeFace face; - LLVolumeFace::VertexMapData::PointMap point_map; + // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. + if (prim.getVertexCount() < USHRT_MAX) + { + // So primitives already have all of the data we need for a given face in SL land. + // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call + // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 + LLVolumeFace face; + LLVolumeFace::VertexMapData::PointMap point_map; + + std::vector vertices; + std::vector indices; + + LLImportMaterial impMat; + + LL::GLTF::Material* material = nullptr; + + if (prim.mMaterial >= 0) + material = &mGLTFAsset.mMaterials[prim.mMaterial]; + + impMat.mDiffuseColor = LLColor4::white; + + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + GLTFVertex vert; + vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[2][i]); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], prim.mTexCoords0[i][1]); + vertices.push_back(vert); + } + + for (U32 i = 0; i < prim.getIndexCount(); i++) + { + indices.push_back(prim.mIndexArray[i]); + } + + std::vector faceVertices; + + for (U32 i = 0; i < vertices.size(); i++) + { + LLVolumeFace::VertexData vert; + LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); + LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); + vert.setPosition(position); + vert.setNormal(normal); + vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); + faceVertices.push_back(vert); + } + + face.fillFromLegacyData(faceVertices, indices); + + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); + mats["mat" + std::to_string(prim.mMaterial)] = impMat; + } + } + + return true; +} +/* +LLModel::EModelStatus loadFaceFromGLTFModel(LLModel* pModel, const LL::GLTF::Mesh& mesh, material_map& mats, LLSD& log_msg) +{ + LLVolumeFace face; + std::vector verts; + std::vector indices; + + S32 pos_offset = -1; + S32 tc_offset = -1; + S32 norm_offset = -1; + + auto pos_source = mesh.mPrimitives[0].mPositions; + auto tc_source = mesh.mPrimitives[0].mNormals; + auto norm_source = mesh.mPrimitives[0].mTexCoords0; + + S32 idx_stride = 0; + + if (pos_source.size() > USHRT_MAX) + { + LL_WARNS() << "Unable to process mesh due to 16-bit index limits; invalid model; invalid model." << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorBadElement"; + log_msg.append(args); + return LLModel::BAD_ELEMENT; + + } + + std::vector idx = mesh.mPrimitives[0].mIndexArray; + + domListOfFloats dummy; + domListOfFloats& v = pos_source ? pos_source->getFloat_array()->getValue() : dummy; + domListOfFloats& tc = tc_source ? tc_source->getFloat_array()->getValue() : dummy; + domListOfFloats& n = norm_source ? norm_source->getFloat_array()->getValue() : dummy; + + if (pos_source.size() == 0) + { + return LLModel::BAD_ELEMENT; + } + + // VFExtents change + face.mExtents[0].set(pos_source[0][0], pos_source[0][1], pos_source[0][2]); + face.mExtents[1].set(pos_source[0][0], pos_source[0][1], pos_source[0][2]); + + LLVolumeFace::VertexMapData::PointMap point_map; + + if (idx_stride <= 0 || (pos_source && pos_offset >= idx_stride) || (tc_source && tc_offset >= idx_stride) || + (norm_source && norm_offset >= idx_stride)) + { + // Looks like these offsets should fit inside idx_stride + // Might be good idea to also check idx.getCount()%idx_stride != 0 + LL_WARNS() << "Invalid pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL; + return LLModel::BAD_ELEMENT; + } + + for (U32 i = 0; i < idx.getCount(); i += idx_stride) + { + LLVolumeFace::VertexData cv; + if (pos_source) + { + cv.setPosition( + LLVector4a((F32)v[idx[i + pos_offset] * 3 + 0], (F32)v[idx[i + pos_offset] * 3 + 1], (F32)v[idx[i + pos_offset] * 3 + 2])); + } - std::vector vertices; - std::vector indices; + if (tc_source) + { + cv.mTexCoord.setVec((F32)tc[idx[i + tc_offset] * 2 + 0], (F32)tc[idx[i + tc_offset] * 2 + 1]); + } - LLImportMaterial impMat; + if (norm_source) + { + cv.setNormal(LLVector4a((F32)n[idx[i + norm_offset] * 3 + 0], + (F32)n[idx[i + norm_offset] * 3 + 1], + (F32)n[idx[i + norm_offset] * 3 + 2])); + } - auto material = mGLTFAsset.mMaterials[prim.mMaterial]; + bool found = false; - impMat.mDiffuseColor = LLColor4::white; + LLVolumeFace::VertexMapData::PointMap::iterator point_iter; + point_iter = point_map.find(LLVector3(cv.getPosition().getF32ptr())); + if (point_iter != point_map.end()) + { + for (U32 j = 0; j < point_iter->second.size(); ++j) + { + // We have a matching loc + // + if ((point_iter->second)[j] == cv) + { + U16 shared_index = (point_iter->second)[j].mIndex; + + // Don't share verts within the same tri, degenerate + // + U32 indx_size = static_cast(indices.size()); + U32 verts_new_tri = indx_size % 3; + if ((verts_new_tri < 1 || indices[indx_size - 1] != shared_index) && + (verts_new_tri < 2 || indices[indx_size - 2] != shared_index)) + { + found = true; + indices.push_back(shared_index); + } + break; + } + } + } - for (U32 i = 0; i < prim.getVertexCount(); i++) + if (!found) { - LLVolumeFace::VertexData vert; - vert.setPosition(prim.mPositions[i]); - vert.setNormal(prim.mNormals[i]); - vert.mTexCoord = prim.mTexCoords0[i]; - vertices.push_back(vert); + // VFExtents change + update_min_max(face.mExtents[0], face.mExtents[1], cv.getPosition()); + verts.push_back(cv); + if (verts.size() >= 65535) + { + // llerrs << "Attempted to write model exceeding 16-bit index buffer limitation." << LL_ENDL; + return LLModel::VERTEX_NUMBER_OVERFLOW; + } + U16 index = (U16)(verts.size() - 1); + indices.push_back(index); + + LLVolumeFace::VertexMapData d; + d.setPosition(cv.getPosition()); + d.mTexCoord = cv.mTexCoord; + d.setNormal(cv.getNormal()); + d.mIndex = index; + if (point_iter != point_map.end()) + { + point_iter->second.push_back(d); + } + else + { + point_map[LLVector3(d.getPosition().getF32ptr())].push_back(d); + } } - for (S32 i = 0; i < prim.mIndexArray.size(); i++) + if (indices.size() % 3 == 0 && verts.size() >= 65532) { - indices.push_back(prim.mIndexArray[i]); + std::string material; + + if (tri->getMaterial()) + { + material = std::string(tri->getMaterial()); + } + + materials.push_back(material); + face_list.push_back(face); + face_list.rbegin()->fillFromLegacyData(verts, indices); + LLVolumeFace& new_face = *face_list.rbegin(); + if (!norm_source) + { + // ll_aligned_free_16(new_face.mNormals); + new_face.mNormals = NULL; + } + + if (!tc_source) + { + // ll_aligned_free_16(new_face.mTexCoords); + new_face.mTexCoords = NULL; + } + + face = LLVolumeFace(); + // VFExtents change + face.mExtents[0].set((F32)v[0], (F32)v[1], (F32)v[2]); + face.mExtents[1].set((F32)v[0], (F32)v[1], (F32)v[2]); + + verts.clear(); + indices.clear(); + point_map.clear(); } + } - face.fillFromLegacyData(vertices, indices); + if (!verts.empty()) + { + std::string material; - pModel->getVolumeFaces().push_back(face); - pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); - mats["mat" + std::to_string(prim.mMaterial)] = impMat; + if (tri->getMaterial()) + { + material = std::string(tri->getMaterial()); + } + + materials.push_back(material); + face_list.push_back(face); + + face_list.rbegin()->fillFromLegacyData(verts, indices); + LLVolumeFace& new_face = *face_list.rbegin(); + if (!norm_source) + { + // ll_aligned_free_16(new_face.mNormals); + new_face.mNormals = NULL; + } + + if (!tc_source) + { + // ll_aligned_free_16(new_face.mTexCoords); + new_face.mTexCoords = NULL; + } } - return true; + return LLModel::NO_ERRORS; } - +*/ bool LLGLTFLoader::parseMaterials() { return true; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 3b7147f588..cfb545be6f 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -139,6 +139,13 @@ class LLGLTFLoader : public LLModelLoader virtual bool OpenFile(const std::string &filename); + struct GLTFVertex + { + glm::vec3 position; + glm::vec3 normal; + glm::vec2 uv0; + }; + protected: LL::GLTF::Asset mGLTFAsset; tinygltf::Model mGltfModel; -- cgit v1.2.3 From 008f89f7e9698028e8beaa42a64428909271ebf3 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Sun, 4 May 2025 19:05:34 -0400 Subject: More fixes - still a bit hacky but getting there. --- indra/newview/gltf/llgltfloader.cpp | 98 +++++++++++++++++++++++++++---------- indra/newview/gltf/llgltfloader.h | 2 + 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index ce87a9594f..bb9cedb262 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -118,6 +118,8 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } + mTransform.setIdentity(); + mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); @@ -147,33 +149,44 @@ bool LLGLTFLoader::parseMeshes() if (meshidx >= 0) { - LLModel* pModel = new LLModel(volume_params, 0.f); - auto mesh = mGLTFAsset.mMeshes[meshidx]; - - if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) - { - mModelList.push_back(pModel); - LLVector3 mesh_scale_vector = LLVector3(node.mScale); - LLVector3 mesh_translation_vector = LLVector3(node.mTranslation); - - LLMatrix4 mesh_translation; - mesh_translation.setTranslation(mesh_translation_vector); - mesh_translation *= transform; - transform = LLMatrix4((float*)&node.mAssetMatrix[0][0]); - /* - LLMatrix4 mesh_scale; - mesh_scale.initScale(mesh_scale_vector); - mesh_scale *= transform; - transform = mesh_scale; - */ - - mScene[transform].push_back(LLModelInstance(pModel, node.mName, transform, mats)); - } - else + if (mGLTFAsset.mMeshes.size() > meshidx) { - setLoadState(ERROR_MODEL + pModel->getStatus()); - delete (pModel); - return false; + LLModel* pModel = new LLModel(volume_params, 0.f); + auto mesh = mGLTFAsset.mMeshes[meshidx]; + if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) + { + mModelList.push_back(pModel); + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(LLVector3(node.mTranslation)); + mesh_translation *= mTransform; + mTransform = mesh_translation; + mTransform.condition(); + + LLMatrix4 mesh_rotation; + mesh_rotation.initRotation(LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3])); + mesh_rotation *= mTransform; + mTransform = mesh_rotation; + mTransform.condition(); + + LLMatrix4 mesh_scale; + mesh_scale.initScale(LLVector3(glm::abs(node.mScale))); + mesh_scale *= mTransform; + mTransform = mesh_scale; + mTransform.condition(); + + LLVector3 mesh_scale_vector = LLVector3(node.mScale); + LLVector3 mesh_translation_vector = LLVector3(node.mTranslation); + + transform = mTransform; + + mScene[transform].push_back(LLModelInstance(pModel, pModel->mLabel, transform, mats)); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete (pModel); + return false; + } } } } @@ -243,11 +256,42 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); mats["mat" + std::to_string(prim.mMaterial)] = impMat; } + else { + LL_INFOS() << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorBadElement"; + mWarningsArray.append(args); + return false; + } } return true; } -/* + +void LLGLTFLoader::processPrimitive(const LL::GLTF::Primitive& primitive, const LL::GLTF::Node& node) +{ + LLMatrix4 translation; + translation.setTranslation(LLVector3(node.mTranslation)); + translation *= mTransform; + mTransform = translation; + mTransform.condition(); + + LLMatrix4 rotation; + rotation = LLMatrix4(LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3])); + rotation *= mTransform; + mTransform = rotation; + mTransform.condition(); + + LLMatrix4 scale; + scale.initScale(LLVector3(glm::abs(node.mScale))); + scale *= mTransform; + mTransform = scale; + mTransform.condition(); + + if (primitive.mPositions.size()) { + } +} + /* LLModel::EModelStatus loadFaceFromGLTFModel(LLModel* pModel, const LL::GLTF::Mesh& mesh, material_map& mats, LLSD& log_msg) { LLVolumeFace face; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index cfb545be6f..32275d168d 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -168,6 +168,8 @@ private: bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map& mats); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + void processPrimitive(const LL::GLTF::Primitive& primitive, const LL::GLTF::Node& node); + // bool mPreprocessGLTF; /* Below inherited from dae loader - unknown if/how useful here -- cgit v1.2.3 From 3a4a2a525fe667250d014ad5d09a84a5c35aedf9 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Sun, 4 May 2025 19:22:01 -0400 Subject: Fix normals --- indra/newview/gltf/llgltfloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index bb9cedb262..3f5dbb3f87 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -227,7 +227,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & { GLTFVertex vert; vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); - vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[2][i]); + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], prim.mTexCoords0[i][1]); vertices.push_back(vert); } -- cgit v1.2.3 From e87c62940656aa740ddfa20bb2eed7a13cdb59c7 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Sun, 4 May 2025 21:06:10 -0400 Subject: Transforms transmigrofied --- indra/newview/gltf/llgltfloader.cpp | 75 ++++++++++++++++--------------------- indra/newview/gltf/llgltfloader.h | 2 - 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3f5dbb3f87..3f58f512e5 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -118,7 +118,6 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } - mTransform.setIdentity(); mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); @@ -141,9 +140,10 @@ bool LLGLTFLoader::parseMeshes() LLVolumeParams volume_params; volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + mTransform.setIdentity(); for (auto node : mGLTFAsset.mNodes) { - LLMatrix4 transform; + LLMatrix4 transformation; material_map mats; auto meshidx = node.mMesh; @@ -156,30 +156,42 @@ bool LLGLTFLoader::parseMeshes() if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { mModelList.push_back(pModel); - LLMatrix4 mesh_translation; - mesh_translation.setTranslation(LLVector3(node.mTranslation)); - mesh_translation *= mTransform; - mTransform = mesh_translation; - mTransform.condition(); + LLMatrix4 saved_transform = mTransform; - LLMatrix4 mesh_rotation; - mesh_rotation.initRotation(LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3])); - mesh_rotation *= mTransform; - mTransform = mesh_rotation; + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); + mTransform *= gltf_transform; mTransform.condition(); - LLMatrix4 mesh_scale; - mesh_scale.initScale(LLVector3(glm::abs(node.mScale))); - mesh_scale *= mTransform; - mTransform = mesh_scale; - mTransform.condition(); + transformation = mTransform; + // adjust the transformation to compensate for mesh normalization + LLVector3 mesh_scale_vector; + LLVector3 mesh_translation_vector; + pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); - LLVector3 mesh_scale_vector = LLVector3(node.mScale); - LLVector3 mesh_translation_vector = LLVector3(node.mTranslation); + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transformation; + transformation = mesh_translation; - transform = mTransform; + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transformation; + transformation = mesh_scale; + + if (transformation.determinant() < 0) + { // negative scales are not supported + LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + << pModel->mLabel << LL_ENDL; + LLSD args; + args["Message"] = "NegativeScaleNormTrans"; + args["LABEL"] = pModel->mLabel; + mWarningsArray.append(args); + + } - mScene[transform].push_back(LLModelInstance(pModel, pModel->mLabel, transform, mats)); + mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); + stretch_extents(pModel, transformation); + mTransform = saved_transform; } else { @@ -268,29 +280,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & return true; } -void LLGLTFLoader::processPrimitive(const LL::GLTF::Primitive& primitive, const LL::GLTF::Node& node) -{ - LLMatrix4 translation; - translation.setTranslation(LLVector3(node.mTranslation)); - translation *= mTransform; - mTransform = translation; - mTransform.condition(); - - LLMatrix4 rotation; - rotation = LLMatrix4(LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3])); - rotation *= mTransform; - mTransform = rotation; - mTransform.condition(); - - LLMatrix4 scale; - scale.initScale(LLVector3(glm::abs(node.mScale))); - scale *= mTransform; - mTransform = scale; - mTransform.condition(); - - if (primitive.mPositions.size()) { - } -} /* LLModel::EModelStatus loadFaceFromGLTFModel(LLModel* pModel, const LL::GLTF::Mesh& mesh, material_map& mats, LLSD& log_msg) { diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 32275d168d..cfb545be6f 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -168,8 +168,6 @@ private: bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map& mats); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); - void processPrimitive(const LL::GLTF::Primitive& primitive, const LL::GLTF::Node& node); - // bool mPreprocessGLTF; /* Below inherited from dae loader - unknown if/how useful here -- cgit v1.2.3 From dd74b361e35a4e2516fee0b16ca0acf00da58547 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Sun, 4 May 2025 23:40:10 -0400 Subject: Fix import rotation and UVs --- indra/llprimitive/llmodelloader.h | 1 + indra/newview/gltf/llgltfloader.cpp | 241 +++++------------------------------- indra/newview/gltf/llgltfloader.h | 2 +- 3 files changed, 35 insertions(+), 209 deletions(-) diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 530e61e2b8..2dd0058ba6 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -111,6 +111,7 @@ public: bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info) model_list mModelList; + // The scene is pretty much what ends up getting loaded for upload. Basically assign things to this guy if you want something uploaded. scene mScene; typedef std::queue > model_queue; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3f58f512e5..3c723c7acd 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -153,15 +153,20 @@ bool LLGLTFLoader::parseMeshes() { LLModel* pModel = new LLModel(volume_params, 0.f); auto mesh = mGLTFAsset.mMeshes[meshidx]; - if (populateModelFromMesh(pModel, mesh, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) + if (populateModelFromMesh(pModel, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { mModelList.push_back(pModel); LLMatrix4 saved_transform = mTransform; - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); + mTransform *= gltf_transform; mTransform.condition(); + // GLTF is +Y up, SL is +Z up + LLMatrix4 rotation; + rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); + mTransform *= rotation; + transformation = mTransform; // adjust the transformation to compensate for mesh normalization LLVector3 mesh_scale_vector; @@ -206,7 +211,7 @@ bool LLGLTFLoader::parseMeshes() return true; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map &mats) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& node, material_map& mats) { pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); @@ -222,10 +227,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 LLVolumeFace face; LLVolumeFace::VertexMapData::PointMap point_map; - + std::vector vertices; std::vector indices; - + LLImportMaterial impMat; LL::GLTF::Material* material = nullptr; @@ -240,7 +245,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & GLTFVertex vert; vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], prim.mTexCoords0[i][1]); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); vertices.push_back(vert); } @@ -250,12 +255,29 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & } std::vector faceVertices; - + glm::vec3 min = glm::vec3(0); + glm::vec3 max = glm::vec3(0); for (U32 i = 0; i < vertices.size(); i++) { LLVolumeFace::VertexData vert; - LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); - LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); + + if (vertices[i].position.x > max.x) + max.x = vertices[i].position.x; + if (vertices[i].position.y > max.y) + max.y = vertices[i].position.y; + if (vertices[i].position.z > max.z) + max.z = vertices[i].position.z; + + + if (vertices[i].position.x < min.x) + min.x = vertices[i].position.x; + if (vertices[i].position.y < min.y) + min.y = vertices[i].position.y; + if (vertices[i].position.z < min.z) + min.z = vertices[i].position.z; + + LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); + LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); vert.setPosition(position); vert.setNormal(normal); vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); @@ -263,6 +285,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & } face.fillFromLegacyData(faceVertices, indices); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); pModel->getVolumeFaces().push_back(face); pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); @@ -280,205 +304,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh & return true; } - /* -LLModel::EModelStatus loadFaceFromGLTFModel(LLModel* pModel, const LL::GLTF::Mesh& mesh, material_map& mats, LLSD& log_msg) -{ - LLVolumeFace face; - std::vector verts; - std::vector indices; - - S32 pos_offset = -1; - S32 tc_offset = -1; - S32 norm_offset = -1; - - auto pos_source = mesh.mPrimitives[0].mPositions; - auto tc_source = mesh.mPrimitives[0].mNormals; - auto norm_source = mesh.mPrimitives[0].mTexCoords0; - - S32 idx_stride = 0; - - if (pos_source.size() > USHRT_MAX) - { - LL_WARNS() << "Unable to process mesh due to 16-bit index limits; invalid model; invalid model." << LL_ENDL; - LLSD args; - args["Message"] = "ParsingErrorBadElement"; - log_msg.append(args); - return LLModel::BAD_ELEMENT; - - } - - std::vector idx = mesh.mPrimitives[0].mIndexArray; - - domListOfFloats dummy; - domListOfFloats& v = pos_source ? pos_source->getFloat_array()->getValue() : dummy; - domListOfFloats& tc = tc_source ? tc_source->getFloat_array()->getValue() : dummy; - domListOfFloats& n = norm_source ? norm_source->getFloat_array()->getValue() : dummy; - - if (pos_source.size() == 0) - { - return LLModel::BAD_ELEMENT; - } - - // VFExtents change - face.mExtents[0].set(pos_source[0][0], pos_source[0][1], pos_source[0][2]); - face.mExtents[1].set(pos_source[0][0], pos_source[0][1], pos_source[0][2]); - - LLVolumeFace::VertexMapData::PointMap point_map; - - if (idx_stride <= 0 || (pos_source && pos_offset >= idx_stride) || (tc_source && tc_offset >= idx_stride) || - (norm_source && norm_offset >= idx_stride)) - { - // Looks like these offsets should fit inside idx_stride - // Might be good idea to also check idx.getCount()%idx_stride != 0 - LL_WARNS() << "Invalid pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL; - return LLModel::BAD_ELEMENT; - } - - for (U32 i = 0; i < idx.getCount(); i += idx_stride) - { - LLVolumeFace::VertexData cv; - if (pos_source) - { - cv.setPosition( - LLVector4a((F32)v[idx[i + pos_offset] * 3 + 0], (F32)v[idx[i + pos_offset] * 3 + 1], (F32)v[idx[i + pos_offset] * 3 + 2])); - } - - if (tc_source) - { - cv.mTexCoord.setVec((F32)tc[idx[i + tc_offset] * 2 + 0], (F32)tc[idx[i + tc_offset] * 2 + 1]); - } - - if (norm_source) - { - cv.setNormal(LLVector4a((F32)n[idx[i + norm_offset] * 3 + 0], - (F32)n[idx[i + norm_offset] * 3 + 1], - (F32)n[idx[i + norm_offset] * 3 + 2])); - } - - bool found = false; - - LLVolumeFace::VertexMapData::PointMap::iterator point_iter; - point_iter = point_map.find(LLVector3(cv.getPosition().getF32ptr())); - - if (point_iter != point_map.end()) - { - for (U32 j = 0; j < point_iter->second.size(); ++j) - { - // We have a matching loc - // - if ((point_iter->second)[j] == cv) - { - U16 shared_index = (point_iter->second)[j].mIndex; - - // Don't share verts within the same tri, degenerate - // - U32 indx_size = static_cast(indices.size()); - U32 verts_new_tri = indx_size % 3; - if ((verts_new_tri < 1 || indices[indx_size - 1] != shared_index) && - (verts_new_tri < 2 || indices[indx_size - 2] != shared_index)) - { - found = true; - indices.push_back(shared_index); - } - break; - } - } - } - - if (!found) - { - // VFExtents change - update_min_max(face.mExtents[0], face.mExtents[1], cv.getPosition()); - verts.push_back(cv); - if (verts.size() >= 65535) - { - // llerrs << "Attempted to write model exceeding 16-bit index buffer limitation." << LL_ENDL; - return LLModel::VERTEX_NUMBER_OVERFLOW; - } - U16 index = (U16)(verts.size() - 1); - indices.push_back(index); - - LLVolumeFace::VertexMapData d; - d.setPosition(cv.getPosition()); - d.mTexCoord = cv.mTexCoord; - d.setNormal(cv.getNormal()); - d.mIndex = index; - if (point_iter != point_map.end()) - { - point_iter->second.push_back(d); - } - else - { - point_map[LLVector3(d.getPosition().getF32ptr())].push_back(d); - } - } - - if (indices.size() % 3 == 0 && verts.size() >= 65532) - { - std::string material; - - if (tri->getMaterial()) - { - material = std::string(tri->getMaterial()); - } - - materials.push_back(material); - face_list.push_back(face); - face_list.rbegin()->fillFromLegacyData(verts, indices); - LLVolumeFace& new_face = *face_list.rbegin(); - if (!norm_source) - { - // ll_aligned_free_16(new_face.mNormals); - new_face.mNormals = NULL; - } - - if (!tc_source) - { - // ll_aligned_free_16(new_face.mTexCoords); - new_face.mTexCoords = NULL; - } - - face = LLVolumeFace(); - // VFExtents change - face.mExtents[0].set((F32)v[0], (F32)v[1], (F32)v[2]); - face.mExtents[1].set((F32)v[0], (F32)v[1], (F32)v[2]); - - verts.clear(); - indices.clear(); - point_map.clear(); - } - } - - if (!verts.empty()) - { - std::string material; - - if (tri->getMaterial()) - { - material = std::string(tri->getMaterial()); - } - - materials.push_back(material); - face_list.push_back(face); - - face_list.rbegin()->fillFromLegacyData(verts, indices); - LLVolumeFace& new_face = *face_list.rbegin(); - if (!norm_source) - { - // ll_aligned_free_16(new_face.mNormals); - new_face.mNormals = NULL; - } - - if (!tc_source) - { - // ll_aligned_free_16(new_face.mTexCoords); - new_face.mTexCoords = NULL; - } - } - - return LLModel::NO_ERRORS; -} -*/ bool LLGLTFLoader::parseMaterials() { return true; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index cfb545be6f..044cc262d7 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -165,7 +165,7 @@ private: void uploadMeshes(); bool parseMaterials(); void uploadMaterials(); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, material_map& mats); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); // bool mPreprocessGLTF; -- cgit v1.2.3 From 4dedd13e61c6ba287c9145d232b6adec6f4bb7e3 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 5 May 2025 00:18:12 -0400 Subject: Support bulk material uploads. --- indra/newview/llfloatermodelpreview.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 8332a430e6..96f9ddbf8a 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -64,6 +64,7 @@ #include "llcallbacklist.h" #include "llviewertexteditor.h" #include "llviewernetwork.h" +#include "llmaterialeditor.h" //static @@ -113,6 +114,11 @@ void LLMeshFilePicker::notify(const std::vector& filenames) if (filenames.size() > 0) { mMP->loadModel(filenames[0], mLOD); + + if (filenames[0].substr(filenames[0].length() - 4) == ".glb" || filenames[0].substr(filenames[0].length() - 5) == ".gltf") + { + LLMaterialEditor::loadMaterialFromFile(filenames[0], -1); + } } else { -- cgit v1.2.3 From 629e56d864726eaa0340be220be8c3e3f500ca32 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 5 May 2025 05:06:46 -0400 Subject: Make sure transformation matricies are actually setup. Start getting joints in. --- indra/newview/gltf/llgltfloader.cpp | 75 ++++++++++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 1 + 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3c723c7acd..26257dfc33 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -26,6 +26,7 @@ #include "llgltfloader.h" #include "meshoptimizer.h" +#include // Import & define single-header gltf import/export lib #define TINYGLTF_IMPLEMENTATION @@ -118,7 +119,6 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } - mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); @@ -141,6 +141,14 @@ bool LLGLTFLoader::parseMeshes() volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); mTransform.setIdentity(); + + // Populate the joints from skins first. + // There's not many skins - and you can pretty easily iterate through the nodes from that. + for (auto skin : mGLTFAsset.mSkins) + { + populateJointFromSkin(skin); + } + for (auto node : mGLTFAsset.mNodes) { LLMatrix4 transformation; @@ -157,10 +165,12 @@ bool LLGLTFLoader::parseMeshes() { mModelList.push_back(pModel); LLMatrix4 saved_transform = mTransform; - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); - mTransform *= gltf_transform; - mTransform.condition(); + // This will make sure the matrix is always valid from the node. + node.makeMatrixValid(); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); + mTransform = gltf_transform; // GLTF is +Y up, SL is +Z up LLMatrix4 rotation; @@ -168,6 +178,7 @@ bool LLGLTFLoader::parseMeshes() mTransform *= rotation; transformation = mTransform; + // adjust the transformation to compensate for mesh normalization LLVector3 mesh_scale_vector; LLVector3 mesh_translation_vector; @@ -182,7 +193,7 @@ bool LLGLTFLoader::parseMeshes() mesh_scale.initScale(mesh_scale_vector); mesh_scale *= transformation; transformation = mesh_scale; - + if (transformation.determinant() < 0) { // negative scales are not supported LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " @@ -210,12 +221,25 @@ bool LLGLTFLoader::parseMeshes() return true; } + +void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) +{ + for (auto joint : skin.mJoints) + { + auto jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + } +} -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& node, material_map& mats) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); + auto skinIdx = nodeno.mSkin; + auto prims = mesh.mPrimitives; for (auto prim : prims) { @@ -284,6 +308,45 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& faceVertices.push_back(vert); } + if (skinIdx >= 0) + { + auto skin = mGLTFAsset.mSkins[skinIdx]; + + for (int i = 0; i < prim.mJoints.size(); i++) + { + auto accessorIdx = prim.mAttributes["JOINTS_0"]; + LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; + if (accessorIdx >= 0) + { + auto accessor = mGLTFAsset.mAccessors[accessorIdx]; + componentType = accessor.mComponentType; + } + glm::u16vec4 joint; + if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) + { + auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); + joint = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); + } + else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) + { + joint = glm::unpackUint4x16(prim.mJoints[i]); + } + + // Look up the joint index in the skin + auto jointIndex0 = skin.mJoints[joint.x]; + auto jointIndex1 = skin.mJoints[joint.y]; + auto jointIndex2 = skin.mJoints[joint.z]; + auto jointIndex3 = skin.mJoints[joint.w]; + + // Get the nodes for these joints. + auto node0 = mGLTFAsset.mNodes[jointIndex0]; + auto node1 = mGLTFAsset.mNodes[jointIndex1]; + auto node2 = mGLTFAsset.mNodes[jointIndex2]; + auto node3 = mGLTFAsset.mNodes[jointIndex3]; + + } + } + face.fillFromLegacyData(faceVertices, indices); face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 044cc262d7..9d0560de63 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -166,6 +166,7 @@ private: bool parseMaterials(); void uploadMaterials(); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); + void populateJointFromSkin(const LL::GLTF::Skin& skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); // bool mPreprocessGLTF; -- cgit v1.2.3 From 47a5c7a41340a18d90a5a8724762ff32f9ac8afd Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Mon, 5 May 2025 16:12:26 -0400 Subject: Make sure we're pushing to the joints name set as well. --- indra/llprimitive/llmodelloader.h | 5 +++++ indra/newview/gltf/llgltfloader.cpp | 1 + 2 files changed, 6 insertions(+) diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 2dd0058ba6..73ec0ed1f4 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -120,9 +120,14 @@ public: model_queue mPhysicsQ; //map of avatar joints as named in COLLADA assets to internal joint names + // Do not use this for anything other than looking up the name of a joint. This is populated elsewhere. JointMap mJointMap; + + // The joint list is what you want to use to actually setup the specific joint transformations. JointTransformMap& mJointList; JointNameSet& mJointsFromNode; + + U32 mMaxJointsPerMesh; LLModelLoader( diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 26257dfc33..7f3f158fd7 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -230,6 +230,7 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) jointNode.makeMatrixValid(); mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + mJointsFromNode.push_front(jointNode.mName); } } -- cgit v1.2.3 From b3d1a1cb1600ca34994050a14cdfe9e474e57d43 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 5 May 2025 20:21:45 +0300 Subject: GLTF import texture upload --- indra/newview/gltf/llgltfloader.cpp | 573 +++++++++++++++++++++++++++++------- 1 file changed, 466 insertions(+), 107 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7f3f158fd7..15ed5f40ed 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -122,10 +122,8 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); - /* mMaterialsLoaded = parseMaterials(); if (mMaterialsLoaded) uploadMaterials(); - */ setLoadState(DONE); @@ -178,7 +176,7 @@ bool LLGLTFLoader::parseMeshes() mTransform *= rotation; transformation = mTransform; - + // adjust the transformation to compensate for mesh normalization LLVector3 mesh_scale_vector; LLVector3 mesh_translation_vector; @@ -193,7 +191,7 @@ bool LLGLTFLoader::parseMeshes() mesh_scale.initScale(mesh_scale_vector); mesh_scale *= transformation; transformation = mesh_scale; - + if (transformation.determinant() < 0) { // negative scales are not supported LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " @@ -233,7 +231,7 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) mJointsFromNode.push_front(jointNode.mName); } } - + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { pModel->mLabel = mesh.mName; @@ -252,25 +250,98 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 LLVolumeFace face; LLVolumeFace::VertexMapData::PointMap point_map; - + std::vector vertices; std::vector indices; - - LLImportMaterial impMat; - - LL::GLTF::Material* material = nullptr; - if (prim.mMaterial >= 0) - material = &mGLTFAsset.mMaterials[prim.mMaterial]; + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color - impMat.mDiffuseColor = LLColor4::white; + // Process material if available + if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + + // Set diffuse color from base color factor + impMat.mDiffuseColor = LLColor4( + material->mPbrMetallicRoughness.mBaseColorFactor[0], + material->mPbrMetallicRoughness.mBaseColorFactor[1], + material->mPbrMetallicRoughness.mBaseColorFactor[2], + material->mPbrMetallicRoughness.mBaseColorFactor[3] + ); + + // Process base color texture if it exists + if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) + { + S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; + if (texIndex < mGLTFAsset.mTextures.size()) + { + S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; + if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) + { + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + + // Use URI as texture file name + if (!image.mUri.empty()) + { + // URI might be a remote URL or a local path + std::string filename = image.mUri; + + // Extract just the filename from the URI + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + { + filename = filename.substr(pos + 1); + } + + // Store the texture filename + impMat.mDiffuseMapFilename = filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; + + LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename << LL_ENDL; + + // If the image has a texture loaded already, use it + if (image.mTexture.notNull()) + { + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; + } + else + { + // Let the model preview know we need to load this texture + mNumOfFetchingTextures++; + LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL; + } + } + else if (image.mTexture.notNull()) + { + // No URI but we have a texture, use it directly + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + } + else if (image.mBufferView >= 0) + { + // For embedded textures (no URI but has buffer data) + // Create a pseudo filename for the embedded texture + std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; + impMat.mDiffuseMapFilename = pseudo_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; + + // Mark for loading + mNumOfFetchingTextures++; + LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + } + } + } + } + } for (U32 i = 0; i < prim.getVertexCount(); i++) { GLTFVertex vert; vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); - vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); vertices.push_back(vert); } @@ -286,19 +357,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LLVolumeFace::VertexData vert; - if (vertices[i].position.x > max.x) + if (i == 0 || vertices[i].position.x > max.x) max.x = vertices[i].position.x; - if (vertices[i].position.y > max.y) + if (i == 0 || vertices[i].position.y > max.y) max.y = vertices[i].position.y; - if (vertices[i].position.z > max.z) + if (i == 0 || vertices[i].position.z > max.z) max.z = vertices[i].position.z; - - if (vertices[i].position.x < min.x) + if (i == 0 || vertices[i].position.x < min.x) min.x = vertices[i].position.x; - if (vertices[i].position.y < min.y) + if (i == 0 || vertices[i].position.y < min.y) min.y = vertices[i].position.y; - if (vertices[i].position.z < min.z) + if (i == 0 || vertices[i].position.z < min.z) min.z = vertices[i].position.z; LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); @@ -353,8 +423,26 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); pModel->getVolumeFaces().push_back(face); - pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); - mats["mat" + std::to_string(prim.mMaterial)] = impMat; + + // Create a unique material name for this primitive + std::string materialName; + if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + materialName = material->mName; + + if (materialName.empty()) + { + materialName = "mat" + std::to_string(prim.mMaterial); + } + } + else + { + materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + } + + pModel->getMaterialList().push_back(materialName); + mats[materialName] = impMat; } else { LL_INFOS() << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; @@ -370,56 +458,70 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& bool LLGLTFLoader::parseMaterials() { - return true; - /* if (!mGltfLoaded) return false; // fill local texture data structures mSamplers.clear(); - for (auto in_sampler : mGltfModel.samplers) + for (auto& in_sampler : mGLTFAsset.mSamplers) { gltf_sampler sampler; - sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR; - sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;; - sampler.wrapS = in_sampler.wrapS; - sampler.wrapT = in_sampler.wrapT; - sampler.name = in_sampler.name; // unused + sampler.magFilter = in_sampler.mMagFilter > 0 ? in_sampler.mMagFilter : GL_LINEAR; + sampler.minFilter = in_sampler.mMinFilter > 0 ? in_sampler.mMinFilter : GL_LINEAR; + sampler.wrapS = in_sampler.mWrapS; + sampler.wrapT = in_sampler.mWrapT; + sampler.name = in_sampler.mName; mSamplers.push_back(sampler); } mImages.clear(); - for (auto in_image : mGltfModel.images) + for (auto& in_image : mGLTFAsset.mImages) { gltf_image image; - image.numChannels = in_image.component; - image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes - image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc - image.size = static_cast(in_image.image.size()); - image.height = in_image.height; - image.width = in_image.width; - image.data = in_image.image.data(); - - if (in_image.as_is) + image.numChannels = in_image.mComponent; + image.bytesPerChannel = in_image.mBits >> 3; // Convert bits to bytes + image.pixelType = in_image.mPixelType; + image.size = 0; // We'll calculate this below if we have valid dimensions + + // Get dimensions from the texture if available + if (in_image.mTexture && in_image.mTexture->getDiscardLevel() >= 0) { - LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL; - return false; + image.height = in_image.mTexture->getHeight(); + image.width = in_image.mTexture->getWidth(); + // Since we don't have direct access to the raw data, we'll use the dimensions to calculate size + if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) + { + image.size = static_cast(image.height * image.width * image.numChannels * image.bytesPerChannel); + } + } + else + { + // Fallback to provided dimensions + image.height = in_image.mHeight; + image.width = in_image.mWidth; + if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) + { + image.size = static_cast(image.height * image.width * image.numChannels * image.bytesPerChannel); + } } - if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel) + // If we couldn't determine the size, skip this image + if (image.size == 0) { - LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL; - return false; + LL_WARNS("GLTF_IMPORT") << "Image size could not be determined" << LL_ENDL; + continue; } + // We don't have direct access to the image data, so data pointer remains nullptr + image.data = nullptr; mImages.push_back(image); } mTextures.clear(); - for (auto in_tex : mGltfModel.textures) + for (auto& in_tex : mGLTFAsset.mTextures) { gltf_texture tex; - tex.imageIdx = in_tex.source; - tex.samplerIdx = in_tex.sampler; + tex.imageIdx = in_tex.mSource; + tex.samplerIdx = in_tex.mSampler; tex.imageUuid.setNull(); if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) @@ -432,58 +534,96 @@ bool LLGLTFLoader::parseMaterials() } // parse each material - for (tinygltf::Material gltf_material : mGltfModel.materials) + mMaterials.clear(); + for (const auto& gltf_material : mGLTFAsset.mMaterials) { gltf_render_material mat; - mat.name = gltf_material.name; - - tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness; - mat.hasPBR = true; // Always true, for now - - mat.baseColor.set(pbr.baseColorFactor.data()); - mat.hasBaseTex = pbr.baseColorTexture.index >= 0; - mat.baseColorTexIdx = pbr.baseColorTexture.index; - mat.baseColorTexCoords = pbr.baseColorTexture.texCoord; - - mat.metalness = pbr.metallicFactor; - mat.roughness = pbr.roughnessFactor; - mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0; - mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index; - mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord; - - mat.normalScale = gltf_material.normalTexture.scale; - mat.hasNormalTex = gltf_material.normalTexture.index >= 0; - mat.normalTexIdx = gltf_material.normalTexture.index; - mat.normalTexCoords = gltf_material.normalTexture.texCoord; - - mat.occlusionScale = gltf_material.occlusionTexture.strength; - mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0; - mat.occlusionTexIdx = gltf_material.occlusionTexture.index; - mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord; - - mat.emissiveColor.set(gltf_material.emissiveFactor.data()); - mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0; - mat.emissiveTexIdx = gltf_material.emissiveTexture.index; - mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord; - - mat.alphaMode = gltf_material.alphaMode; - mat.alphaMask = gltf_material.alphaCutoff; - - if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) || - (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) || - (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) || - (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) || - (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size()))) + mat.name = gltf_material.mName; + + // PBR Metallic Roughness properties + mat.hasPBR = true; + + // Base color factor + mat.baseColor = LLColor4( + gltf_material.mPbrMetallicRoughness.mBaseColorFactor[0], + gltf_material.mPbrMetallicRoughness.mBaseColorFactor[1], + gltf_material.mPbrMetallicRoughness.mBaseColorFactor[2], + gltf_material.mPbrMetallicRoughness.mBaseColorFactor[3] + ); + + // Base color texture + mat.hasBaseTex = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0; + mat.baseColorTexIdx = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex; + mat.baseColorTexCoords = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mTexCoord; + + // Metalness and roughness + mat.metalness = gltf_material.mPbrMetallicRoughness.mMetallicFactor; + mat.roughness = gltf_material.mPbrMetallicRoughness.mRoughnessFactor; + + // Metallic-roughness texture + mat.hasMRTex = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex >= 0; + mat.metalRoughTexIdx = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex; + mat.metalRoughTexCoords = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTexCoord; + + // Normal texture + mat.normalScale = gltf_material.mNormalTexture.mScale; + mat.hasNormalTex = gltf_material.mNormalTexture.mIndex >= 0; + mat.normalTexIdx = gltf_material.mNormalTexture.mIndex; + mat.normalTexCoords = gltf_material.mNormalTexture.mTexCoord; + + // Occlusion texture + mat.occlusionScale = gltf_material.mOcclusionTexture.mStrength; + mat.hasOcclusionTex = gltf_material.mOcclusionTexture.mIndex >= 0; + mat.occlusionTexIdx = gltf_material.mOcclusionTexture.mIndex; + mat.occlusionTexCoords = gltf_material.mOcclusionTexture.mTexCoord; + + // Emissive texture and color + mat.emissiveColor = LLColor4( + gltf_material.mEmissiveFactor[0], + gltf_material.mEmissiveFactor[1], + gltf_material.mEmissiveFactor[2], + 1.0f + ); + mat.hasEmissiveTex = gltf_material.mEmissiveTexture.mIndex >= 0; + mat.emissiveTexIdx = gltf_material.mEmissiveTexture.mIndex; + mat.emissiveTexCoords = gltf_material.mEmissiveTexture.mTexCoord; + + // Convert AlphaMode enum to string + switch (gltf_material.mAlphaMode) + { + case LL::GLTF::Material::AlphaMode::OPAQUE: + mat.alphaMode = "OPAQUE"; + break; + case LL::GLTF::Material::AlphaMode::MASK: + mat.alphaMode = "MASK"; + break; + case LL::GLTF::Material::AlphaMode::BLEND: + mat.alphaMode = "BLEND"; + break; + default: + mat.alphaMode = "OPAQUE"; + break; + } + + mat.alphaMask = gltf_material.mAlphaCutoff; + + // Verify that all referenced textures are valid + if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) || + (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) || + (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) || + (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) || + (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size()))) { LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL; return false; } - if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV - (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || - (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || - (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || - (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) + // Verify texture coordinate sets are valid (mesh can have up to 3 sets of UV) + if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || + (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || + (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || + (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || + (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) { LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; return false; @@ -493,7 +633,6 @@ bool LLGLTFLoader::parseMaterials() } return true; - */ } // TODO: convert raw vertex buffers to UUIDs @@ -505,18 +644,34 @@ void LLGLTFLoader::uploadMeshes() // convert raw image buffers to texture UUIDs & assemble into a render material void LLGLTFLoader::uploadMaterials() { - for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple + LL_INFOS("GLTF_IMPORT") << "Uploading materials, count: " << mMaterials.size() << LL_ENDL; + + for (gltf_render_material& mat : mMaterials) { - if (mat.hasBaseTex) + LL_INFOS("GLTF_IMPORT") << "Processing material: " << mat.name << LL_ENDL; + + // Process base color texture + if (mat.hasBaseTex && mat.baseColorTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; if (gtex.imageUuid.isNull()) { + LL_INFOS("GLTF_IMPORT") << "Loading base color texture for material " << mat.name << LL_ENDL; gtex.imageUuid = imageBufferToTextureUUID(gtex); + + if (gtex.imageUuid.notNull()) + { + LL_INFOS("GLTF_IMPORT") << "Base color texture loaded, ID: " << gtex.imageUuid.asString() << LL_ENDL; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to load base color texture for material " << mat.name << LL_ENDL; + } } } - if (mat.hasMRTex) + // Process other textures similarly + if (mat.hasMRTex && mat.metalRoughTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; if (gtex.imageUuid.isNull()) @@ -525,7 +680,7 @@ void LLGLTFLoader::uploadMaterials() } } - if (mat.hasNormalTex) + if (mat.hasNormalTex && mat.normalTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.normalTexIdx]; if (gtex.imageUuid.isNull()) @@ -534,7 +689,7 @@ void LLGLTFLoader::uploadMaterials() } } - if (mat.hasOcclusionTex) + if (mat.hasOcclusionTex && mat.occlusionTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; if (gtex.imageUuid.isNull()) @@ -543,7 +698,7 @@ void LLGLTFLoader::uploadMaterials() } } - if (mat.hasEmissiveTex) + if (mat.hasEmissiveTex && mat.emissiveTexIdx < mTextures.size()) { gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; if (gtex.imageUuid.isNull()) @@ -552,18 +707,222 @@ void LLGLTFLoader::uploadMaterials() } } } + + // Update material map for all model instances to ensure textures are properly associated + // mScene is a std::map, not an array, so we need to iterate through it correctly + for (auto& scene_entry : mScene) + { + for (LLModelInstance& instance : scene_entry.second) + { + LLModel* model = instance.mModel; + + if (model) + { + for (size_t i = 0; i < model->getMaterialList().size(); ++i) + { + const std::string& matName = model->getMaterialList()[i]; + if (!matName.empty()) + { + // Ensure this material exists in the instance's material map + if (instance.mMaterial.find(matName) == instance.mMaterial.end()) + { + // Find material in our render materials + for (const auto& renderMat : mMaterials) + { + if (renderMat.name == matName) + { + // Create an import material from the render material + LLImportMaterial impMat; + impMat.mDiffuseColor = renderMat.baseColor; + + // Set diffuse texture if available + if (renderMat.hasBaseTex && renderMat.baseColorTexIdx < mTextures.size()) + { + const gltf_texture& gtex = mTextures[renderMat.baseColorTexIdx]; + if (!gtex.imageUuid.isNull()) + { + impMat.setDiffuseMap(gtex.imageUuid); + LL_INFOS("GLTF_IMPORT") << "Setting texture " << gtex.imageUuid.asString() << " for material " << matName << LL_ENDL; + } + } + + // Add material to instance's material map + instance.mMaterial[matName] = impMat; + LL_INFOS("GLTF_IMPORT") << "Added material " << matName << " to instance" << LL_ENDL; + break; + } + } + } + } + } + } + } + } } LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) { - //gltf_image& image = mImages[tex.imageIdx]; - //gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) + { + LL_WARNS("GLTF_IMPORT") << "Invalid texture indices in imageBufferToTextureUUID" << LL_ENDL; + return LLUUID::null; + } - // fill an LLSD container with image+sampler data + gltf_image& image = mImages[tex.imageIdx]; + gltf_sampler& sampler = mSamplers[tex.samplerIdx]; - // upload texture + S32 sourceIndex = tex.imageIdx; + if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) + { + LL_WARNS("GLTF_IMPORT") << "Invalid image index: " << sourceIndex << LL_ENDL; + return LLUUID::null; + } + + LL::GLTF::Image& source_image = mGLTFAsset.mImages[sourceIndex]; + + // If the image already has a texture loaded, use it + if (source_image.mTexture.notNull()) + { + LL_INFOS("GLTF_IMPORT") << "Using already loaded texture ID: " << source_image.mTexture->getID().asString() << LL_ENDL; + return source_image.mTexture->getID(); + } + + // Create an import material to pass to the texture load function + LLImportMaterial material; + + // Try to get the texture filename from the URI + if (!source_image.mUri.empty()) + { + std::string filename = source_image.mUri; + + // Extract just the filename from the URI + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + { + filename = filename.substr(pos + 1); + } + + material.mDiffuseMapFilename = filename; + material.mDiffuseMapLabel = filename; + } + else if (source_image.mBufferView >= 0) + { + // For embedded textures, create a pseudo-filename + std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; + material.mDiffuseMapFilename = pseudo_filename; + material.mDiffuseMapLabel = pseudo_filename; + } + else + { + LL_WARNS("GLTF_IMPORT") << "No URI or buffer data for image" << LL_ENDL; + return LLUUID::null; + } + + // Create LLSD container with image and sampler data for texture upload + LLSD texture_data = LLSD::emptyMap(); + + // Image data + texture_data["width"] = LLSD::Integer(image.width); + texture_data["height"] = LLSD::Integer(image.height); + texture_data["components"] = LLSD::Integer(image.numChannels); + texture_data["bytes_per_component"] = LLSD::Integer(image.bytesPerChannel); + texture_data["pixel_type"] = LLSD::Integer(image.pixelType); + + // Sampler data + texture_data["min_filter"] = LLSD::Integer(sampler.minFilter); + texture_data["mag_filter"] = LLSD::Integer(sampler.magFilter); + texture_data["wrap_s"] = LLSD::Integer(sampler.wrapS); + texture_data["wrap_t"] = LLSD::Integer(sampler.wrapT); + + // Add URI for reference + if (!source_image.mUri.empty()) + { + texture_data["uri"] = source_image.mUri; + } + + // Check if we have a buffer view for embedded data + if (source_image.mBufferView >= 0) + { + texture_data["has_embedded_data"] = LLSD::Boolean(true); + texture_data["buffer_view"] = LLSD::Integer(source_image.mBufferView); + + // Extract embedded data for texture loading + if (source_image.mBufferView < mGLTFAsset.mBufferViews.size()) + { + const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[source_image.mBufferView]; + if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) + { + const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) + { + // Add embedded data reference to texture_data + texture_data["buffer_index"] = LLSD::Integer(buffer_view.mBuffer); + texture_data["byte_offset"] = LLSD::Integer(buffer_view.mByteOffset); + texture_data["byte_length"] = LLSD::Integer(buffer_view.mByteLength); + + LL_INFOS("GLTF_IMPORT") << "Found embedded texture data: offset=" << buffer_view.mByteOffset + << " length=" << buffer_view.mByteLength << LL_ENDL; + } + } + } + } + + // Store the texture metadata in the binding field + std::ostringstream ostr; + LLSDSerialize::toXML(texture_data, ostr); + material.mBinding = ostr.str(); + + LL_INFOS("GLTF_IMPORT") << "Loading texture: " << material.mDiffuseMapFilename << LL_ENDL; - // retrieve UUID + // Flag to track if texture was loaded immediately + bool texture_loaded = false; + + // Call texture loading function with our import material + if (mTextureLoadFunc) + { + // Increment textures to fetch counter BEFORE calling load function + mNumOfFetchingTextures++; + + U32 result = mTextureLoadFunc(material, mOpaqueData); + + // If result is 0, texture is being loaded asynchronously + // If result is >0, texture was loaded immediately + if (result > 0) + { + // Texture was loaded immediately, so decrement counter + mNumOfFetchingTextures--; + texture_loaded = true; + + if (material.getDiffuseMap().notNull()) + { + LL_INFOS("GLTF_IMPORT") << "Texture loaded successfully, ID: " << material.getDiffuseMap().asString() << LL_ENDL; + + // Store the texture in the source image for future reference + if (source_image.mTexture.isNull()) + { + // Create and store a texture object using the UUID + source_image.mTexture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap()); + } + + return material.getDiffuseMap(); + } + } + else if (result == 0) + { + LL_INFOS("GLTF_IMPORT") << "Texture loading queued asynchronously for " << material.mDiffuseMapFilename << LL_ENDL; + } + else // result < 0, indicating error + { + // Texture loading failed, decrement counter + mNumOfFetchingTextures--; + LL_WARNS("GLTF_IMPORT") << "Texture loading failed for " << material.mDiffuseMapFilename << LL_ENDL; + } + } + else + { + LL_WARNS("GLTF_IMPORT") << "No texture loading function available" << LL_ENDL; + } return LLUUID::null; } + -- cgit v1.2.3 From 5e8f871aea8f5a4393ab8f9080c227ea1bf46c21 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 6 May 2025 18:13:24 +0300 Subject: Apply 1/100th scale to the vertex positions --- indra/newview/gltf/llgltfloader.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 15ed5f40ed..08d396d1e2 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -338,8 +338,16 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (U32 i = 0; i < prim.getVertexCount(); i++) { + // Apply scaling directly to the vertex positions as they're read from the file + const float DIRECT_SCALE = 0.01f; // 1/100th scale + GLTFVertex vert; - vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); + vert.position = glm::vec3( + prim.mPositions[i][0] * DIRECT_SCALE, + prim.mPositions[i][1] * DIRECT_SCALE, + prim.mPositions[i][2] * DIRECT_SCALE + ); + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); vertices.push_back(vert); -- cgit v1.2.3 From 2efe514f14a0f0b405599301a14413edfce873ee Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 14 May 2025 10:33:01 -0400 Subject: Make pulling weights per vertex. --- indra/newview/gltf/llgltfloader.cpp | 88 +++++++++++++++++++++++-------------- indra/newview/gltf/llgltfloader.h | 2 + 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7f3f158fd7..2db803ef3e 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -271,6 +271,32 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); vert.uv0 = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); + + if (skinIdx >= 0) + { + auto accessorIdx = prim.mAttributes["JOINTS_0"]; + LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; + if (accessorIdx >= 0) + { + auto accessor = mGLTFAsset.mAccessors[accessorIdx]; + componentType = accessor.mComponentType; + } + + // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. + // Detect and unpack accordingly. + if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) + { + auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); + vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); + } + else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) + { + vert.joints = glm::unpackUint4x16(prim.mJoints[i]); + } + + vert.weights = glm::vec4(prim.mWeights[i]); + } + vertices.push_back(vert); } @@ -282,6 +308,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::vector faceVertices; glm::vec3 min = glm::vec3(0); glm::vec3 max = glm::vec3(0); + for (U32 i = 0; i < vertices.size(); i++) { LLVolumeFace::VertexData vert; @@ -307,45 +334,42 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.setNormal(normal); vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); faceVertices.push_back(vert); - } - if (skinIdx >= 0) - { - auto skin = mGLTFAsset.mSkins[skinIdx]; + + // create list of weights that influence this vertex + LLModel::weight_list weight_list; - for (int i = 0; i < prim.mJoints.size(); i++) - { - auto accessorIdx = prim.mAttributes["JOINTS_0"]; - LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; - if (accessorIdx >= 0) - { - auto accessor = mGLTFAsset.mAccessors[accessorIdx]; - componentType = accessor.mComponentType; - } - glm::u16vec4 joint; - if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + + std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + + + std::vector wght; + F32 total = 0.f; + + for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) + { // take up to 4 most significant weights + // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. + if (weight_list[i].mWeight > 0.f) { - auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); - joint = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); + wght.push_back(weight_list[i]); + total += weight_list[i].mWeight; } - else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) + } + + F32 scale = 1.f / total; + if (scale != 1.f) + { // normalize weights + for (U32 i = 0; i < wght.size(); ++i) { - joint = glm::unpackUint4x16(prim.mJoints[i]); + wght[i].mWeight *= scale; } - - // Look up the joint index in the skin - auto jointIndex0 = skin.mJoints[joint.x]; - auto jointIndex1 = skin.mJoints[joint.y]; - auto jointIndex2 = skin.mJoints[joint.z]; - auto jointIndex3 = skin.mJoints[joint.w]; - - // Get the nodes for these joints. - auto node0 = mGLTFAsset.mNodes[jointIndex0]; - auto node1 = mGLTFAsset.mNodes[jointIndex1]; - auto node2 = mGLTFAsset.mNodes[jointIndex2]; - auto node3 = mGLTFAsset.mNodes[jointIndex3]; - } + + pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } face.fillFromLegacyData(faceVertices, indices); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 9d0560de63..519f2d8a07 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -144,6 +144,8 @@ class LLGLTFLoader : public LLModelLoader glm::vec3 position; glm::vec3 normal; glm::vec2 uv0; + glm::u16vec4 joints; + glm::vec4 weights; }; protected: -- cgit v1.2.3 From b572bf75968005388fab8ad0b3259d417c7f224c Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 14 May 2025 19:59:20 +0300 Subject: Improve mesh scaling --- indra/newview/gltf/llgltfloader.cpp | 148 ++++++++++++++++++++++++++++-------- indra/newview/gltf/llgltfloader.h | 3 +- 2 files changed, 120 insertions(+), 31 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 52147ad5c3..2064fa4dd9 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -147,6 +147,96 @@ bool LLGLTFLoader::parseMeshes() populateJointFromSkin(skin); } + /* Two-pass mesh processing approach: + * 1. First pass: Calculate global bounds across all meshes to determine proper scaling + * and centering for the entire model. This ensures consistent normalization. + * 2. Second pass: Process each mesh node with the calculated global scale factor and + * center offset, ensuring the entire model is properly proportioned and centered. + */ + + // First pass: Calculate global bounds across all meshes in the model + LLVector3 global_min_bounds(FLT_MAX, FLT_MAX, FLT_MAX); + LLVector3 global_max_bounds(-FLT_MAX, -FLT_MAX, -FLT_MAX); + bool has_geometry = false; + + // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up + LLMatrix4 coord_system_rotation; + coord_system_rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); + + // Gather bounds from all meshes + for (auto node : mGLTFAsset.mNodes) + { + auto meshidx = node.mMesh; + if (meshidx >= 0 && meshidx < mGLTFAsset.mMeshes.size()) + { + auto mesh = mGLTFAsset.mMeshes[meshidx]; + + // Make node matrix valid for correct transformation + node.makeMatrixValid(); + + // Apply coordinate system rotation to node transform + LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix)); + LLMatrix4 node_transform = coord_system_rotation; + node_transform *= node_matrix; + + // Examine all primitives in this mesh + for (auto prim : mesh.mPrimitives) + { + if (prim.getVertexCount() >= USHRT_MAX) + continue; + + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + // Transform vertex position by node transform with coordinate system rotation + LLVector4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); + LLVector4 transformed_pos = pos * node_transform; + + global_min_bounds.mV[VX] = llmin(global_min_bounds.mV[VX], transformed_pos.mV[VX]); + global_min_bounds.mV[VY] = llmin(global_min_bounds.mV[VY], transformed_pos.mV[VY]); + global_min_bounds.mV[VZ] = llmin(global_min_bounds.mV[VZ], transformed_pos.mV[VZ]); + + global_max_bounds.mV[VX] = llmax(global_max_bounds.mV[VX], transformed_pos.mV[VX]); + global_max_bounds.mV[VY] = llmax(global_max_bounds.mV[VY], transformed_pos.mV[VY]); + global_max_bounds.mV[VZ] = llmax(global_max_bounds.mV[VZ], transformed_pos.mV[VZ]); + + has_geometry = true; + } + } + } + } + + // Calculate model dimensions and center point for the entire model + F32 global_scale_factor = 1.0f; + LLVector3 global_center_offset(0.0f, 0.0f, 0.0f); + + if (has_geometry) + { + // Calculate bounding box center - this will be our new origin + LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f; + global_center_offset = -center; // Offset to move center to origin + + // Calculate diagonal length of the bounding box + LLVector3 dimensions = global_max_bounds - global_min_bounds; + F32 diagonal = dimensions.length(); + + // Always scale to the target size to ensure consistent bounding box + const F32 TARGET_SIZE = 1.0f; // Target diagonal size for models in meters + + if (diagonal > 0.0f) + { + // Calculate scale factor to normalize model size to TARGET_SIZE + global_scale_factor = TARGET_SIZE / diagonal; + + LL_INFOS("GLTF_IMPORT") << "Model dimensions: " << dimensions.mV[VX] << "x" + << dimensions.mV[VY] << "x" << dimensions.mV[VZ] + << ", diagonal: " << diagonal + << ", applying global scale factor: " << global_scale_factor + << ", global centering offset: (" << global_center_offset.mV[VX] << ", " + << global_center_offset.mV[VY] << ", " << global_center_offset.mV[VZ] << ")" << LL_ENDL; + } + } + + // Second pass: Process each node with the global scale and offset for (auto node : mGLTFAsset.mNodes) { LLMatrix4 transformation; @@ -159,7 +249,9 @@ bool LLGLTFLoader::parseMeshes() { LLModel* pModel = new LLModel(volume_params, 0.f); auto mesh = mGLTFAsset.mMeshes[meshidx]; - if (populateModelFromMesh(pModel, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) + if (populateModelFromMesh(pModel, mesh, node, mats, global_scale_factor, global_center_offset) && + (LLModel::NO_ERRORS == pModel->getStatus()) && + validate_model(pModel)) { mModelList.push_back(pModel); LLMatrix4 saved_transform = mTransform; @@ -171,9 +263,7 @@ bool LLGLTFLoader::parseMeshes() mTransform = gltf_transform; // GLTF is +Y up, SL is +Z up - LLMatrix4 rotation; - rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); - mTransform *= rotation; + mTransform *= coord_system_rotation; transformation = mTransform; @@ -200,7 +290,6 @@ bool LLGLTFLoader::parseMeshes() args["Message"] = "NegativeScaleNormTrans"; args["LABEL"] = pModel->mLabel; mWarningsArray.append(args); - } mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); @@ -220,19 +309,8 @@ bool LLGLTFLoader::parseMeshes() return true; } -void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) -{ - for (auto joint : skin.mJoints) - { - auto jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); - - mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); - mJointsFromNode.push_front(jointNode.mName); - } -} - -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, + const F32 scale_factor, const LLVector3& center_offset) { pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); @@ -336,20 +414,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } + // Apply the global scale and center offset to all vertices for (U32 i = 0; i < prim.getVertexCount(); i++) { - // Apply scaling directly to the vertex positions as they're read from the file - const float DIRECT_SCALE = 0.01f; // 1/100th scale - GLTFVertex vert; vert.position = glm::vec3( - prim.mPositions[i][0] * DIRECT_SCALE, - prim.mPositions[i][1] * DIRECT_SCALE, - prim.mPositions[i][2] * DIRECT_SCALE + (prim.mPositions[i][0] + center_offset.mV[VX]) * scale_factor, + (prim.mPositions[i][1] + center_offset.mV[VY]) * scale_factor, + (prim.mPositions[i][2] + center_offset.mV[VZ]) * scale_factor ); - vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); - vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); + + vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); if (skinIdx >= 0) { @@ -357,7 +433,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; if (accessorIdx >= 0) { - auto accessor = mGLTFAsset.mAccessors[accessorIdx]; + auto accessor = mGLTFAsset.mAccessors[accessorIdx]; componentType = accessor.mComponentType; } @@ -412,7 +488,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); faceVertices.push_back(vert); - + // create list of weights that influence this vertex LLModel::weight_list weight_list; @@ -423,7 +499,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - + std::vector wght; F32 total = 0.f; @@ -487,6 +563,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& return true; } +void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) +{ + for (auto joint : skin.mJoints) + { + auto jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + mJointsFromNode.push_front(jointNode.mName); + } +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 519f2d8a07..bfcdccac00 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -167,7 +167,8 @@ private: void uploadMeshes(); bool parseMaterials(); void uploadMaterials(); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, + const F32 scale_factor = 1.0f, const LLVector3& center_offset = LLVector3()); void populateJointFromSkin(const LL::GLTF::Skin& skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); -- cgit v1.2.3 From 3569cc1993dc03637b29fcd77fe5b01b6ea372fb Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 14 May 2025 22:04:55 +0300 Subject: Use correct model dimensions and bounding box rotation --- indra/newview/gltf/llgltfloader.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2064fa4dd9..c63459b172 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -174,9 +174,8 @@ bool LLGLTFLoader::parseMeshes() // Make node matrix valid for correct transformation node.makeMatrixValid(); - // Apply coordinate system rotation to node transform LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix)); - LLMatrix4 node_transform = coord_system_rotation; + LLMatrix4 node_transform; node_transform *= node_matrix; // Examine all primitives in this mesh @@ -187,7 +186,7 @@ bool LLGLTFLoader::parseMeshes() for (U32 i = 0; i < prim.getVertexCount(); i++) { - // Transform vertex position by node transform with coordinate system rotation + // Transform vertex position by node transform LLVector4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); LLVector4 transformed_pos = pos * node_transform; @@ -215,21 +214,23 @@ bool LLGLTFLoader::parseMeshes() LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f; global_center_offset = -center; // Offset to move center to origin - // Calculate diagonal length of the bounding box + // Calculate dimensions of the bounding box LLVector3 dimensions = global_max_bounds - global_min_bounds; - F32 diagonal = dimensions.length(); + + // Find the maximum dimension rather than the diagonal + F32 max_dimension = llmax(dimensions.mV[VX], llmax(dimensions.mV[VY], dimensions.mV[VZ])); // Always scale to the target size to ensure consistent bounding box - const F32 TARGET_SIZE = 1.0f; // Target diagonal size for models in meters + const F32 TARGET_SIZE = 1.0f; // Target size for maximum dimension in meters - if (diagonal > 0.0f) + if (max_dimension > 0.0f) { - // Calculate scale factor to normalize model size to TARGET_SIZE - global_scale_factor = TARGET_SIZE / diagonal; + // Calculate scale factor to normalize model's maximum dimension to TARGET_SIZE + global_scale_factor = TARGET_SIZE / max_dimension; LL_INFOS("GLTF_IMPORT") << "Model dimensions: " << dimensions.mV[VX] << "x" << dimensions.mV[VY] << "x" << dimensions.mV[VZ] - << ", diagonal: " << diagonal + << ", max dimension: " << max_dimension << ", applying global scale factor: " << global_scale_factor << ", global centering offset: (" << global_center_offset.mV[VX] << ", " << global_center_offset.mV[VY] << ", " << global_center_offset.mV[VZ] << ")" << LL_ENDL; -- cgit v1.2.3 From 47d95f1eae379f16ed0ae320c9bb1a6002186e32 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 16 May 2025 19:10:23 -0400 Subject: White space. --- indra/newview/gltf/llgltfloader.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 52147ad5c3..7570c33dcf 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -174,24 +174,19 @@ bool LLGLTFLoader::parseMeshes() LLMatrix4 rotation; rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); mTransform *= rotation; - transformation = mTransform; - // adjust the transformation to compensate for mesh normalization LLVector3 mesh_scale_vector; LLVector3 mesh_translation_vector; pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); - LLMatrix4 mesh_translation; mesh_translation.setTranslation(mesh_translation_vector); mesh_translation *= transformation; transformation = mesh_translation; - LLMatrix4 mesh_scale; mesh_scale.initScale(mesh_scale_vector); mesh_scale *= transformation; transformation = mesh_scale; - if (transformation.determinant() < 0) { // negative scales are not supported LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " @@ -390,21 +385,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (U32 i = 0; i < vertices.size(); i++) { LLVolumeFace::VertexData vert; - if (i == 0 || vertices[i].position.x > max.x) max.x = vertices[i].position.x; if (i == 0 || vertices[i].position.y > max.y) max.y = vertices[i].position.y; if (i == 0 || vertices[i].position.z > max.z) max.z = vertices[i].position.z; - if (i == 0 || vertices[i].position.x < min.x) min.x = vertices[i].position.x; if (i == 0 || vertices[i].position.y < min.y) min.y = vertices[i].position.y; if (i == 0 || vertices[i].position.z < min.z) min.z = vertices[i].position.z; - LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); vert.setPosition(position); @@ -422,8 +414,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - - std::vector wght; F32 total = 0.f; -- cgit v1.2.3 From a9c75d8136f9df6885e525ea8f2b383fe4a22593 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Fri, 16 May 2025 19:12:40 -0400 Subject: More white space. --- indra/newview/gltf/llgltfloader.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7570c33dcf..d9cee8b2ea 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -403,8 +403,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.setNormal(normal); vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); faceVertices.push_back(vert); - - // create list of weights that influence this vertex LLModel::weight_list weight_list; -- cgit v1.2.3 From 875a4180803aa6903bb13263a63e02b38552b742 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 15 May 2025 18:28:25 +0300 Subject: #4080 Import GLTF skin data --- indra/llprimitive/llmodelloader.cpp | 2 + indra/newview/gltf/llgltfloader.cpp | 85 ++++++++++++++++++++-- .../skins/default/xui/en/floater_model_preview.xml | 1 + 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 7facd53a72..8f86b90b69 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -150,6 +150,8 @@ void LLModelLoader::run() { mWarningsArray.clear(); doLoadModel(); + // todo: we are inside of a thread, push this into main thread worker, + // not into doOnIdleOneTime that laks tread safety doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this)); } diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 11c1b05ee5..8d109b610c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -142,7 +142,7 @@ bool LLGLTFLoader::parseMeshes() // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. - for (auto skin : mGLTFAsset.mSkins) + for (auto& skin : mGLTFAsset.mSkins) { populateJointFromSkin(skin); } @@ -164,7 +164,7 @@ bool LLGLTFLoader::parseMeshes() coord_system_rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); // Gather bounds from all meshes - for (auto node : mGLTFAsset.mNodes) + for (auto &node : mGLTFAsset.mNodes) { auto meshidx = node.mMesh; if (meshidx >= 0 && meshidx < mGLTFAsset.mMeshes.size()) @@ -238,7 +238,7 @@ bool LLGLTFLoader::parseMeshes() } // Second pass: Process each node with the global scale and offset - for (auto node : mGLTFAsset.mNodes) + for (auto &node : mGLTFAsset.mNodes) { LLMatrix4 transformation; material_map mats; @@ -281,7 +281,7 @@ bool LLGLTFLoader::parseMeshes() transformation = mesh_scale; if (transformation.determinant() < 0) { // negative scales are not supported - LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + LL_INFOS("GLTF") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " << pModel->mLabel << LL_ENDL; LLSD args; args["Message"] = "NegativeScaleNormTrans"; @@ -292,6 +292,60 @@ bool LLGLTFLoader::parseMeshes() mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); stretch_extents(pModel, transformation); mTransform = saved_transform; + + S32 skin_index = node.mSkin; + if (skin_index >= 0 && mGLTFAsset.mSkins.size() > skin_index) + { + LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skin_index]; + LLMeshSkinInfo& skin_info = pModel->mSkinInfo; + + size_t jointCnt = gltf_skin.mJoints.size(); + if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size()) + { + LL_INFOS("GLTF") << "Bind matrices count mismatch joints count" << LL_ENDL; + LLSD args; + args["Message"] = "InvBindCountMismatch"; + mWarningsArray.append(args); + } + + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + std::string legal_name(jointNode.mName); + if (mJointMap.find(legal_name) != mJointMap.end()) + { + legal_name = mJointMap[legal_name]; + } + skin_info.mJointNames.push_back(legal_name); + skin_info.mJointNums.push_back(-1); + + if (i < gltf_skin.mInverseBindMatricesData.size()) + { + // Process bind matrix + LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; + LLMatrix4 gltf_transform(glm::value_ptr(gltf_mat)); + skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); + + LL_DEBUGS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + + // Translate based of mJointList + gltf_transform.setTranslation(mJointList[legal_name].getTranslation()); + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(gltf_transform)); + } + } + + // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh + // into the coordinate space of the joints. + // In GLTF, this matrix is omitted, and it is assumed that this transform is either + // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. + LLMatrix4 bind_shape; + bind_shape.setIdentity(); + skin_info.mBindShapeMatrix.loadu(bind_shape); + } } else { @@ -560,10 +614,29 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) for (auto joint : skin.mJoints) { auto jointNode = mGLTFAsset.mNodes[joint]; + + std::string legal_name(jointNode.mName); + if (mJointMap.find(legal_name) != mJointMap.end()) + { + legal_name = mJointMap[legal_name]; + } + else + { + LL_INFOS("GLTF") << "Rigged to unrecognized joint name : " + << legal_name << LL_ENDL; + LLSD args; + args["Message"] = "UnrecognizedJoint"; + args["[NAME]"] = legal_name; + mWarningsArray.append(args); + } + jointNode.makeMatrixValid(); - mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); - mJointsFromNode.push_front(jointNode.mName); + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + mJointList[legal_name] = gltf_transform; + mJointsFromNode.push_front(legal_name); + + LL_DEBUGS("GLTF") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; } } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 90223fcda8..99c348c236 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -45,6 +45,7 @@ Rigged to unrecognized joint name [NAME] Skinning disabled due to [COUNT] unknown joints Model [MODEL_NAME] loaded + Bind matrices count mismatch joints count Texture coordinates data is not complete. Found NaN while loading position data from DAE-Model, invalid model. -- cgit v1.2.3 From a3386f8b3aa57281c526d543461620b82896fe8f Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 19 May 2025 22:30:37 +0300 Subject: #4102 Allow selecting gltf models on mac --- indra/newview/llfilepicker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 09a2206cd7..150b5481ca 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -671,6 +671,8 @@ std::unique_ptr> LLFilePicker::navOpenFilterProc(ELoadF case FFLOAD_HDRI: allowedv->push_back("exr"); case FFLOAD_MODEL: + allowedv->push_back("gltf"); + allowedv->push_back("glb"); case FFLOAD_COLLADA: allowedv->push_back("dae"); break; -- cgit v1.2.3 From a58adfff8f4af25196352b140bab35d209edf7b0 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 20 May 2025 23:59:06 +0300 Subject: #4105 Support .gltf and .glb files in lookupLODModelFiles --- indra/newview/llmodelpreview.cpp | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 7ccd299135..2d568ecb8b 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -3090,25 +3090,48 @@ void LLModelPreview::lookupLODModelFiles(S32 lod) S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS; std::string lod_filename = mLODFile[LLModel::LOD_HIGH]; - std::string ext = ".dae"; std::string lod_filename_lower(lod_filename); LLStringUtil::toLower(lod_filename_lower); - std::string::size_type i = lod_filename_lower.rfind(ext); - if (i != std::string::npos) + + // Check for each supported file extension + std::vector supported_exts = { ".dae", ".gltf", ".glb" }; + std::string found_ext; + std::string::size_type ext_pos = std::string::npos; + + for (const auto& ext : supported_exts) { - lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext); + std::string::size_type i = lod_filename_lower.rfind(ext); + if (i != std::string::npos) + { + ext_pos = i; + found_ext = ext; + break; + } } - if (gDirUtilp->fileExists(lod_filename)) + + if (ext_pos != std::string::npos) { - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - if (fmp) + // Replace extension with LOD suffix + original extension + std::string lod_file_to_check = lod_filename; + lod_file_to_check.replace(ext_pos, found_ext.size(), getLodSuffix(next_lod) + found_ext); + + if (gDirUtilp->fileExists(lod_file_to_check)) + { + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (fmp) + { + fmp->setCtrlLoadFromFile(next_lod); + } + loadModel(lod_file_to_check, next_lod); + } + else { - fmp->setCtrlLoadFromFile(next_lod); + lookupLODModelFiles(next_lod); } - loadModel(lod_filename, next_lod); } else { + // No recognized extension found, continue with next LOD lookupLODModelFiles(next_lod); } } -- cgit v1.2.3 From a9e9c03762f176e0f930d74ccfc96e3c04112b13 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 20 May 2025 20:10:14 +0300 Subject: #4080 Rigged mesh support #2 --- indra/newview/gltf/llgltfloader.cpp | 248 ++++++++++++++++++++++++------------ 1 file changed, 164 insertions(+), 84 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 8d109b610c..a5fb4108c9 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -208,7 +208,8 @@ bool LLGLTFLoader::parseMeshes() F32 global_scale_factor = 1.0f; LLVector3 global_center_offset(0.0f, 0.0f, 0.0f); - if (has_geometry) + if (has_geometry + && mJointList.empty()) // temporary disable offset and scaling for rigged meshes { // Calculate bounding box center - this will be our new origin LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f; @@ -292,60 +293,6 @@ bool LLGLTFLoader::parseMeshes() mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); stretch_extents(pModel, transformation); mTransform = saved_transform; - - S32 skin_index = node.mSkin; - if (skin_index >= 0 && mGLTFAsset.mSkins.size() > skin_index) - { - LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skin_index]; - LLMeshSkinInfo& skin_info = pModel->mSkinInfo; - - size_t jointCnt = gltf_skin.mJoints.size(); - if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size()) - { - LL_INFOS("GLTF") << "Bind matrices count mismatch joints count" << LL_ENDL; - LLSD args; - args["Message"] = "InvBindCountMismatch"; - mWarningsArray.append(args); - } - - for (size_t i = 0; i < jointCnt; ++i) - { - // Process joint name and idnex - S32 joint = gltf_skin.mJoints[i]; - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); - - std::string legal_name(jointNode.mName); - if (mJointMap.find(legal_name) != mJointMap.end()) - { - legal_name = mJointMap[legal_name]; - } - skin_info.mJointNames.push_back(legal_name); - skin_info.mJointNums.push_back(-1); - - if (i < gltf_skin.mInverseBindMatricesData.size()) - { - // Process bind matrix - LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - LLMatrix4 gltf_transform(glm::value_ptr(gltf_mat)); - skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); - - LL_DEBUGS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; - - // Translate based of mJointList - gltf_transform.setTranslation(mJointList[legal_name].getTranslation()); - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(gltf_transform)); - } - } - - // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh - // into the coordinate space of the joints. - // In GLTF, this matrix is omitted, and it is assumed that this transform is either - // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. - LLMatrix4 bind_shape; - bind_shape.setIdentity(); - skin_info.mBindShapeMatrix.loadu(bind_shape); - } } else { @@ -366,7 +313,33 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); - auto skinIdx = nodeno.mSkin; + S32 skinIdx = nodeno.mSkin; + + // Mark unsuported joints with '-1' so that they won't get added into weights + // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. + std::vector gltf_joint_index_use_count; + if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx) + { + LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; + + size_t jointCnt = gltf_skin.mJoints.size(); + gltf_joint_index_use_count.resize(jointCnt); + + S32 replacement_index = 0; + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + std::string legal_name(jointNode.mName); + if (mJointMap.find(legal_name) == mJointMap.end()) + { + gltf_joint_index_use_count[i] = -1; // mark as unsupported + } + } + } auto prims = mesh.mPrimitives; for (auto prim : prims) @@ -536,39 +509,70 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); faceVertices.push_back(vert); - // create list of weights that influence this vertex - LLModel::weight_list weight_list; - - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + if (skinIdx >= 0) + { + // create list of weights that influence this vertex + LLModel::weight_list weight_list; + + // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count) + // don't reindex them yet, more indexes will be removed + // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be + // 'empty' ones + if (gltf_joint_index_use_count[vertices[i].joints.x] >= 0 + && vertices[i].weights.x > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + gltf_joint_index_use_count[vertices[i].joints.x]++; + } + if (gltf_joint_index_use_count[vertices[i].joints.y] >= 0 + && vertices[i].weights.y > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + gltf_joint_index_use_count[vertices[i].joints.y]++; + } + if (gltf_joint_index_use_count[vertices[i].joints.z] >= 0 + && vertices[i].weights.z > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + gltf_joint_index_use_count[vertices[i].joints.z]++; + } + if (gltf_joint_index_use_count[vertices[i].joints.w] >= 0 + && vertices[i].weights.w > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + gltf_joint_index_use_count[vertices[i].joints.w]++; + } - std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - std::vector wght; - F32 total = 0.f; + std::vector wght; + F32 total = 0.f; - for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) - { // take up to 4 most significant weights - // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. - if (weight_list[i].mWeight > 0.f) + for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) { + // take up to 4 most significant weights + // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. wght.push_back(weight_list[i]); total += weight_list[i].mWeight; } - } - F32 scale = 1.f / total; - if (scale != 1.f) - { // normalize weights - for (U32 i = 0; i < wght.size(); ++i) + if (total != 0.f) + { + F32 scale = 1.f / total; + if (scale != 1.f) + { // normalize weights + for (U32 i = 0; i < wght.size(); ++i) + { + wght[i].mWeight *= scale; + } + } + } + + if (wght.size() > 0) { - wght[i].mWeight *= scale; + pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } } - - pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } face.fillFromLegacyData(faceVertices, indices); @@ -606,6 +610,85 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } + // Fill joint names, bind matrices and prepare to remap weight indices + if (skinIdx >= 0) + { + LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; + LLMeshSkinInfo& skin_info = pModel->mSkinInfo; + + size_t jointCnt = gltf_skin.mJoints.size(); + if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size()) + { + LL_INFOS("GLTF") << "Bind matrices count mismatch joints count" << LL_ENDL; + LLSD args; + args["Message"] = "InvBindCountMismatch"; + mWarningsArray.append(args); + } + + std::vector gltfindex_to_joitindex_map; + gltfindex_to_joitindex_map.resize(jointCnt); + + S32 replacement_index = 0; + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use_count[i] <= 0) + { + // Unused (0) or unsupported (-1) joint, drop it + continue; + } + LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + + std::string legal_name(jointNode.mName); + if (mJointMap.find(legal_name) != mJointMap.end()) + { + legal_name = mJointMap[legal_name]; + } + else + { + llassert(false); // should have been stopped by gltf_joint_index_use_count[i] == -1 + continue; + } + gltfindex_to_joitindex_map[i] = replacement_index++; + + skin_info.mJointNames.push_back(legal_name); + skin_info.mJointNums.push_back(-1); + + if (i < gltf_skin.mInverseBindMatricesData.size()) + { + // Process bind matrix + LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; + LLMatrix4 gltf_transform(glm::value_ptr(gltf_mat)); + skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); + + LL_INFOS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + + // Translate based of mJointList + gltf_transform.setTranslation(mJointList[legal_name].getTranslation()); // name is supposed to be in mJointList + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(gltf_transform)); + } + } + + // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh + // into the coordinate space of the joints. + // In GLTF, this matrix is omitted, and it is assumed that this transform is either + // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. + LLMatrix4 bind_shape; + bind_shape.setIdentity(); + skin_info.mBindShapeMatrix.loadu(bind_shape); + + // Remap indices for pModel->mSkinWeights + for (auto& weights : pModel->mSkinWeights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; + } + } + } + return true; } @@ -622,12 +705,9 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) } else { - LL_INFOS("GLTF") << "Rigged to unrecognized joint name : " - << legal_name << LL_ENDL; - LLSD args; - args["Message"] = "UnrecognizedJoint"; - args["[NAME]"] = legal_name; - mWarningsArray.append(args); + // ignore unrecognized joint + LL_DEBUGS("GLTF") << "Ignoring joing: " << legal_name << LL_ENDL; + continue; } jointNode.makeMatrixValid(); -- cgit v1.2.3 From 18182b137a6ad99c314b43c0cd388d73248a0e41 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 21 May 2025 20:09:15 -0400 Subject: Fix for crash when loading texutres on a GLTF mesh. --- indra/newview/gltf/asset.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index beccb02bd4..d4d935ecc0 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -957,12 +957,13 @@ bool Image::prep(Asset& asset) { // embedded in a buffer, load the texture from the buffer BufferView& bufferView = asset.mBufferViews[mBufferView]; Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; - - U8* data = buffer.mData.data() + bufferView.mByteOffset; - - mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); - - if (mTexture.isNull()) + if (mLoadIntoTexturePipe) + { + U8* data = buffer.mData.data() + bufferView.mByteOffset; + + mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); + } + else if (mTexture.isNull()) { LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -977,7 +978,7 @@ bool Image::prep(Asset& asset) std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri; LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file); - if (tracking_id.notNull()) + if (tracking_id.notNull() && mLoadIntoTexturePipe) { LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); mTexture = LLViewerTextureManager::getFetchedTexture(world_id); -- cgit v1.2.3 From 30b72ba4106a3d818666e9a6e8a0aad5fbc0c7cb Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 21 May 2025 20:43:45 -0400 Subject: Make sure we're guarding texture uploads to the GPU properly If we're not loading a texture, most of this should not be necessary. --- indra/newview/gltf/asset.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index d4d935ecc0..4e66bf2cbf 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -963,7 +963,7 @@ bool Image::prep(Asset& asset) mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); } - else if (mTexture.isNull()) + else if (mTexture.isNull() && mLoadIntoTexturePipe) { LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -983,7 +983,7 @@ bool Image::prep(Asset& asset) LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); mTexture = LLViewerTextureManager::getFetchedTexture(world_id); } - else + else if (mLoadIntoTexturePipe) { LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL; LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; @@ -998,7 +998,7 @@ bool Image::prep(Asset& asset) return false; } - if (!asset.mFilename.empty()) + if (!asset.mFilename.empty() && mLoadIntoTexturePipe) { // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW); mTexture->forceToSaveRawImage(0, F32_MAX); -- cgit v1.2.3 From 30aa14d0b3f4e87580982e8a8f936bad1283e21a Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 21 May 2025 21:00:06 -0400 Subject: Make loading the asset into VRAM optional. --- indra/newview/gltf/asset.cpp | 145 +++++++++++++++++++----------------- indra/newview/gltf/asset.h | 7 +- indra/newview/gltf/llgltfloader.cpp | 2 +- indra/newview/gltfscenemanager.cpp | 2 +- 4 files changed, 81 insertions(+), 75 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 4e66bf2cbf..55b275ecb4 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -516,7 +516,7 @@ bool Asset::prep() for (auto& image : mImages) { - if (!image.prep(*this)) + if (!image.prep(*this, mLoadIntoVRAM)) { return false; } @@ -545,101 +545,103 @@ bool Asset::prep() return false; } } + if (mLoadIntoVRAM) + { + // prepare vertex buffers - // prepare vertex buffers - - // material count is number of materials + 1 for default material - U32 mat_count = (U32) mMaterials.size() + 1; - - if (LLGLSLShader::sCurBoundShaderPtr == nullptr) - { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer - gDebugProgram.bind(); - } + // material count is number of materials + 1 for default material + U32 mat_count = (U32) mMaterials.size() + 1; - for (S32 double_sided = 0; double_sided < 2; ++double_sided) - { - RenderData& rd = mRenderData[double_sided]; - for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) - { - rd.mBatches[i].resize(mat_count); + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); } - // for each material - for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) + for (S32 double_sided = 0; double_sided < 2; ++double_sided) { - // for each shader variant - U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; - U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; - - S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; - if (ds_mat != double_sided) + RenderData& rd = mRenderData[double_sided]; + for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) { - continue; + rd.mBatches[i].resize(mat_count); } - for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) + // for each material + for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) { - U32 attribute_mask = 0; - // for each mesh - for (auto& mesh : mMeshes) + // for each shader variant + U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + + S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; + if (ds_mat != double_sided) { - // for each primitive - for (auto& primitive : mesh.mPrimitives) + continue; + } + + for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) + { + U32 attribute_mask = 0; + // for each mesh + for (auto& mesh : mMeshes) { - if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + // for each primitive + for (auto& primitive : mesh.mPrimitives) { - // accumulate vertex and index counts - primitive.mVertexOffset = vertex_count[variant]; - primitive.mIndexOffset = index_count[variant]; + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + { + // accumulate vertex and index counts + primitive.mVertexOffset = vertex_count[variant]; + primitive.mIndexOffset = index_count[variant]; - vertex_count[variant] += primitive.getVertexCount(); - index_count[variant] += primitive.getIndexCount(); + vertex_count[variant] += primitive.getVertexCount(); + index_count[variant] += primitive.getIndexCount(); - // all primitives of a given variant and material should all have the same attribute mask - llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); - attribute_mask |= primitive.mAttributeMask; + // all primitives of a given variant and material should all have the same attribute mask + llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); + attribute_mask |= primitive.mAttributeMask; + } } } - } - // allocate vertex buffer and pack it - if (vertex_count[variant] > 0) - { - U32 mat_idx = mat_id + 1; - #if 0 - LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); + // allocate vertex buffer and pack it + if (vertex_count[variant] > 0) + { + U32 mat_idx = mat_id + 1; + #if 0 + LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); - rd.mBatches[variant][mat_idx].mVertexBuffer = vb; - vb->allocateBuffer(vertex_count[variant], - index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used - vb->setBuffer(); + rd.mBatches[variant][mat_idx].mVertexBuffer = vb; + vb->allocateBuffer(vertex_count[variant], + index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used + vb->setBuffer(); - for (auto& mesh : mMeshes) - { - for (auto& primitive : mesh.mPrimitives) + for (auto& mesh : mMeshes) { - if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + for (auto& primitive : mesh.mPrimitives) { - primitive.upload(vb); + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + { + primitive.upload(vb); + } } } - } - vb->unmapBuffer(); + vb->unmapBuffer(); - vb->unbind(); - #endif + vb->unbind(); + #endif + } } } } - } - // sanity check that all primitives have a vertex buffer - for (auto& mesh : mMeshes) - { - for (auto& primitive : mesh.mPrimitives) + // sanity check that all primitives have a vertex buffer + for (auto& mesh : mMeshes) { - //llassert(primitive.mVertexBuffer.notNull()); + for (auto& primitive : mesh.mPrimitives) + { + //llassert(primitive.mVertexBuffer.notNull()); + } } } #if 0 @@ -678,9 +680,10 @@ Asset::Asset(const Value& src) *this = src; } -bool Asset::load(std::string_view filename) +bool Asset::load(std::string_view filename, bool loadIntoVRAM) { LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; + mLoadIntoVRAM = loadIntoVRAM; mFilename = filename; std::string ext = gDirUtilp->getExtension(mFilename); @@ -698,7 +701,7 @@ bool Asset::load(std::string_view filename) } else if (ext == "glb") { - return loadBinary(str); + return loadBinary(str, mLoadIntoVRAM); } else { @@ -715,8 +718,9 @@ bool Asset::load(std::string_view filename) return false; } -bool Asset::loadBinary(const std::string& data) +bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM) { + mLoadIntoVRAM = loadIntoVRAM; // load from binary gltf const U8* ptr = (const U8*)data.data(); const U8* end = ptr + data.size(); @@ -941,8 +945,9 @@ void Asset::eraseBufferView(S32 bufferView) LLViewerFetchedTexture* fetch_texture(const LLUUID& id); -bool Image::prep(Asset& asset) +bool Image::prep(Asset& asset, bool loadIntoVRAM) { + mLoadIntoTexturePipe = loadIntoVRAM; LLUUID id; if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull()) { // loaded from an asset, fetch the texture from the asset system diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index e70fffa986..3cec18268b 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -319,7 +319,7 @@ namespace LL // preserve only uri and name void clearData(Asset& asset); - bool prep(Asset& asset); + bool prep(Asset& asset, bool loadIntoVRAM); }; // Render Batch -- vertex buffer and list of primitives to render using @@ -394,6 +394,7 @@ namespace LL // UBO for storing material data U32 mMaterialsUBO = 0; + bool mLoadIntoVRAM = false; // prepare for first time use bool prep(); @@ -431,12 +432,12 @@ namespace LL // accepts .gltf and .glb files // Any existing data will be lost // returns result of prep() on success - bool load(std::string_view filename); + bool load(std::string_view filename, bool loadIntoVRAM); // load .glb contents from memory // data - binary contents of .glb file // returns result of prep() on success - bool loadBinary(const std::string& data); + bool loadBinary(const std::string& data, bool loadIntoVRAM); const Asset& operator=(const Value& src); void serialize(boost::json::object& dst) const; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index a5fb4108c9..22c7c5d13e 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -108,7 +108,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); - mGltfLoaded = mGLTFAsset.load(filename); + mGltfLoaded = mGLTFAsset.load(filename, false); if (!mGltfLoaded) { diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index 9faead9533..3cb5e9a0d7 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -317,7 +317,7 @@ void GLTFSceneManager::load(const std::string& filename) { std::shared_ptr asset = std::make_shared(); - if (asset->load(filename)) + if (asset->load(filename, true)) { gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions asset->updateTransforms(); -- cgit v1.2.3 From 3670cf64eb31d8e8f76db1006cf35a87fc3d5b54 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 21 May 2025 21:04:14 -0400 Subject: White space. --- indra/newview/gltf/asset.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 55b275ecb4..129661c195 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -965,7 +965,6 @@ bool Image::prep(Asset& asset, bool loadIntoVRAM) if (mLoadIntoTexturePipe) { U8* data = buffer.mData.data() + bufferView.mByteOffset; - mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); } else if (mTexture.isNull() && mLoadIntoTexturePipe) -- cgit v1.2.3 From fe84905b1762c821fbc0ef1db97b8df885e6cce1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 21 May 2025 21:53:59 +0300 Subject: #4128 Crash on mInvBindMatrix --- indra/newview/llskinningutil.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index cee43f3cff..43836a420b 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -135,6 +135,12 @@ void LLSkinningUtil::initSkinningMatrixPalette( initJointNums(const_cast(skin), avatar); + if (skin->mInvBindMatrix.size() < count ) + { + // faulty model? mInvBindMatrix.size() should have matched mJointNames.size() + return; + } + LLMatrix4a world[LL_CHARACTER_MAX_ANIMATED_JOINTS]; for (S32 j = 0; j < count; ++j) -- cgit v1.2.3 From 6a92f8777de3e5031509af7dca44d334881faf47 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 21 May 2025 22:06:51 +0300 Subject: Crash at LLVOVolume::isAnimatedObject --- indra/newview/llvovolume.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 63ff6bc79d..fb6aa7a326 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -3783,7 +3783,12 @@ bool LLVOVolume::canBeAnimatedObject() const bool LLVOVolume::isAnimatedObject() const { - LLVOVolume *root_vol = (LLVOVolume*)getRootEdit(); + LLViewerObject *root_obj = getRootEdit(); + if (root_obj->getPCode() != LL_PCODE_VOLUME) + { + return false; // at the moment only volumes can be animated + } + LLVOVolume* root_vol = (LLVOVolume*)root_obj; mIsAnimatedObject = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; return mIsAnimatedObject; } -- cgit v1.2.3 From 9d3a03e6da9604dc3d76ed0649c06c58beb22771 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 22 May 2025 14:58:13 +0300 Subject: #4118 Allow Bulk Upload to initialize on Bulk Upload All option --- indra/newview/llmaterialeditor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 28160177f6..378f5fdf91 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -137,7 +137,8 @@ LLFloaterComboOptions* LLFloaterComboOptions::showUI( { combo_picker->mComboOptions->addSimpleElement(*iter); } - combo_picker->mComboOptions->selectFirstItem(); + // select 'Bulk Upload All' option + combo_picker->mComboOptions->selectNthItem((S32)options.size() - 1); combo_picker->openFloater(LLSD(title)); combo_picker->setFocus(true); -- cgit v1.2.3 From f5d4d3f86224df61bacabf219b6d053bcdebc3ef Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 22 May 2025 19:53:35 +0300 Subject: #4109 Improve handling of GLTF transform hierarchy --- indra/newview/gltf/llgltfloader.cpp | 44 ++++++++++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 1 + 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 22c7c5d13e..871936db95 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -307,6 +307,34 @@ bool LLGLTFLoader::parseMeshes() return true; } +void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) +{ + auto& node = asset.mNodes[node_index]; + + // Start with this node's transform + glm::mat4 node_transform = node.mMatrix; + + // Find parent node and apply its transform if it exists + for (auto& other_node : asset.mNodes) + { + for (auto& child_index : other_node.mChildren) + { + if (child_index == node_index) + { + // Found a parent, recursively get its combined transform + glm::mat4 parent_transform; + computeCombinedNodeTransform(asset, static_cast(&other_node - &asset.mNodes[0]), parent_transform); + + // Apply parent transform to current node transform + node_transform = parent_transform * node_transform; + break; + } + } + } + + combined_transform = node_transform; +} + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, const F32 scale_factor, const LLVector3& center_offset) { @@ -438,14 +466,24 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } + // Compute combined transform for this node considering parent hierarchy + S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); + glm::mat4 combined_transform; + computeCombinedNodeTransform(mGLTFAsset, node_index, combined_transform); + // Apply the global scale and center offset to all vertices for (U32 i = 0; i < prim.getVertexCount(); i++) { + // Transform vertex position with combined hierarchy transform + glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); + glm::vec4 transformed_pos = combined_transform * pos; + + // Apply scaling and centering after hierarchy transform GLTFVertex vert; vert.position = glm::vec3( - (prim.mPositions[i][0] + center_offset.mV[VX]) * scale_factor, - (prim.mPositions[i][1] + center_offset.mV[VY]) * scale_factor, - (prim.mPositions[i][2] + center_offset.mV[VZ]) * scale_factor + (transformed_pos.x + center_offset.mV[VX]) * scale_factor, + (transformed_pos.y + center_offset.mV[VY]) * scale_factor, + (transformed_pos.z + center_offset.mV[VZ]) * scale_factor ); vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index bfcdccac00..4d02909f72 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -167,6 +167,7 @@ private: void uploadMeshes(); bool parseMaterials(); void uploadMaterials(); + void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform); bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, const F32 scale_factor = 1.0f, const LLVector3& center_offset = LLVector3()); void populateJointFromSkin(const LL::GLTF::Skin& skin); -- cgit v1.2.3 From d713be4bbe8c5d35aaeeff7744906f4fc1833643 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 23 May 2025 01:08:58 +0300 Subject: #4109 Use correct GLTF coordinate system rotation --- indra/newview/gltf/llgltfloader.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 871936db95..cfc55903d5 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -176,6 +176,7 @@ bool LLGLTFLoader::parseMeshes() LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix)); LLMatrix4 node_transform; + node_transform *= coord_system_rotation; // Apply coordinate rotation first node_transform *= node_matrix; // Examine all primitives in this mesh @@ -261,25 +262,24 @@ bool LLGLTFLoader::parseMeshes() // This will make sure the matrix is always valid from the node. node.makeMatrixValid(); - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); - mTransform = gltf_transform; - - // GLTF is +Y up, SL is +Z up - mTransform *= coord_system_rotation; - + mTransform.setIdentity(); transformation = mTransform; + // adjust the transformation to compensate for mesh normalization LLVector3 mesh_scale_vector; LLVector3 mesh_translation_vector; pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + LLMatrix4 mesh_translation; mesh_translation.setTranslation(mesh_translation_vector); mesh_translation *= transformation; transformation = mesh_translation; + LLMatrix4 mesh_scale; mesh_scale.initScale(mesh_scale_vector); mesh_scale *= transformation; transformation = mesh_scale; + if (transformation.determinant() < 0) { // negative scales are not supported LL_INFOS("GLTF") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " @@ -471,10 +471,16 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& glm::mat4 combined_transform; computeCombinedNodeTransform(mGLTFAsset, node_index, combined_transform); + // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up + glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + + // Apply coordinate system rotation to the combined transform + combined_transform = coord_system_rotation * combined_transform; + // Apply the global scale and center offset to all vertices for (U32 i = 0; i < prim.getVertexCount(); i++) { - // Transform vertex position with combined hierarchy transform + // Transform vertex position with combined hierarchy transform (including coord rotation) glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); glm::vec4 transformed_pos = combined_transform * pos; @@ -486,7 +492,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& (transformed_pos.z + center_offset.mV[VZ]) * scale_factor ); - vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + // Also rotate the normal vector + glm::vec4 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2], 0.0f); + glm::vec4 transformed_normal = coord_system_rotation * normal_vec; + vert.normal = glm::normalize(glm::vec3(transformed_normal)); + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); if (skinIdx >= 0) -- cgit v1.2.3 From 71d543ff07dba5a530e5c96891ccb6629e2fa173 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 23 May 2025 02:07:05 +0300 Subject: #4109 #4080 Apply coordinate rotation to GLTF inverse bind matrices --- indra/newview/gltf/llgltfloader.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index cfc55903d5..f0dcbf9cf4 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -257,10 +257,6 @@ bool LLGLTFLoader::parseMeshes() validate_model(pModel)) { mModelList.push_back(pModel); - LLMatrix4 saved_transform = mTransform; - - // This will make sure the matrix is always valid from the node. - node.makeMatrixValid(); mTransform.setIdentity(); transformation = mTransform; @@ -292,7 +288,6 @@ bool LLGLTFLoader::parseMeshes() mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); stretch_extents(pModel, transformation); - mTransform = saved_transform; } else { @@ -708,7 +703,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { // Process bind matrix LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - LLMatrix4 gltf_transform(glm::value_ptr(gltf_mat)); + + // For inverse bind matrices, we need to: + // 1. Get the original bind matrix by inverting + // 2. Apply coordinate rotation to the original + // 3. Invert again to get the rotated inverse bind matrix + glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); + glm::mat4 coord_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + glm::mat4 rotated_original = coord_rotation * original_bind_matrix; + glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); + + LLMatrix4 gltf_transform(glm::value_ptr(rotated_inverse_bind_matrix)); skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); LL_INFOS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; @@ -742,6 +747,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) { + // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up + glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + for (auto joint : skin.mJoints) { auto jointNode = mGLTFAsset.mNodes[joint]; @@ -754,13 +762,17 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) else { // ignore unrecognized joint - LL_DEBUGS("GLTF") << "Ignoring joing: " << legal_name << LL_ENDL; + LL_DEBUGS("GLTF") << "Ignoring joint: " << legal_name << LL_ENDL; continue; } jointNode.makeMatrixValid(); - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); + // Apply coordinate system rotation to joint transform + glm::mat4 gltf_joint_matrix = jointNode.mMatrix; + glm::mat4 rotated_joint_matrix = coord_system_rotation * gltf_joint_matrix; + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_joint_matrix)); mJointList[legal_name] = gltf_transform; mJointsFromNode.push_front(legal_name); -- cgit v1.2.3 From 8a9f09ea0ff03ead5d5040c45d7c0a56c325ec07 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 23 May 2025 02:59:47 +0300 Subject: #4109 #4080 Undistort GLTF rigged mesh in Z-up coordinate transformation --- indra/newview/gltf/llgltfloader.cpp | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index f0dcbf9cf4..f028c8cba8 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -716,11 +716,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LLMatrix4 gltf_transform(glm::value_ptr(rotated_inverse_bind_matrix)); skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); - LL_INFOS("GLTF") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; - // Translate based of mJointList - gltf_transform.setTranslation(mJointList[legal_name].getTranslation()); // name is supposed to be in mJointList - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(gltf_transform)); + // For alternate bind matrix, use the ORIGINAL joint transform (before rotation) + // Get the original joint node and use its matrix directly + S32 joint = gltf_skin.mJoints[i]; + LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + jointNode.makeMatrixValid(); + LLMatrix4 original_joint_transform(glm::value_ptr(jointNode.mMatrix)); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } } @@ -750,6 +756,8 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL; + for (auto joint : skin.mJoints) { auto jointNode = mGLTFAsset.mNodes[joint]; @@ -768,15 +776,31 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) jointNode.makeMatrixValid(); - // Apply coordinate system rotation to joint transform + // Debug: Log original joint matrix glm::mat4 gltf_joint_matrix = jointNode.mMatrix; + LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' original matrix:" << LL_ENDL; + for(int i = 0; i < 4; i++) + { + LL_INFOS("GLTF_DEBUG") << " [" << gltf_joint_matrix[i][0] << ", " << gltf_joint_matrix[i][1] + << ", " << gltf_joint_matrix[i][2] << ", " << gltf_joint_matrix[i][3] << "]" << LL_ENDL; + } + + // Apply coordinate system rotation to joint transform glm::mat4 rotated_joint_matrix = coord_system_rotation * gltf_joint_matrix; + // Debug: Log rotated joint matrix + LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' rotated matrix:" << LL_ENDL; + for(int i = 0; i < 4; i++) + { + LL_INFOS("GLTF_DEBUG") << " [" << rotated_joint_matrix[i][0] << ", " << rotated_joint_matrix[i][1] + << ", " << rotated_joint_matrix[i][2] << ", " << rotated_joint_matrix[i][3] << "]" << LL_ENDL; + } + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_joint_matrix)); mJointList[legal_name] = gltf_transform; mJointsFromNode.push_front(legal_name); - LL_DEBUGS("GLTF") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; } } -- cgit v1.2.3 From 4faebc08301f57e5191d29010c00e72748a45323 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sat, 24 May 2025 19:45:52 +0300 Subject: #4109 Remove workaround code --- indra/newview/gltf/llgltfloader.cpp | 109 +++--------------------------------- indra/newview/gltf/llgltfloader.h | 3 +- 2 files changed, 10 insertions(+), 102 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index f028c8cba8..c2211838d1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -147,100 +147,14 @@ bool LLGLTFLoader::parseMeshes() populateJointFromSkin(skin); } - /* Two-pass mesh processing approach: - * 1. First pass: Calculate global bounds across all meshes to determine proper scaling - * and centering for the entire model. This ensures consistent normalization. - * 2. Second pass: Process each mesh node with the calculated global scale factor and - * center offset, ensuring the entire model is properly proportioned and centered. - */ - - // First pass: Calculate global bounds across all meshes in the model - LLVector3 global_min_bounds(FLT_MAX, FLT_MAX, FLT_MAX); - LLVector3 global_max_bounds(-FLT_MAX, -FLT_MAX, -FLT_MAX); - bool has_geometry = false; - - // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up - LLMatrix4 coord_system_rotation; - coord_system_rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); - - // Gather bounds from all meshes - for (auto &node : mGLTFAsset.mNodes) - { - auto meshidx = node.mMesh; - if (meshidx >= 0 && meshidx < mGLTFAsset.mMeshes.size()) - { - auto mesh = mGLTFAsset.mMeshes[meshidx]; - - // Make node matrix valid for correct transformation - node.makeMatrixValid(); - - LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix)); - LLMatrix4 node_transform; - node_transform *= coord_system_rotation; // Apply coordinate rotation first - node_transform *= node_matrix; - - // Examine all primitives in this mesh - for (auto prim : mesh.mPrimitives) - { - if (prim.getVertexCount() >= USHRT_MAX) - continue; - - for (U32 i = 0; i < prim.getVertexCount(); i++) - { - // Transform vertex position by node transform - LLVector4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); - LLVector4 transformed_pos = pos * node_transform; - - global_min_bounds.mV[VX] = llmin(global_min_bounds.mV[VX], transformed_pos.mV[VX]); - global_min_bounds.mV[VY] = llmin(global_min_bounds.mV[VY], transformed_pos.mV[VY]); - global_min_bounds.mV[VZ] = llmin(global_min_bounds.mV[VZ], transformed_pos.mV[VZ]); - - global_max_bounds.mV[VX] = llmax(global_max_bounds.mV[VX], transformed_pos.mV[VX]); - global_max_bounds.mV[VY] = llmax(global_max_bounds.mV[VY], transformed_pos.mV[VY]); - global_max_bounds.mV[VZ] = llmax(global_max_bounds.mV[VZ], transformed_pos.mV[VZ]); - - has_geometry = true; - } - } - } - } - - // Calculate model dimensions and center point for the entire model - F32 global_scale_factor = 1.0f; - LLVector3 global_center_offset(0.0f, 0.0f, 0.0f); - - if (has_geometry - && mJointList.empty()) // temporary disable offset and scaling for rigged meshes + for (auto& node : mGLTFAsset.mNodes) { - // Calculate bounding box center - this will be our new origin - LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f; - global_center_offset = -center; // Offset to move center to origin - - // Calculate dimensions of the bounding box - LLVector3 dimensions = global_max_bounds - global_min_bounds; - - // Find the maximum dimension rather than the diagonal - F32 max_dimension = llmax(dimensions.mV[VX], llmax(dimensions.mV[VY], dimensions.mV[VZ])); - - // Always scale to the target size to ensure consistent bounding box - const F32 TARGET_SIZE = 1.0f; // Target size for maximum dimension in meters - - if (max_dimension > 0.0f) - { - // Calculate scale factor to normalize model's maximum dimension to TARGET_SIZE - global_scale_factor = TARGET_SIZE / max_dimension; - - LL_INFOS("GLTF_IMPORT") << "Model dimensions: " << dimensions.mV[VX] << "x" - << dimensions.mV[VY] << "x" << dimensions.mV[VZ] - << ", max dimension: " << max_dimension - << ", applying global scale factor: " << global_scale_factor - << ", global centering offset: (" << global_center_offset.mV[VX] << ", " - << global_center_offset.mV[VY] << ", " << global_center_offset.mV[VZ] << ")" << LL_ENDL; - } + // Make node matrix valid for correct transformation + node.makeMatrixValid(); } - // Second pass: Process each node with the global scale and offset - for (auto &node : mGLTFAsset.mNodes) + // Process each node + for (auto& node : mGLTFAsset.mNodes) { LLMatrix4 transformation; material_map mats; @@ -252,7 +166,7 @@ bool LLGLTFLoader::parseMeshes() { LLModel* pModel = new LLModel(volume_params, 0.f); auto mesh = mGLTFAsset.mMeshes[meshidx]; - if (populateModelFromMesh(pModel, mesh, node, mats, global_scale_factor, global_center_offset) && + if (populateModelFromMesh(pModel, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { @@ -292,7 +206,7 @@ bool LLGLTFLoader::parseMeshes() else { setLoadState(ERROR_MODEL + pModel->getStatus()); - delete (pModel); + delete pModel; return false; } } @@ -330,8 +244,7 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 combined_transform = node_transform; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, - const F32 scale_factor, const LLVector3& center_offset) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { pModel->mLabel = mesh.mName; pModel->ClearFacesAndMaterials(); @@ -481,11 +394,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Apply scaling and centering after hierarchy transform GLTFVertex vert; - vert.position = glm::vec3( - (transformed_pos.x + center_offset.mV[VX]) * scale_factor, - (transformed_pos.y + center_offset.mV[VY]) * scale_factor, - (transformed_pos.z + center_offset.mV[VZ]) * scale_factor - ); + vert.position = glm::vec3(transformed_pos.x, transformed_pos.y, transformed_pos.z); // Also rotate the normal vector glm::vec4 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2], 0.0f); diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 4d02909f72..fac5b719fb 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -168,8 +168,7 @@ private: bool parseMaterials(); void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, - const F32 scale_factor = 1.0f, const LLVector3& center_offset = LLVector3()); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); void populateJointFromSkin(const LL::GLTF::Skin& skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); -- cgit v1.2.3 From 3eab945d904610d30d1efd6ab8dd25d17822dbc7 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sat, 24 May 2025 20:41:55 +0300 Subject: #4109 Refactor LLGLTFLoader::populateModelFromMesh() --- indra/newview/gltf/llgltfloader.cpp | 56 +++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c2211838d1..40dae9a7c3 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -251,6 +251,20 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& S32 skinIdx = nodeno.mSkin; + // Pre-compute coordinate system rotation matrix (GLTF Y-up to SL Z-up) + static const glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + + // Compute final combined transform matrix (hierarchy + coordinate rotation) + S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); + glm::mat4 hierarchy_transform; + computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform); + + // Combine transforms: coordinate rotation applied to hierarchy transform + const glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + + // Pre-compute normal transform matrix (transpose of inverse of upper-left 3x3) + const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(final_transform))); + // Mark unsuported joints with '-1' so that they won't get added into weights // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. std::vector gltf_joint_index_use_count; @@ -286,11 +300,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // So primitives already have all of the data we need for a given face in SL land. // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 - LLVolumeFace face; - LLVolumeFace::VertexMapData::PointMap point_map; - + LLVolumeFace face; std::vector vertices; - std::vector indices; + std::vector indices; LLImportMaterial impMat; impMat.mDiffuseColor = LLColor4::white; // Default color @@ -374,32 +386,19 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // Compute combined transform for this node considering parent hierarchy - S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); - glm::mat4 combined_transform; - computeCombinedNodeTransform(mGLTFAsset, node_index, combined_transform); - - // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up - glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - - // Apply coordinate system rotation to the combined transform - combined_transform = coord_system_rotation * combined_transform; - // Apply the global scale and center offset to all vertices for (U32 i = 0; i < prim.getVertexCount(); i++) { - // Transform vertex position with combined hierarchy transform (including coord rotation) + // Use pre-computed final_transform glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); - glm::vec4 transformed_pos = combined_transform * pos; + glm::vec4 transformed_pos = final_transform * pos; - // Apply scaling and centering after hierarchy transform GLTFVertex vert; - vert.position = glm::vec3(transformed_pos.x, transformed_pos.y, transformed_pos.z); + vert.position = glm::vec3(transformed_pos); - // Also rotate the normal vector - glm::vec4 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2], 0.0f); - glm::vec4 transformed_normal = coord_system_rotation * normal_vec; - vert.normal = glm::normalize(glm::vec3(transformed_normal)); + // Use pre-computed normal_transform + glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.normal = glm::normalize(normal_transform * normal_vec); vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); @@ -610,19 +609,14 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (i < gltf_skin.mInverseBindMatricesData.size()) { - // Process bind matrix + // Use pre-computed coord_system_rotation instead of recreating it LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - // For inverse bind matrices, we need to: - // 1. Get the original bind matrix by inverting - // 2. Apply coordinate rotation to the original - // 3. Invert again to get the rotated inverse bind matrix glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); - glm::mat4 coord_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - glm::mat4 rotated_original = coord_rotation * original_bind_matrix; + glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); - LLMatrix4 gltf_transform(glm::value_ptr(rotated_inverse_bind_matrix)); + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; -- cgit v1.2.3 From e8eac13b7ba238172c7d5d677cff744bff8dff6d Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sat, 24 May 2025 21:10:10 +0300 Subject: #4109 Refactor LLGLTFLoader::populateModelFromMesh() #2 --- indra/newview/gltf/llgltfloader.cpp | 49 +++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 40dae9a7c3..2e8521b597 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -434,25 +434,36 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& indices.push_back(prim.mIndexArray[i]); } + // Check for empty vertex array before processing + if (vertices.empty()) + { + LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL; + continue; // Skip this primitive + } + std::vector faceVertices; - glm::vec3 min = glm::vec3(0); - glm::vec3 max = glm::vec3(0); + glm::vec3 min = glm::vec3(FLT_MAX); + glm::vec3 max = glm::vec3(-FLT_MAX); for (U32 i = 0; i < vertices.size(); i++) { LLVolumeFace::VertexData vert; - if (i == 0 || vertices[i].position.x > max.x) - max.x = vertices[i].position.x; - if (i == 0 || vertices[i].position.y > max.y) - max.y = vertices[i].position.y; - if (i == 0 || vertices[i].position.z > max.z) - max.z = vertices[i].position.z; - if (i == 0 || vertices[i].position.x < min.x) - min.x = vertices[i].position.x; - if (i == 0 || vertices[i].position.y < min.y) - min.y = vertices[i].position.y; - if (i == 0 || vertices[i].position.z < min.z) - min.z = vertices[i].position.z; + + // Update min/max bounds + if (i == 0) + { + min = max = vertices[i].position; + } + else + { + min.x = std::min(min.x, vertices[i].position.x); + min.y = std::min(min.y, vertices[i].position.y); + min.z = std::min(min.z, vertices[i].position.z); + max.x = std::max(max.x, vertices[i].position.x); + max.y = std::max(max.y, vertices[i].position.y); + max.z = std::max(max.z, vertices[i].position.z); + } + LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); vert.setPosition(position); @@ -499,12 +510,12 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::vector wght; F32 total = 0.f; - for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) + for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j) { // take up to 4 most significant weights // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. - wght.push_back(weight_list[i]); - total += weight_list[i].mWeight; + wght.push_back(weight_list[j]); + total += weight_list[j].mWeight; } if (total != 0.f) @@ -512,9 +523,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& F32 scale = 1.f / total; if (scale != 1.f) { // normalize weights - for (U32 i = 0; i < wght.size(); ++i) + for (U32 j = 0; j < wght.size(); ++j) { - wght[i].mWeight *= scale; + wght[j].mWeight *= scale; } } } -- cgit v1.2.3 From 83fa366de9df385daec05c262f88eefd86af6adf Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sun, 25 May 2025 00:53:25 +0300 Subject: #4109 Fix inside-out geometry from negative scale transforms in GLTF loader --- indra/newview/gltf/llgltfloader.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2e8521b597..a64df86770 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -262,6 +262,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Combine transforms: coordinate rotation applied to hierarchy transform const glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + // Check if we have a negative scale (flipped coordinate system) + bool hasNegativeScale = glm::determinant(final_transform) < 0.0f; + // Pre-compute normal transform matrix (transpose of inverse of upper-left 3x3) const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(final_transform))); @@ -429,9 +432,22 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vertices.push_back(vert); } - for (U32 i = 0; i < prim.getIndexCount(); i++) + // When processing indices, flip winding order if needed + for (U32 i = 0; i < prim.getIndexCount(); i += 3) { - indices.push_back(prim.mIndexArray[i]); + if (hasNegativeScale) + { + // Flip winding order for negative scale + indices.push_back(prim.mIndexArray[i]); + indices.push_back(prim.mIndexArray[i + 2]); // Swap these two + indices.push_back(prim.mIndexArray[i + 1]); + } + else + { + indices.push_back(prim.mIndexArray[i]); + indices.push_back(prim.mIndexArray[i + 1]); + indices.push_back(prim.mIndexArray[i + 2]); + } } // Check for empty vertex array before processing -- cgit v1.2.3 From d6419f729e3997c3d7cd67c7ff4ee38ab835c509 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sun, 25 May 2025 01:21:36 +0300 Subject: #4109 Fix GLTF model extents calculation --- indra/newview/gltf/llgltfloader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index a64df86770..3312e61595 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -588,6 +588,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } + // Call normalizeVolumeFaces to compute proper extents + pModel->normalizeVolumeFaces(); + // Fill joint names, bind matrices and prepare to remap weight indices if (skinIdx >= 0) { -- cgit v1.2.3 From b423900792aca2e094fac85f9aeb34b26f9c2109 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Sun, 25 May 2025 19:28:24 +0300 Subject: #4105 Fix duplicate GLTF model instances causing upload errors --- indra/newview/gltf/llgltfloader.cpp | 35 ++++++++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 2 +- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3312e61595..17f52af6b1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -153,6 +153,9 @@ bool LLGLTFLoader::parseMeshes() node.makeMatrixValid(); } + // Track how many times each mesh name has been used + std::map mesh_name_counts; + // Process each node for (auto& node : mGLTFAsset.mNodes) { @@ -166,7 +169,17 @@ bool LLGLTFLoader::parseMeshes() { LLModel* pModel = new LLModel(volume_params, 0.f); auto mesh = mGLTFAsset.mMeshes[meshidx]; - if (populateModelFromMesh(pModel, mesh, node, mats) && + + // Get base mesh name and track usage + std::string base_name = mesh.mName; + if (base_name.empty()) + { + base_name = "mesh_" + std::to_string(meshidx); + } + + S32 instance_count = mesh_name_counts[base_name]++; + + if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { @@ -244,9 +257,25 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 combined_transform = node_transform; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) { - pModel->mLabel = mesh.mName; + // Create unique model name + std::string base_name = mesh.mName; + if (base_name.empty()) + { + S32 mesh_index = static_cast(&mesh - &mGLTFAsset.mMeshes[0]); + base_name = "mesh_" + std::to_string(mesh_index); + } + + if (instance_count > 0) + { + pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); + } + else + { + pModel->mLabel = base_name; + } + pModel->ClearFacesAndMaterials(); S32 skinIdx = nodeno.mSkin; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index fac5b719fb..69289aabce 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -168,7 +168,7 @@ private: bool parseMaterials(); void uploadMaterials(); void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform); - bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); + bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointFromSkin(const LL::GLTF::Skin& skin); LLUUID imageBufferToTextureUUID(const gltf_texture& tex); -- cgit v1.2.3 From d342aa79c24fe20d06a018eabdb03912d11d4702 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 26 May 2025 17:31:37 +0300 Subject: #4080 Rigged mesh support #3 --- indra/newview/gltf/llgltfloader.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 17f52af6b1..de7c341cdf 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -690,6 +690,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } + else + { + // For gltf mInverseBindMatrices are optional, but not for viewer + // todo: get a model that triggers this + skin_info.mInvBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); // might need to be an 'identity' + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); + } } // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh -- cgit v1.2.3 From a364b5ee1d4c009b2e571fde66272ae06e434c21 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 26 May 2025 17:43:56 +0300 Subject: #4109 Add validation for non-triangulated geometry in GLTF loader --- indra/newview/gltf/llgltfloader.cpp | 10 ++++++++++ indra/newview/skins/default/xui/en/floater_model_preview.xml | 1 + 2 files changed, 11 insertions(+) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index de7c341cdf..93ed904f2e 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -461,6 +461,16 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& vertices.push_back(vert); } + if (prim.getIndexCount() % 3 != 0) + { + LL_WARNS("GLTF_IMPORT") << "Invalid primitive: index count " << prim.getIndexCount() + << " is not divisible by 3. GLTF files must contain triangulated geometry." << LL_ENDL; + LLSD args; + args["Message"] = "InvalidGeometryNonTriangulated"; + mWarningsArray.append(args); + continue; // Skip this primitive + } + // When processing indices, flip winding order if needed for (U32 i = 0; i < prim.getIndexCount(); i += 3) { diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 99c348c236..067a1e0c8b 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -61,6 +61,7 @@ Document has no root Document has no visual_scene Unable to process mesh without position data. Invalid model. + Invalid geometry: GLTF files must contain triangulated meshes only. Date: Mon, 26 May 2025 21:27:09 +0300 Subject: #4080 Rigged mesh support #4 --- indra/newview/gltf/llgltfloader.cpp | 40 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 93ed904f2e..82666761df 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -67,6 +67,14 @@ static const std::string lod_suffix[LLModel::NUM_LODS] = "_PHYS", }; +// Premade rotation matrix, GLTF is Y-up while SL is Z-up +static const glm::mat4 coord_system_rotation( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f +); + LLGLTFLoader::LLGLTFLoader(std::string filename, S32 lod, @@ -203,9 +211,21 @@ bool LLGLTFLoader::parseMeshes() mesh_scale *= transformation; transformation = mesh_scale; + // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh + // into the coordinate space of the joints. + // In GLTF, this matrix is omitted, and it is assumed that this transform is either + // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. + // + // TODO: This appers to be missing rotation when joints rotate the model + // or inverted bind matrices are missing inherited rotation + // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly + // prior to skinning) + pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); + LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; + if (transformation.determinant() < 0) { // negative scales are not supported - LL_INFOS("GLTF") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " << pModel->mLabel << LL_ENDL; LLSD args; args["Message"] = "NegativeScaleNormTrans"; @@ -280,9 +300,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& S32 skinIdx = nodeno.mSkin; - // Pre-compute coordinate system rotation matrix (GLTF Y-up to SL Z-up) - static const glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - // Compute final combined transform matrix (hierarchy + coordinate rotation) S32 node_index = static_cast(&nodeno - &mGLTFAsset.mNodes[0]); glm::mat4 hierarchy_transform; @@ -619,7 +636,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mats[materialName] = impMat; } else { - LL_INFOS() << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; LLSD args; args["Message"] = "ParsingErrorBadElement"; mWarningsArray.append(args); @@ -639,7 +656,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& size_t jointCnt = gltf_skin.mJoints.size(); if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size()) { - LL_INFOS("GLTF") << "Bind matrices count mismatch joints count" << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; LLSD args; args["Message"] = "InvBindCountMismatch"; mWarningsArray.append(args); @@ -709,14 +726,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh - // into the coordinate space of the joints. - // In GLTF, this matrix is omitted, and it is assumed that this transform is either - // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. - LLMatrix4 bind_shape; - bind_shape.setIdentity(); - skin_info.mBindShapeMatrix.loadu(bind_shape); - // Remap indices for pModel->mSkinWeights for (auto& weights : pModel->mSkinWeights) { @@ -732,9 +741,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) { - // Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up - glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL; for (auto joint : skin.mJoints) -- cgit v1.2.3 From 3f0aa3383a25d203dcdf35d0b7937cbcba3ea5e3 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 26 May 2025 22:03:55 +0300 Subject: #4109 Improve LLGLTFLoader::computeCombinedNodeTransform() --- indra/newview/gltf/llgltfloader.cpp | 39 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 82666761df..5922385358 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -251,30 +251,35 @@ bool LLGLTFLoader::parseMeshes() void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) { - auto& node = asset.mNodes[node_index]; + if (node_index < 0 || node_index >= static_cast(asset.mNodes.size())) + { + combined_transform = glm::mat4(1.0f); + return; + } + + const auto& node = asset.mNodes[node_index]; + + // Ensure the node's matrix is valid + const_cast(node).makeMatrixValid(); // Start with this node's transform - glm::mat4 node_transform = node.mMatrix; + combined_transform = node.mMatrix; - // Find parent node and apply its transform if it exists - for (auto& other_node : asset.mNodes) + // Find and apply parent transform if it exists + for (size_t i = 0; i < asset.mNodes.size(); ++i) { - for (auto& child_index : other_node.mChildren) - { - if (child_index == node_index) - { - // Found a parent, recursively get its combined transform - glm::mat4 parent_transform; - computeCombinedNodeTransform(asset, static_cast(&other_node - &asset.mNodes[0]), parent_transform); + const auto& potential_parent = asset.mNodes[i]; + auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), node_index); - // Apply parent transform to current node transform - node_transform = parent_transform * node_transform; - break; - } + if (it != potential_parent.mChildren.end()) + { + // Found parent - recursively get its combined transform and apply it + glm::mat4 parent_transform; + computeCombinedNodeTransform(asset, static_cast(i), parent_transform); + combined_transform = parent_transform * combined_transform; + return; // Early exit - a node can only have one parent } } - - combined_transform = node_transform; } bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) -- cgit v1.2.3 From 0d99487d46d6ccf7000723c35ac78018b5763dea Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 27 May 2025 20:28:00 +0300 Subject: #4107 upload the model ignoring unsupported extension --- indra/newview/gltf/asset.cpp | 10 ++-------- indra/newview/gltf/asset.h | 2 ++ indra/newview/gltf/llgltfloader.cpp | 7 +++++++ indra/newview/skins/default/xui/en/floater_model_preview.xml | 1 + 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 129661c195..6576904874 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -489,22 +489,16 @@ void Asset::update() bool Asset::prep() { LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; - // check required extensions and fail if not supported - bool unsupported = false; + // check required extensions for (auto& extension : mExtensionsRequired) { if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) { LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; - unsupported = true; + mUnsupportedExtension = true; } } - if (unsupported) - { - return false; - } - // do buffers first as other resources depend on them for (auto& buffer : mBuffers) { diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 3cec18268b..3a20f7d6ea 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -396,6 +396,8 @@ namespace LL U32 mMaterialsUBO = 0; bool mLoadIntoVRAM = false; + bool mUnsupportedExtension = false; + // prepare for first time use bool prep(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5922385358..236c75a125 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -127,6 +127,13 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } + if (mGLTFAsset.mUnsupportedExtension) + { + LLSD args; + args["Message"] = "UnsupportedExtension"; + mWarningsArray.append(args); + } + mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 067a1e0c8b..d326cbde3b 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -62,6 +62,7 @@ Document has no visual_scene Unable to process mesh without position data. Invalid model. Invalid geometry: GLTF files must contain triangulated meshes only. + Model uses unsupported extension, related material properties are ignored. Date: Tue, 27 May 2025 22:01:35 +0300 Subject: #4157 Improve "Unable to upload material" error message --- indra/newview/skins/default/xui/en/notifications.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index a51feeb7ab..82e77119ab 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -9453,8 +9453,11 @@ Unable to upload texture: '[NAME]' icon="alertmodal.tga" name="CannotUploadMaterial" type="alertmodal"> -There was a problem uploading the file +Unable to upload material file. The file may be corrupted, in an unsupported format, or contain invalid data. Please check that you're using a valid GLTF/GLB file with proper material definitions. fail + Date: Mon, 26 May 2025 21:27:09 +0300 Subject: #4080 Rigged mesh support #5 --- indra/newview/gltf/llgltfloader.cpp | 110 ++++++++++++++++++++++++++++-------- indra/newview/gltf/llgltfloader.h | 4 +- 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 236c75a125..1f8733f4ff 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -155,6 +155,12 @@ bool LLGLTFLoader::parseMeshes() mTransform.setIdentity(); + for (auto& node : mGLTFAsset.mNodes) + { + // Make node matrix valid for correct transformation + node.makeMatrixValid(); + } + // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. for (auto& skin : mGLTFAsset.mSkins) @@ -162,12 +168,6 @@ bool LLGLTFLoader::parseMeshes() populateJointFromSkin(skin); } - for (auto& node : mGLTFAsset.mNodes) - { - // Make node matrix valid for correct transformation - node.makeMatrixValid(); - } - // Track how many times each mesh name has been used std::map mesh_name_counts; @@ -218,17 +218,21 @@ bool LLGLTFLoader::parseMeshes() mesh_scale *= transformation; transformation = mesh_scale; - // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh - // into the coordinate space of the joints. - // In GLTF, this matrix is omitted, and it is assumed that this transform is either - // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. - // - // TODO: This appers to be missing rotation when joints rotate the model - // or inverted bind matrices are missing inherited rotation - // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly - // prior to skinning) - pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); - LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; + if (node.mSkin >= 0) + { + // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh + // into the coordinate space of the joints. + // In GLTF, this matrix is omitted, and it is assumed that this transform is either + // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. + // + // TODO: There appears to be missing rotation when joints rotate the model + // or inverted bind matrices are missing inherited rotation + // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly + // prior to skinning) + + pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); + LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; + } if (transformation.determinant() < 0) { // negative scales are not supported @@ -256,7 +260,7 @@ bool LLGLTFLoader::parseMeshes() return true; } -void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) +void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const { if (node_index < 0 || node_index >= static_cast(asset.mNodes.size())) { @@ -342,7 +346,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Process joint name and idnex S32 joint = gltf_skin.mJoints[i]; LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) == mJointMap.end()) @@ -688,7 +691,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& continue; } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) != mJointMap.end()) @@ -723,8 +725,15 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Get the original joint node and use its matrix directly S32 joint = gltf_skin.mJoints[i]; LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - jointNode.makeMatrixValid(); - LLMatrix4 original_joint_transform(glm::value_ptr(jointNode.mMatrix)); + glm::mat4 joint_mat = jointNode.mMatrix; + S32 root_joint = findValidRootJoint(joint, gltf_skin); // skeleton can have multiple real roots + if (root_joint == joint) + { + // This is very likely incomplete in some way. + // Root shouldn't be the only one to need full coordinate fix + joint_mat = coord_system_rotation * joint_mat; + } + LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); @@ -771,8 +780,6 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) continue; } - jointNode.makeMatrixValid(); - // Debug: Log original joint matrix glm::mat4 gltf_joint_matrix = jointNode.mMatrix; LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' original matrix:" << LL_ENDL; @@ -801,6 +808,61 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) } } +S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_joint = 0; + S32 found_joint = source_joint; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_joint = found_joint; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + if (it != jointNode.mChildren.end()) + { + found_joint = joint; + break; + } + } + } + } while (root_joint != found_joint); + + return root_joint; +} + +S32 LLGLTFLoader::findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_joint = 0; + S32 found_joint = 0; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_joint = found_joint; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + if (it != jointNode.mChildren.end()) + { + found_joint = joint; + break; + } + } + } while (root_joint != found_joint); + + LL_INFOS("GLTF_DEBUG") << "mJointList name: "; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_joint]; + LL_CONT << jointNode.mName << " index: " << root_joint << LL_ENDL; + return root_joint; +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 69289aabce..d0f761df2c 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -167,9 +167,11 @@ private: void uploadMeshes(); bool parseMaterials(); void uploadMaterials(); - void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform); + void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const; bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count); void populateJointFromSkin(const LL::GLTF::Skin& skin); + S32 findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const; + S32 findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint LLUUID imageBufferToTextureUUID(const gltf_texture& tex); // bool mPreprocessGLTF; -- cgit v1.2.3 From 4c60231c3fa52a0875ff5ddd7cc4e416f839da95 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 28 May 2025 12:02:50 +0300 Subject: #4080 Rigged mesh support #6 For now not touching normalizeVolumeFaces() to not brick dae upload --- indra/llprimitive/llmodel.cpp | 156 ++++++++++++++++++++++++++++++++++++ indra/llprimitive/llmodel.h | 1 + indra/newview/gltf/llgltfloader.cpp | 13 ++- 3 files changed, 166 insertions(+), 4 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 4e3e49ec9f..c67dac2733 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -334,6 +334,162 @@ void LLModel::normalizeVolumeFaces() } } +void LLModel::normalizeVolumeFacesAndWeights() +{ + if (!mVolumeFaces.empty()) + { + LLVector4a min, max; + + // For all of the volume faces + // in the model, loop over + // them and see what the extents + // of the volume along each axis. + min = mVolumeFaces[0].mExtents[0]; + max = mVolumeFaces[0].mExtents[1]; + + for (U32 i = 1; i < mVolumeFaces.size(); ++i) + { + LLVolumeFace& face = mVolumeFaces[i]; + + update_min_max(min, max, face.mExtents[0]); + update_min_max(min, max, face.mExtents[1]); + + if (face.mTexCoords) + { + LLVector2& min_tc = face.mTexCoordExtents[0]; + LLVector2& max_tc = face.mTexCoordExtents[1]; + + min_tc = face.mTexCoords[0]; + max_tc = face.mTexCoords[0]; + + for (S32 j = 1; j < face.mNumVertices; ++j) + { + update_min_max(min_tc, max_tc, face.mTexCoords[j]); + } + } + else + { + face.mTexCoordExtents[0].set(0, 0); + face.mTexCoordExtents[1].set(1, 1); + } + } + + // Now that we have the extents of the model + // we can compute the offset needed to center + // the model at the origin. + + // Compute center of the model + // and make it negative to get translation + // needed to center at origin. + LLVector4a trans; + trans.setAdd(min, max); + trans.mul(-0.5f); + + // Compute the total size along all + // axes of the model. + LLVector4a size; + size.setSub(max, min); + + // Prevent division by zero. + F32 x = size[0]; + F32 y = size[1]; + F32 z = size[2]; + F32 w = size[3]; + if (fabs(x) < F_APPROXIMATELY_ZERO) + { + x = 1.0; + } + if (fabs(y) < F_APPROXIMATELY_ZERO) + { + y = 1.0; + } + if (fabs(z) < F_APPROXIMATELY_ZERO) + { + z = 1.0; + } + size.set(x, y, z, w); + + // Compute scale as reciprocal of size + LLVector4a scale; + scale.splat(1.f); + scale.div(size); + + LLVector4a inv_scale(1.f); + inv_scale.div(scale); + + for (U32 i = 0; i < mVolumeFaces.size(); ++i) + { + LLVolumeFace& face = mVolumeFaces[i]; + + // We shrink the extents so + // that they fall within + // the unit cube. + // VFExtents change + face.mExtents[0].add(trans); + face.mExtents[0].mul(scale); + + face.mExtents[1].add(trans); + face.mExtents[1].mul(scale); + + // For all the positions, we scale + // the positions to fit within the unit cube. + LLVector4a* pos = (LLVector4a*)face.mPositions; + LLVector4a* norm = (LLVector4a*)face.mNormals; + LLVector4a* t = (LLVector4a*)face.mTangents; + + for (S32 j = 0; j < face.mNumVertices; ++j) + { + pos[j].add(trans); + pos[j].mul(scale); + if (norm && !norm[j].equals3(LLVector4a::getZero())) + { + norm[j].mul(inv_scale); + norm[j].normalize3(); + } + + if (t) + { + F32 w = t[j].getF32ptr()[3]; + t[j].mul(inv_scale); + t[j].normalize3(); + t[j].getF32ptr()[3] = w; + } + } + } + + weight_map old_weights = mSkinWeights; + mSkinWeights.clear(); + mPosition.clear(); + + for (auto& weights : old_weights) + { + LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]); + pos.add(trans); + pos.mul(scale); + LLVector3 scaled_pos(pos.getF32ptr()); + mPosition.push_back(scaled_pos); + mSkinWeights[scaled_pos] = weights.second; + } + + // mNormalizedScale is the scale at which + // we would need to multiply the model + // by to get the original size of the + // model instead of the normalized size. + LLVector4a normalized_scale; + normalized_scale.splat(1.f); + normalized_scale.div(scale); + mNormalizedScale.set(normalized_scale.getF32ptr()); + mNormalizedTranslation.set(trans.getF32ptr()); + mNormalizedTranslation *= -1.f; + + // remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation) + for (auto& face : mVolumeFaces) + { + face.mNormalizedScale = mNormalizedScale; + } + } +} + void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const { scale_out = mNormalizedScale; diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index fe28926720..5c6d0a55d2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -202,6 +202,7 @@ public: void sortVolumeFacesByMaterialName(); void normalizeVolumeFaces(); + void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); void optimizeVolumeFaces(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1f8733f4ff..f16efe2ff1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -468,6 +468,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (skinIdx >= 0) { + vert.weights = glm::vec4(prim.mWeights[i]); + auto accessorIdx = prim.mAttributes["JOINTS_0"]; LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; if (accessorIdx >= 0) @@ -487,8 +489,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { vert.joints = glm::unpackUint4x16(prim.mJoints[i]); } - - vert.weights = glm::vec4(prim.mWeights[i]); + else + { + vert.joints = glm::zero(); + vert.weights = glm::zero(); + } } vertices.push_back(vert); } @@ -659,8 +664,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // Call normalizeVolumeFaces to compute proper extents - pModel->normalizeVolumeFaces(); + // Call normalizeVolumeFacesAndWeights to compute proper extents + pModel->normalizeVolumeFacesAndWeights(); // Fill joint names, bind matrices and prepare to remap weight indices if (skinIdx >= 0) -- cgit v1.2.3 From 0dbf2b40f6f30728858fadb6390c465dc36669aa Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 28 May 2025 20:54:03 +0300 Subject: #4080 Fix gltf crash when opening --- indra/newview/gltf/buffer_util.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index ef9bba8128..2632a0f263 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -158,6 +158,12 @@ namespace LL dst.load3(src); } + template<> + inline void copyVec3(F32* src, LLColor4U& dst) + { + dst.set((U8)(src[0] * 255.f), (U8)(src[1] * 255.f), (U8)(src[2] * 255.f), 255); + } + template<> inline void copyVec3(U16* src, LLColor4U& dst) { -- cgit v1.2.3 From be40d20bca15b97ccba557dc530fe55a92456ebf Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 29 May 2025 20:20:29 +0300 Subject: #4190 provide unsupported extension info in log --- indra/newview/gltf/asset.cpp | 2 +- indra/newview/gltf/asset.h | 2 +- indra/newview/gltf/llgltfloader.cpp | 11 ++++++++++- indra/newview/skins/default/xui/en/floater_model_preview.xml | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 6576904874..3859bdc103 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -495,7 +495,7 @@ bool Asset::prep() if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) { LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; - mUnsupportedExtension = true; + mUnsupportedExtensions.push_back(extension); } } diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 3a20f7d6ea..a22de4d59d 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -396,7 +396,7 @@ namespace LL U32 mMaterialsUBO = 0; bool mLoadIntoVRAM = false; - bool mUnsupportedExtension = false; + std::vector mUnsupportedExtensions; // prepare for first time use bool prep(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index f16efe2ff1..2461a878fb 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -127,10 +127,19 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return false; } - if (mGLTFAsset.mUnsupportedExtension) + if (mGLTFAsset.mUnsupportedExtensions.size() > 0) { LLSD args; args["Message"] = "UnsupportedExtension"; + std::string del; + std::string ext; + for (auto& extension : mGLTFAsset.mUnsupportedExtensions) + { + ext += del; + ext += extension; + del = ","; + } + args["EXT"] = ext; mWarningsArray.append(args); } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index d326cbde3b..c0cddf0984 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -62,7 +62,7 @@ Document has no visual_scene Unable to process mesh without position data. Invalid model. Invalid geometry: GLTF files must contain triangulated meshes only. - Model uses unsupported extension, related material properties are ignored. + Model uses unsupported extension, related material properties are ignored: [EXT] Date: Fri, 30 May 2025 03:06:33 +0300 Subject: #4191 skip loading model compressed with Draco --- indra/newview/gltf/asset.cpp | 19 +++++++++- indra/newview/gltf/asset.h | 1 + indra/newview/gltf/llgltfloader.cpp | 43 +++++++++++----------- indra/newview/gltf/llgltfloader.h | 2 + .../skins/default/xui/en/floater_model_preview.xml | 3 +- 5 files changed, 44 insertions(+), 24 deletions(-) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 3859bdc103..8c9f77686a 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -50,6 +50,10 @@ namespace LL "KHR_texture_transform" }; + static std::unordered_set ExtensionsIgnored = { + "KHR_materials_pbrSpecularGlossiness" + }; + Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode) { if (alpha_mode == "OPAQUE") @@ -494,10 +498,21 @@ bool Asset::prep() { if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) { - LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; - mUnsupportedExtensions.push_back(extension); + if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end()) + { + LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; + mUnsupportedExtensions.push_back(extension); + } + else + { + mIgnoredExtensions.push_back(extension); + } } } + if (mUnsupportedExtensions.size() > 0) + { + return false; + } // do buffers first as other resources depend on them for (auto& buffer : mBuffers) diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index a22de4d59d..b9554d753c 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -397,6 +397,7 @@ namespace LL bool mLoadIntoVRAM = false; std::vector mUnsupportedExtensions; + std::vector mIgnoredExtensions; // prepare for first time use bool prep(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2461a878fb..5cdd7f09e0 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -111,8 +111,6 @@ LLGLTFLoader::~LLGLTFLoader() {} bool LLGLTFLoader::OpenFile(const std::string &filename) { tinygltf::TinyGLTF loader; - std::string error_msg; - std::string warn_msg; std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); @@ -120,28 +118,11 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) if (!mGltfLoaded) { - if (!warn_msg.empty()) - LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL; - if (!error_msg.empty()) - LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL; + notifyUnsupportedExtension(true); return false; } - if (mGLTFAsset.mUnsupportedExtensions.size() > 0) - { - LLSD args; - args["Message"] = "UnsupportedExtension"; - std::string del; - std::string ext; - for (auto& extension : mGLTFAsset.mUnsupportedExtensions) - { - ext += del; - ext += extension; - del = ","; - } - args["EXT"] = ext; - mWarningsArray.append(args); - } + notifyUnsupportedExtension(false); mMeshesLoaded = parseMeshes(); if (mMeshesLoaded) uploadMeshes(); @@ -1347,3 +1328,23 @@ LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) return LLUUID::null; } +void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) +{ + std::vector extensions = unsupported ? mGLTFAsset.mUnsupportedExtensions : mGLTFAsset.mIgnoredExtensions; + if (extensions.size() > 0) + { + LLSD args; + args["Message"] = unsupported ? "UnsupportedExtension" : "IgnoredExtension"; + std::string del; + std::string ext; + for (auto& extension : extensions) + { + ext += del; + ext += extension; + del = ","; + } + args["EXT"] = ext; + mWarningsArray.append(args); + } +} + diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index d0f761df2c..6e0fe2b32c 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -174,6 +174,8 @@ private: S32 findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint LLUUID imageBufferToTextureUUID(const gltf_texture& tex); + void notifyUnsupportedExtension(bool unsupported); + // bool mPreprocessGLTF; /* Below inherited from dae loader - unknown if/how useful here diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index c0cddf0984..6231abf9a5 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -62,7 +62,8 @@ Document has no visual_scene Unable to process mesh without position data. Invalid model. Invalid geometry: GLTF files must contain triangulated meshes only. - Model uses unsupported extension, related material properties are ignored: [EXT] + Model uses unsupported extension: [EXT], related material properties are ignored. + Unable to load a model, unsupported extension: [EXT]