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/newview/gltf/llgltfloader.cpp | 431 ++++++++++++++++++++++++++++++++++++ 1 file changed, 431 insertions(+) create mode 100644 indra/newview/gltf/llgltfloader.cpp (limited to 'indra/newview/gltf/llgltfloader.cpp') 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; +} -- 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 ++++++++++++++++++++++++++++++++---- 1 file changed, 247 insertions(+), 31 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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; -- 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 +++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 27 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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; -- 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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 ++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 43 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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) { -- 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/newview/gltf/llgltfloader.cpp | 241 +++++------------------------------- 1 file changed, 33 insertions(+), 208 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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; -- 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 ++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 6 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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); -- 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/newview/gltf/llgltfloader.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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 +++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 32 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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); -- 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 ++++++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 30 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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; -- 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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/newview/gltf/llgltfloader.cpp | 85 ++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 6 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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; } } -- 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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 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/llgltfloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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) { -- 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 ++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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]); -- 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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 +++--------------------------------- 1 file changed, 9 insertions(+), 100 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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); -- 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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; -- 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(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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) { -- cgit v1.2.3 From d9d800886d9694121c987654b72c40b972516aac Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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/llgltfloader.cpp | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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(); -- cgit v1.2.3 From 078cc9b0c1dc00b55bad9b3152651a66e8e2e79f Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 26 May 2025 21:27:09 +0300 Subject: #4080 Rigged mesh support #5 --- indra/newview/gltf/llgltfloader.cpp | 110 ++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 24 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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; -- 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/newview/gltf/llgltfloader.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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 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/llgltfloader.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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); } -- cgit v1.2.3 From 136149d1a196d2c0c15b9977937e64ccd26c1a49 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Fri, 30 May 2025 03:06:33 +0300 Subject: #4191 skip loading model compressed with Draco --- indra/newview/gltf/llgltfloader.cpp | 43 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') 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); + } +} + -- cgit v1.2.3 From 4ae9b7b24564a5d50360781d2b63f73a6aa2141c Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 2 Jun 2025 19:43:48 +0300 Subject: #4203 show valid log info about 16 bit limit --- indra/newview/gltf/llgltfloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5cdd7f09e0..800b986b59 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -648,7 +648,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else { LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; LLSD args; - args["Message"] = "ParsingErrorBadElement"; + args["Message"] = "ErrorIndexLimit"; mWarningsArray.append(args); return false; } -- cgit v1.2.3 From b322b3f6f11d6eb9d4113cf1b344d05daf2c1be0 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 3 Jun 2025 13:25:02 +0300 Subject: #4097 Fix crash in LLGLTFLoader::populateModelFromMesh() --- indra/newview/gltf/llgltfloader.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 800b986b59..4d705279a5 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -450,9 +450,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& GLTFVertex vert; vert.position = glm::vec3(transformed_pos); - // Use pre-computed normal_transform - glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.normal = glm::normalize(normal_transform * normal_vec); + if (!prim.mNormals.empty()) + { + // Use pre-computed normal_transform + glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.normal = glm::normalize(normal_transform * normal_vec); + } + else + { + // Use default normal (pointing up in model space) + vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); + LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; + } vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); -- cgit v1.2.3 From acd4de66589e960da24cd39fe44083f80a293b05 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 3 Jun 2025 20:27:25 +0300 Subject: #4080 Rigged mesh support #7 --- indra/newview/gltf/llgltfloader.cpp | 210 ++++++++++++++++++++++++------------ 1 file changed, 140 insertions(+), 70 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 4d705279a5..c99d7c2993 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -153,9 +153,9 @@ bool LLGLTFLoader::parseMeshes() // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. - for (auto& skin : mGLTFAsset.mSkins) + for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) { - populateJointFromSkin(skin); + populateJointFromSkin(i); } // Track how many times each mesh name has been used @@ -293,6 +293,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& base_name = "mesh_" + std::to_string(mesh_index); } + LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; + if (instance_count > 0) { pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); @@ -672,16 +674,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; - size_t jointCnt = gltf_skin.mJoints.size(); - if (gltf_skin.mInverseBindMatrices >= 0 && jointCnt != gltf_skin.mInverseBindMatricesData.size()) - { - LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; - LLSD args; - args["Message"] = "InvBindCountMismatch"; - mWarningsArray.append(args); - } - std::vector gltfindex_to_joitindex_map; + size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; @@ -711,44 +705,24 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); - if (i < gltf_skin.mInverseBindMatricesData.size()) - { - // Use pre-computed coord_system_rotation instead of recreating it - LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i]; - - glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); - glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); - - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); - skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform)); - - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + // In scope of same skin multiple meshes reuse same bind matrices + skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[skinIdx][i]); - // For alternate bind matrix, use the ORIGINAL joint transform (before rotation) - // Get the original joint node and use its matrix directly - S32 joint = gltf_skin.mJoints[i]; - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJoint(joint, gltf_skin); // skeleton can have multiple real roots - if (root_joint == joint) - { - // This is very likely incomplete in some way. - // Root shouldn't be the only one to need full coordinate fix - joint_mat = coord_system_rotation * joint_mat; - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); - - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); - } - else + // For alternate bind matrix, use the ORIGINAL joint transform (before rotation) + // Get the original joint node and use its matrix directly + // Todo: this seems blatantly wrong, it should have been rotated + glm::mat4 joint_mat = jointNode.mMatrix; + S32 root_joint = findValidRootJointNode(joint, gltf_skin); // skeleton can have multiple real roots + if (root_joint == joint) { - // For gltf mInverseBindMatrices are optional, but not for viewer - // todo: get a model that triggers this - skin_info.mInvBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); // might need to be an 'identity' - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(mJointList[legal_name])); + // This is very likely incomplete in some way. + // Root shouldn't be the only one to need full coordinate fix + joint_mat = coord_system_rotation * joint_mat; } + LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; + skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } // Remap indices for pModel->mSkinWeights @@ -764,23 +738,71 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& return true; } -void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) +void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { - LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL; + const LL::GLTF::Skin& skin = mGLTFAsset.mSkins[skin_idx]; - for (auto joint : skin.mJoints) + LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing skin " << skin_idx << " with " << skin.mJoints.size() << " joints" << LL_ENDL; + + if (skin.mInverseBindMatrices > 0 && skin.mJoints.size() != skin.mInverseBindMatricesData.size()) { - auto jointNode = mGLTFAsset.mNodes[joint]; + LL_INFOS("GLTF_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL; + LLSD args; + args["Message"] = "InvBindCountMismatch"; + mWarningsArray.append(args); + } + S32 joint_count = (S32)skin.mJoints.size(); + S32 inverse_count = (S32)skin.mInverseBindMatricesData.size(); + if (mInverseBindMatrices.size() <= skin_idx) + { + mInverseBindMatrices.resize(skin_idx + 1); + } + + for (S32 i = 0; i < joint_count; i++) + { + S32 joint = skin.mJoints[i]; + LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); + bool legal_joint = false; if (mJointMap.find(legal_name) != mJointMap.end()) { legal_name = mJointMap[legal_name]; + legal_joint = true; + } + + if (!legal_joint) + { + // add placeholder to not break index + LLMatrix4 gltf_transform; + gltf_transform.setIdentity(); + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + else if (inverse_count > i) + { + LL::GLTF::mat4 gltf_mat = skin.mInverseBindMatricesData[i]; + + // Todo: there should be a simpler way + glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); + glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; + glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); + + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { - // ignore unrecognized joint - LL_DEBUGS("GLTF") << "Ignoring joint: " << legal_name << LL_ENDL; + LLMatrix4 gltf_transform; + gltf_transform.setIdentity(); + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); + } + // todo: prepare mAlternateBindMatrix here + + if (!legal_joint) + { + LL_DEBUGS("GLTF") << "Ignoring unrecognized joint: " << legal_name << LL_ENDL; continue; } @@ -812,14 +834,45 @@ void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) } } -S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const + +S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const { - S32 root_joint = 0; - S32 found_joint = source_joint; + S32 source_joint_node = gltf_skin.mJoints[source_joint]; + S32 root_node = source_joint_node; + S32 found_node = source_joint_node; S32 size = (S32)gltf_skin.mJoints.size(); do { - root_joint = found_joint; + root_node = found_node; + for (S32 i = 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); + if (it != jointNode.mChildren.end()) + { + // Found node's parent + found_node = joint; + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + return i; + } + break; + } + } + } while (root_node != found_node); + + return -1; +} + +S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const +{ + S32 root_node = 0; + S32 found_node = source_joint_node; + S32 size = (S32)gltf_skin.mJoints.size(); + do + { + root_node = found_node; for (S32 i = 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; @@ -827,44 +880,61 @@ S32 LLGLTFLoader::findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& glt if (mJointMap.find(jointNode.mName) != mJointMap.end()) { - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); if (it != jointNode.mChildren.end()) { - found_joint = joint; + // Found node's parent + found_node = joint; break; } } } - } while (root_joint != found_joint); + } while (root_node != found_node); - return root_joint; + return root_node; } -S32 LLGLTFLoader::findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const +S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const { - S32 root_joint = 0; - S32 found_joint = 0; + S32 root_node = 0; + S32 found_node = 0; S32 size = (S32)gltf_skin.mJoints.size(); do { - root_joint = found_joint; + root_node = found_node; for (S32 i = 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_joint); + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); if (it != jointNode.mChildren.end()) { - found_joint = joint; + // Found node's parent + found_node = joint; break; } } - } while (root_joint != found_joint); + } while (root_node != found_node); LL_INFOS("GLTF_DEBUG") << "mJointList name: "; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_joint]; - LL_CONT << jointNode.mName << " index: " << root_joint << LL_ENDL; - return root_joint; + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node]; + LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL; + return root_node; +} + +S32 LLGLTFLoader::findParentNode(S32 node) const +{ + S32 size = (S32)mGLTFAsset.mNodes.size(); + for (S32 i = 0; i < size; i++) + { + const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i]; + std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node); + if (it != jointNode.mChildren.end()) + { + return i; + } + } + return -1; } bool LLGLTFLoader::parseMaterials() -- cgit v1.2.3 From 11ece6840b034973a33626e98bd2412f0577ac5a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 18:00:14 +0300 Subject: #4214 Support mesh splitting for meshes with more than 8 materials --- indra/newview/gltf/llgltfloader.cpp | 107 +++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 8 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c99d7c2993..e330c1d611 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -102,7 +102,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, maxJointsPerMesh ), //mPreprocessGLTF(preprocess), mMeshesLoaded(false), - mMaterialsLoaded(false) + mMaterialsLoaded(false), + mGeneratedModelLimit(modelLimit) { } @@ -135,6 +136,99 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) return (mMeshesLoaded); } +void LLGLTFLoader::addModelToScene( + LLModel* pModel, + U32 submodel_limit, + const LLMatrix4& transformation, + const LLVolumeParams& volume_params) +{ + U32 volume_faces = pModel->getNumVolumeFaces(); + + // Side-steps all manner of issues when splitting models + // and matching lower LOD materials to base models + // + pModel->sortVolumeFacesByMaterialName(); + + int submodelID = 0; + + // remove all faces that definitely won't fit into one model and submodel limit + U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES; + if (face_limit < volume_faces) + { + pModel->setNumVolumeFaces(face_limit); + } + + LLVolume::face_list_t remainder; + std::vector ready_models; + LLModel* current_model = pModel; + do + { + current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); + + // remove unused/redundant vertices after normalizing + current_model->remapVolumeFaces(); + + volume_faces = static_cast(remainder.size()); + + // Don't add to scene yet because weights and materials aren't ready. + // Just save it + ready_models.push_back(current_model); + + // If we have left-over volume faces, create another model + // to absorb them. + if (volume_faces) + { + LLModel* next = new LLModel(volume_params, 0.f); + next->ClearFacesAndMaterials(); + next->mSubmodelID = ++submodelID; + next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; + next->getVolumeFaces() = remainder; + next->mNormalizedScale = current_model->mNormalizedScale; + next->mNormalizedTranslation = current_model->mNormalizedTranslation; + next->mSkinWeights = current_model->mSkinWeights; + next->mPosition = current_model->mPosition; + + const LLMeshSkinInfo& current_skin_info = current_model->mSkinInfo; + LLMeshSkinInfo& next_skin_info = next->mSkinInfo; + next_skin_info.mJointNames = current_skin_info.mJointNames; + next_skin_info.mJointNums = current_skin_info.mJointNums; + next_skin_info.mBindShapeMatrix = current_skin_info.mBindShapeMatrix; + next_skin_info.mInvBindMatrix = current_skin_info.mInvBindMatrix; + next_skin_info.mAlternateBindMatrix = current_skin_info.mAlternateBindMatrix; + next_skin_info.mPelvisOffset = current_skin_info.mPelvisOffset; + + + if (current_model->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES) + { + next->mMaterialList.assign(current_model->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, current_model->mMaterialList.end()); + current_model->mMaterialList.resize(LL_SCULPT_MESH_MAX_FACES); + } + + current_model = next; + } + + remainder.clear(); + + } while (volume_faces); + + for (auto model : ready_models) + { + // remove unused/redundant vertices + current_model->remapVolumeFaces(); + // Todo: go over skin weights, joints, matrices and remove unused ones + + mModelList.push_back(model); + + std::map materials; + for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) + { + materials[model->mMaterialList[i]] = LLImportMaterial(); + } + mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); + stretch_extents(model, transformation); + } +} + bool LLGLTFLoader::parseMeshes() { if (!mGltfLoaded) return false; @@ -160,6 +254,7 @@ bool LLGLTFLoader::parseMeshes() // Track how many times each mesh name has been used std::map mesh_name_counts; + U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; // Process each node for (auto& node : mGLTFAsset.mNodes) @@ -188,8 +283,6 @@ bool LLGLTFLoader::parseMeshes() (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { - mModelList.push_back(pModel); - mTransform.setIdentity(); transformation = mTransform; @@ -234,8 +327,7 @@ bool LLGLTFLoader::parseMeshes() mWarningsArray.append(args); } - mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); - stretch_extents(pModel, transformation); + addModelToScene(pModel, submodel_limit, transformation, volume_params); } else { @@ -347,8 +439,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - auto prims = mesh.mPrimitives; - for (auto prim : prims) + for (const LL::GLTF::Primitive& prim : mesh.mPrimitives) { // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. if (prim.getVertexCount() < USHRT_MAX) @@ -471,7 +562,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { vert.weights = glm::vec4(prim.mWeights[i]); - auto accessorIdx = prim.mAttributes["JOINTS_0"]; + auto accessorIdx = prim.mAttributes.at("JOINTS_0"); LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; if (accessorIdx >= 0) { -- cgit v1.2.3 From 08f6f5c697fce4ccbfba357ab9ce5af915dd0574 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 20:57:10 +0300 Subject: #4214 Weights and Joints remap --- indra/newview/gltf/llgltfloader.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index e330c1d611..60c6832058 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -165,9 +165,6 @@ void LLGLTFLoader::addModelToScene( { current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); - // remove unused/redundant vertices after normalizing - current_model->remapVolumeFaces(); - volume_faces = static_cast(remainder.size()); // Don't add to scene yet because weights and materials aren't ready. @@ -215,7 +212,8 @@ void LLGLTFLoader::addModelToScene( { // remove unused/redundant vertices current_model->remapVolumeFaces(); - // Todo: go over skin weights, joints, matrices and remove unused ones + // remove unused/redundant weights and joints + current_model->remapSkinWeightsAndJoints(); mModelList.push_back(model); -- cgit v1.2.3 From 74d990872c8d8f59083b868a7ae8df5e90df62c2 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 5 Jun 2025 21:07:19 +0300 Subject: #4214 Fix material upload --- indra/newview/gltf/llgltfloader.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 60c6832058..1d7b44e566 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -140,7 +140,8 @@ void LLGLTFLoader::addModelToScene( LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, - const LLVolumeParams& volume_params) + const LLVolumeParams& volume_params, + const material_map& mats) { U32 volume_faces = pModel->getNumVolumeFaces(); @@ -220,7 +221,15 @@ void LLGLTFLoader::addModelToScene( std::map materials; for (U32 i = 0; i < (U32)model->mMaterialList.size(); ++i) { - materials[model->mMaterialList[i]] = LLImportMaterial(); + material_map::const_iterator found = mats.find(model->mMaterialList[i]); + if (found != mats.end()) + { + materials[model->mMaterialList[i]] = found->second; + } + else + { + materials[model->mMaterialList[i]] = LLImportMaterial(); + } } mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); stretch_extents(model, transformation); @@ -325,7 +334,8 @@ bool LLGLTFLoader::parseMeshes() mWarningsArray.append(args); } - addModelToScene(pModel, submodel_limit, transformation, volume_params); + addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + mats.clear(); } else { @@ -815,6 +825,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } // Remap indices for pModel->mSkinWeights + // Todo: this is now partially redundant due to + // remapSkinWeightsAndJoints being called later. + // Consider storing all joints now as is and let it + // ramap later due to missing weights. for (auto& weights : pModel->mSkinWeights) { for (auto& weight : weights.second) -- cgit v1.2.3 From 35398524e560f2c89e59672cefd5c9ff60c9aa37 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 15:32:42 +0300 Subject: Fix split model vertex/joint remapping to use correct model variable --- indra/newview/gltf/llgltfloader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1d7b44e566..528b932dd3 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -212,9 +212,9 @@ void LLGLTFLoader::addModelToScene( for (auto model : ready_models) { // remove unused/redundant vertices - current_model->remapVolumeFaces(); + model->remapVolumeFaces(); // remove unused/redundant weights and joints - current_model->remapSkinWeightsAndJoints(); + model->remapSkinWeightsAndJoints(); mModelList.push_back(model); -- cgit v1.2.3 From 689b829b67204c3097d1ac6abf69916c45526b43 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 16:10:55 +0300 Subject: #4170 Fix GLTF import missing mesh parts from transform tools Process entire node hierarchy instead of only nodes with meshes. GLTF transform tools often create intermediate transform nodes without meshes that were being skipped, causing child meshes to be omitted from import. --- indra/newview/gltf/llgltfloader.cpp | 186 +++++++++++++++++++++++------------- 1 file changed, 118 insertions(+), 68 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 528b932dd3..edcc716298 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -263,91 +263,141 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Process each node - for (auto& node : mGLTFAsset.mNodes) - { - LLMatrix4 transformation; - material_map mats; - auto meshidx = node.mMesh; + // Mark which nodes have been processed to avoid duplicates + std::vector node_processed(mGLTFAsset.mNodes.size(), false); - if (meshidx >= 0) + // First, find root nodes (nodes without parents) and process their hierarchies + for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + { + if (!node_processed[node_idx]) { - if (mGLTFAsset.mMeshes.size() > meshidx) + // Check if this node has a parent + bool has_parent = false; + for (const auto& potential_parent : mGLTFAsset.mNodes) { - LLModel* pModel = new LLModel(volume_params, 0.f); - auto mesh = mGLTFAsset.mMeshes[meshidx]; - - // Get base mesh name and track usage - std::string base_name = mesh.mName; - if (base_name.empty()) + if (std::find(potential_parent.mChildren.begin(), + potential_parent.mChildren.end(), + static_cast(node_idx)) != potential_parent.mChildren.end()) { - base_name = "mesh_" + std::to_string(meshidx); + has_parent = true; + break; } + } - S32 instance_count = mesh_name_counts[base_name]++; + // If no parent, this is a root node - process its hierarchy + if (!has_parent) + { + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + } + } + } - if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && - (LLModel::NO_ERRORS == pModel->getStatus()) && - validate_model(pModel)) - { - mTransform.setIdentity(); - transformation = mTransform; + // Process any remaining unprocessed nodes (disconnected nodes) + for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + { + if (!node_processed[node_idx]) + { + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + } + } - // adjust the transformation to compensate for mesh normalization - LLVector3 mesh_scale_vector; - LLVector3 mesh_translation_vector; - pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + return true; +} - LLMatrix4 mesh_translation; - mesh_translation.setTranslation(mesh_translation_vector); - mesh_translation *= transformation; - transformation = mesh_translation; +void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed) +{ + if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) + return; - LLMatrix4 mesh_scale; - mesh_scale.initScale(mesh_scale_vector); - mesh_scale *= transformation; - transformation = mesh_scale; + if (node_processed[node_idx]) + return; - if (node.mSkin >= 0) - { - // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh - // into the coordinate space of the joints. - // In GLTF, this matrix is omitted, and it is assumed that this transform is either - // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. - // - // TODO: There appears to be missing rotation when joints rotate the model - // or inverted bind matrices are missing inherited rotation - // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly - // prior to skinning) - - pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); - LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; - } + node_processed[node_idx] = true; - if (transformation.determinant() < 0) - { // negative scales are not supported - LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " - << pModel->mLabel << LL_ENDL; - LLSD args; - args["Message"] = "NegativeScaleNormTrans"; - args["LABEL"] = pModel->mLabel; - mWarningsArray.append(args); - } + auto& node = mGLTFAsset.mNodes[node_idx]; - addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); - mats.clear(); - } - else - { - setLoadState(ERROR_MODEL + pModel->getStatus()); - delete pModel; - return false; - } + // Process this node's mesh if it has one + if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) + { + LLMatrix4 transformation; + material_map mats; + + LLModel* pModel = new LLModel(volume_params, 0.f); + auto& mesh = mGLTFAsset.mMeshes[node.mMesh]; + + // Get base mesh name and track usage + std::string base_name = mesh.mName; + if (base_name.empty()) + { + base_name = "mesh_" + std::to_string(node.mMesh); + } + + S32 instance_count = mesh_name_counts[base_name]++; + + if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && + (LLModel::NO_ERRORS == pModel->getStatus()) && + validate_model(pModel)) + { + mTransform.setIdentity(); + transformation = mTransform; + + // adjust the transformation to compensate for mesh normalization + LLVector3 mesh_scale_vector; + LLVector3 mesh_translation_vector; + pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transformation; + transformation = mesh_translation; + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transformation; + transformation = mesh_scale; + + if (node.mSkin >= 0) + { + // "Bind Shape Matrix" is supposed to transform the geometry of the skinned mesh + // into the coordinate space of the joints. + // In GLTF, this matrix is omitted, and it is assumed that this transform is either + // premultiplied with the mesh data, or postmultiplied to the inverse bind matrices. + // + // TODO: There appears to be missing rotation when joints rotate the model + // or inverted bind matrices are missing inherited rotation + // (based of values the 'bento shoes' mesh might be missing 90 degrees horizontaly + // prior to skinning) + + pModel->mSkinInfo.mBindShapeMatrix.loadu(mesh_scale); + LL_INFOS("GLTF_DEBUG") << "Model: " << pModel->mLabel << " mBindShapeMatrix: " << pModel->mSkinInfo.mBindShapeMatrix << LL_ENDL; } + + if (transformation.determinant() < 0) + { // negative scales are not supported + LL_INFOS("GLTF_IMPORT") << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " + << pModel->mLabel << LL_ENDL; + LLSD args; + args["Message"] = "NegativeScaleNormTrans"; + args["LABEL"] = pModel->mLabel; + mWarningsArray.append(args); + } + + addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + mats.clear(); + } + else + { + setLoadState(ERROR_MODEL + pModel->getStatus()); + delete pModel; + return; } } - return true; + // Process all children + for (S32 child_idx : node.mChildren) + { + processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params, node_processed); + } } void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const -- cgit v1.2.3 From 6924862d2ef1ef6af6a04c013aebeecbb5717bde Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 17:22:02 +0300 Subject: #4170 Follow-up: Optimize GLTF node hierarchy traversal --- indra/newview/gltf/llgltfloader.cpp | 48 +++++++++++++------------------------ 1 file changed, 16 insertions(+), 32 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index edcc716298..17de6eb829 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -263,57 +263,41 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Mark which nodes have been processed to avoid duplicates - std::vector node_processed(mGLTFAsset.mNodes.size(), false); + // Build parent mapping for efficient traversal + std::vector node_parents(mGLTFAsset.mNodes.size(), -1); + std::vector is_root(mGLTFAsset.mNodes.size(), true); - // First, find root nodes (nodes without parents) and process their hierarchies - for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + // Build parent relationships + for (size_t parent_idx = 0; parent_idx < mGLTFAsset.mNodes.size(); parent_idx++) { - if (!node_processed[node_idx]) + const auto& parent_node = mGLTFAsset.mNodes[parent_idx]; + for (S32 child_idx : parent_node.mChildren) { - // Check if this node has a parent - bool has_parent = false; - for (const auto& potential_parent : mGLTFAsset.mNodes) - { - if (std::find(potential_parent.mChildren.begin(), - potential_parent.mChildren.end(), - static_cast(node_idx)) != potential_parent.mChildren.end()) - { - has_parent = true; - break; - } - } - - // If no parent, this is a root node - process its hierarchy - if (!has_parent) + if (child_idx >= 0 && child_idx < static_cast(mGLTFAsset.mNodes.size())) { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + node_parents[child_idx] = static_cast(parent_idx); + is_root[child_idx] = false; } } } - // Process any remaining unprocessed nodes (disconnected nodes) + // Process all root nodes and their hierarchies for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) { - if (!node_processed[node_idx]) + if (is_root[node_idx]) { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params, node_processed); + processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params); } } return true; } -void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params, std::vector& node_processed) +void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params) { if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) return; - if (node_processed[node_idx]) - return; - - node_processed[node_idx] = true; - auto& node = mGLTFAsset.mNodes[node_idx]; // Process this node's mesh if it has one @@ -393,10 +377,10 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map } } - // Process all children + // Process all children recursively for (S32 child_idx : node.mChildren) { - processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params, node_processed); + processNodeHierarchy(child_idx, mesh_name_counts, submodel_limit, volume_params); } } -- cgit v1.2.3 From b20d10c0cc96cfcd93468b8e31e47ab1977a9555 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 16:39:51 +0300 Subject: #4148 Skeleton Translation --- indra/newview/gltf/llgltfloader.cpp | 87 +++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 9 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 17de6eb829..7cf26941ad 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -87,7 +87,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, JointNameSet & jointsFromNodes, std::map &jointAliasMap, U32 maxJointsPerMesh, - U32 modelLimit) //, + U32 modelLimit, + joint_rest_map_t jointRestMatrices) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -103,7 +104,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, //mPreprocessGLTF(preprocess), mMeshesLoaded(false), mMaterialsLoaded(false), - mGeneratedModelLimit(modelLimit) + mGeneratedModelLimit(modelLimit), + mJointRestMatrices(jointRestMatrices) { } @@ -917,20 +919,24 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } else if (inverse_count > i) { - LL::GLTF::mat4 gltf_mat = skin.mInverseBindMatricesData[i]; - - // Todo: there should be a simpler way - glm::mat4 original_bind_matrix = glm::inverse(gltf_mat); + glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original); - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix)); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + glm::mat4 tranlated_original = skeleton_transform * rotated_original; + glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { - LLMatrix4 gltf_transform; + glm::mat4 inv_bind(1.0f); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + inv_bind = glm::inverse(skeleton_transform * inv_bind); + + LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); gltf_transform.setIdentity(); LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); @@ -1074,6 +1080,69 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const +{ + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? + + if (joint_node_index < 0 || joint_node_index >= static_cast(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + const auto& node = mGLTFAsset.mNodes[joint_node_index]; + + // Find and apply parent transform if it exists + for (size_t i = 0; i < mGLTFAsset.mNodes.size(); ++i) + { + const auto& potential_parent = mGLTFAsset.mNodes[i]; + auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), joint_node_index); + + if (it != potential_parent.mChildren.end()) + { + // Found parent + if (std::find(gltf_skin.mJoints.begin(), gltf_skin.mJoints.end(), joint_node_index) != gltf_skin.mJoints.end()) + { + // parent is a joint - recursively combine transform + // assumes that matrix is already valid + return buildGltfRestMatrix(static_cast(i), gltf_skin) * node.mMatrix; + } + } + } + // Should we return armature or stop earlier? + return node.mMatrix; +} + +// This function computes the transformation matrix needed to convert from GLTF skeleton space +// to viewer skeleton space for a specific joint +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 gltf_joint_index, const std::string& joint_name) const +{ + joint_rest_map_t::const_iterator found = mJointRestMatrices.find(joint_name); + if (found == mJointRestMatrices.end()) + { + // For now assume they are identical and return an identity (for ease of debuging) + // But there should be no joints viewer isn't aware about + // Warn or assert about missing joints + return glm::mat4(1.0f); + } + glm::mat4 viewer_joint_rest_pose = found->second; + + // Get the GLTF joint's rest pose (in GLTF coordinate system) + S32 joint_node_index = gltf_skin.mJoints[gltf_joint_index]; + glm::mat4 gltf_joint_rest_pose = buildGltfRestMatrix(joint_node_index, gltf_skin); + gltf_joint_rest_pose = coord_system_rotation * gltf_joint_rest_pose; + + LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; + + LLMatrix4 transform(glm::value_ptr(gltf_joint_rest_pose)); + + LL_CONT << transform << LL_ENDL; + + // Compute transformation from GLTF space to viewer space + // This assumes both skeletons are in rest pose initially + return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; -- cgit v1.2.3 From b4fb66c4a2173b2faf717880f0084381f2055cca Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 9 Jun 2025 22:56:43 +0300 Subject: #4170 Use GLTF scene definition for node traversal Process nodes through the scene hierarchy as defined in the GLTF file instead of attempting to reconstruct parent-child relationships. This ensures proper import of models created by GLTF transform tools. --- indra/newview/gltf/llgltfloader.cpp | 47 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7cf26941ad..2dc1aa369b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -265,31 +265,32 @@ bool LLGLTFLoader::parseMeshes() std::map mesh_name_counts; U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; - // Build parent mapping for efficient traversal - std::vector node_parents(mGLTFAsset.mNodes.size(), -1); - std::vector is_root(mGLTFAsset.mNodes.size(), true); - - // Build parent relationships - for (size_t parent_idx = 0; parent_idx < mGLTFAsset.mNodes.size(); parent_idx++) + // Check if we have scenes defined + if (!mGLTFAsset.mScenes.empty()) { - const auto& parent_node = mGLTFAsset.mNodes[parent_idx]; - for (S32 child_idx : parent_node.mChildren) + // Process the default scene (or first scene if no default) + S32 scene_idx = mGLTFAsset.mScene >= 0 ? mGLTFAsset.mScene : 0; + + if (scene_idx < mGLTFAsset.mScenes.size()) { - if (child_idx >= 0 && child_idx < static_cast(mGLTFAsset.mNodes.size())) + const LL::GLTF::Scene& scene = mGLTFAsset.mScenes[scene_idx]; + + LL_INFOS("GLTF_IMPORT") << "Processing scene " << scene_idx << " with " << scene.mNodes.size() << " root nodes" << LL_ENDL; + + // Process all root nodes defined in the scene + for (S32 root_idx : scene.mNodes) { - node_parents[child_idx] = static_cast(parent_idx); - is_root[child_idx] = false; + if (root_idx >= 0 && root_idx < static_cast(mGLTFAsset.mNodes.size())) + { + processNodeHierarchy(root_idx, mesh_name_counts, submodel_limit, volume_params); + } } } } - - // Process all root nodes and their hierarchies - for (size_t node_idx = 0; node_idx < mGLTFAsset.mNodes.size(); node_idx++) + else { - if (is_root[node_idx]) - { - processNodeHierarchy(static_cast(node_idx), mesh_name_counts, submodel_limit, volume_params); - } + LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + return false; } return true; @@ -302,6 +303,10 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map auto& node = mGLTFAsset.mNodes[node_idx]; + LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" + << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") + << " - children: " << node.mChildren.size() << LL_ENDL; + // Process this node's mesh if it has one if (node.mMesh >= 0 && node.mMesh < mGLTFAsset.mMeshes.size()) { @@ -378,6 +383,12 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map return; } } + else if (node.mMesh >= 0) + { + // Log invalid mesh reference + LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " references invalid mesh " << node.mMesh + << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + } // Process all children recursively for (S32 child_idx : node.mChildren) -- cgit v1.2.3 From 4bbd6319c7db61d12a2a14c5f6f4bc6d6b5e0645 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 10 Jun 2025 14:28:07 +0300 Subject: #4148 Skeleton Translation #2 --- indra/newview/gltf/llgltfloader.cpp | 86 ++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2dc1aa369b..8fe5a8f2fb 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -76,6 +76,13 @@ static const glm::mat4 coord_system_rotation( ); +static const glm::mat4 coord_system_rotationxy( + 0.f, 1.f, 0.f, 0.f, + -1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f +); + LLGLTFLoader::LLGLTFLoader(std::string filename, S32 lod, LLModelLoader::load_callback_t load_cb, @@ -254,6 +261,11 @@ bool LLGLTFLoader::parseMeshes() node.makeMatrixValid(); } + if (mGLTFAsset.mSkins.size() > 0) + { + checkForXYrotation(mGLTFAsset.mSkins[0]); + } + // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) @@ -461,7 +473,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform); // Combine transforms: coordinate rotation applied to hierarchy transform - const glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + glm::mat4 final_transform = coord_system_rotation * hierarchy_transform; + if (mApplyXYRotation) + { + final_transform = coord_system_rotationxy * final_transform; + } // Check if we have a negative scale (flipped coordinate system) bool hasNegativeScale = glm::determinant(final_transform) < 0.0f; @@ -864,6 +880,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // This is very likely incomplete in some way. // Root shouldn't be the only one to need full coordinate fix joint_mat = coord_system_rotation * joint_mat; + if (mApplyXYRotation) + { + joint_mat = coord_system_rotationxy * joint_mat; + } } LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); @@ -1151,9 +1171,73 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially + if (mApplyXYRotation) + { + return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); + } return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); } +bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) +{ + glm::mat4 gltf_joint_rest = buildGltfRestMatrix(joint_idx, gltf_skin); + glm::mat4 test_mat = glm::inverse(gltf_joint_rest) * gltf_skin.mInverseBindMatricesData[bind_indx]; + // Normally for shoulders it should be something close to + // {1,0,0,0;0,-1,0,0;0,0,-1,0;0,0,0,1} + // rotated one will look like + // {0,0,0,-1;1,0,0,0;0,-1,0,0;0,0,0,1} + // Todo: This is a cheap hack, + // figure out how rotation is supposed to work + return abs(test_mat[0][0]) < 0.5 && abs(test_mat[1][1]) < 0.5 && abs(test_mat[2][2]) < 0.5; +} + +void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) +{ + // HACK: figure out model's rotation from shoulders' matrix. + // This is wrong on many levels: + // Too limited (only models that have shoulders), + // Will not work well with things that emulate 3 hands in some manner + // Only supports xy 90 degree rotation + // Todo: figure out how to find skeleton's orientation Correctly + // when model is rotated at a triangle level + constexpr char right_shoulder_str[] = "mShoulderRight"; + constexpr char left_shoulder_str[] = "mShoulderLeft"; + + S32 size = (S32)gltf_skin.mJoints.size(); + S32 joints_found = 0; + for (S32 i= 0; i < size; i++) + { + S32 joint = gltf_skin.mJoints[i]; + auto joint_node = mGLTFAsset.mNodes[joint]; + + // todo: we are doing this search thing everywhere, + // just pre-translate every joint + JointMap::iterator found = mJointMap.find(joint_node.mName); + if (found == mJointMap.end()) + { + // unsupported joint + continue; + } + if (found->second == right_shoulder_str || found->second == left_shoulder_str) + { + if (checkForXYrotation(gltf_skin, joint, i)) + { + joints_found++; + } + else + { + return; + } + } + } + + if (joints_found == 2) + { + // Both joints in a weird position/rotation, assume rotated model + mApplyXYRotation = true; + } +} + bool LLGLTFLoader::parseMaterials() { if (!mGltfLoaded) return false; -- cgit v1.2.3 From b3fd05fa97ee90b68fb6003db68afcafeb2c2b95 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 10 Jun 2025 19:36:17 +0300 Subject: #4114 Improve GLTF mesh uploader log --- indra/newview/gltf/llgltfloader.cpp | 46 +++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 8fe5a8f2fb..d0a0a0b541 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -302,6 +302,10 @@ bool LLGLTFLoader::parseMeshes() else { LL_WARNS("GLTF_IMPORT") << "No scenes defined in GLTF file" << LL_ENDL; + + LLSD args; + args["Message"] = "NoScenesFound"; + mWarningsArray.append(args); return false; } @@ -398,8 +402,16 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map else if (node.mMesh >= 0) { // Log invalid mesh reference - LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " references invalid mesh " << node.mMesh + LL_WARNS("GLTF_IMPORT") << "Node " << node_idx << " (" << node.mName + << ") references invalid mesh " << node.mMesh << " (total meshes: " << mGLTFAsset.mMeshes.size() << ")" << LL_ENDL; + + LLSD args; + args["Message"] = "InvalidMeshReference"; + args["NODE_NAME"] = node.mName; + args["MESH_INDEX"] = node.mMesh; + args["TOTAL_MESHES"] = static_cast(mGLTFAsset.mMeshes.size()); + mWarningsArray.append(args); } // Process all children recursively @@ -510,8 +522,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - for (const LL::GLTF::Primitive& prim : mesh.mPrimitives) + for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) { + const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. if (prim.getVertexCount() < USHRT_MAX) { @@ -566,7 +579,14 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& impMat.mDiffuseMapFilename = filename; impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename + << " for material: " << material->mName << LL_ENDL; + + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; + args["MATERIAL_NAME"] = material->mName; + mWarningsArray.append(args); // If the image has a texture loaded already, use it if (image.mTexture.notNull()) @@ -663,10 +683,15 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (prim.getIndexCount() % 3 != 0) { - LL_WARNS("GLTF_IMPORT") << "Invalid primitive: index count " << prim.getIndexCount() - << " is not divisible by 3. GLTF files must contain triangulated geometry." << LL_ENDL; + LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx + << ": Invalid index count " << prim.getIndexCount() + << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; + LLSD args; args["Message"] = "InvalidGeometryNonTriangulated"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); mWarningsArray.append(args); continue; // Skip this primitive } @@ -818,10 +843,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& pModel->getMaterialList().push_back(materialName); mats[materialName] = impMat; } - else { - LL_INFOS("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; + else + { + LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName + << "' primitive " << prim_idx + << " due to 16-bit index limits. Vertex count: " + << prim.getVertexCount() << " exceeds limit: " << USHRT_MAX << LL_ENDL; LLSD args; args["Message"] = "ErrorIndexLimit"; + args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; + args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); + args["LIMIT"] = USHRT_MAX; mWarningsArray.append(args); return false; } -- cgit v1.2.3 From fe10a83f69bb26eb581e143fb99c1250c355938b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 10 Jun 2025 22:23:25 +0300 Subject: #4214 Weights and Joints remap #2 --- indra/newview/gltf/llgltfloader.cpp | 52 ++++++++----------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index d0a0a0b541..3273cce70b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -499,13 +499,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Mark unsuported joints with '-1' so that they won't get added into weights // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. - std::vector gltf_joint_index_use_count; + std::vector gltf_joint_index_valid; if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx) { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; size_t jointCnt = gltf_skin.mJoints.size(); - gltf_joint_index_use_count.resize(jointCnt); + gltf_joint_index_valid.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) @@ -517,7 +517,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) == mJointMap.end()) { - gltf_joint_index_use_count[i] = -1; // mark as unsupported + gltf_joint_index_valid[i] = -1; // mark as unsupported } } } @@ -760,29 +760,25 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // don't reindex them yet, more indexes will be removed // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be // 'empty' ones - if (gltf_joint_index_use_count[vertices[i].joints.x] >= 0 + if (gltf_joint_index_valid[vertices[i].joints.x] >= 0 && vertices[i].weights.x > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); - gltf_joint_index_use_count[vertices[i].joints.x]++; } - if (gltf_joint_index_use_count[vertices[i].joints.y] >= 0 + if (gltf_joint_index_valid[vertices[i].joints.y] >= 0 && vertices[i].weights.y > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); - gltf_joint_index_use_count[vertices[i].joints.y]++; } - if (gltf_joint_index_use_count[vertices[i].joints.z] >= 0 + if (gltf_joint_index_valid[vertices[i].joints.z] >= 0 && vertices[i].weights.z > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); - gltf_joint_index_use_count[vertices[i].joints.z]++; } - if (gltf_joint_index_use_count[vertices[i].joints.w] >= 0 + if (gltf_joint_index_valid[vertices[i].joints.w] >= 0 && vertices[i].weights.w > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); - gltf_joint_index_use_count[vertices[i].joints.w]++; } std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); @@ -868,20 +864,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; - std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); - gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) { // Process joint name and idnex S32 joint = gltf_skin.mJoints[i]; - if (gltf_joint_index_use_count[i] <= 0) - { - // Unused (0) or unsupported (-1) joint, drop it - continue; - } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); @@ -889,12 +878,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - else - { - llassert(false); // should have been stopped by gltf_joint_index_use_count[i] == -1 - continue; - } - gltfindex_to_joitindex_map[i] = replacement_index++; + // else thanks to gltf_joint_index_valid any illegal + // joint should have zero uses. + // Add them anyway to preserve order, remapSkinWeightsAndJoints + // will sort them out later skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); @@ -922,19 +909,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); } - - // Remap indices for pModel->mSkinWeights - // Todo: this is now partially redundant due to - // remapSkinWeightsAndJoints being called later. - // Consider storing all joints now as is and let it - // ramap later due to missing weights. - for (auto& weights : pModel->mSkinWeights) - { - for (auto& weight : weights.second) - { - weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; - } - } } return true; @@ -1203,10 +1177,6 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - if (mApplyXYRotation) - { - return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); - } return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); } -- cgit v1.2.3 From 48eb8a2efecb3c308092ce60dcb0c9975a0c53e1 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 11 Jun 2025 01:21:45 +0300 Subject: #4147 Joint override --- indra/newview/gltf/llgltfloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3273cce70b..3e7b14919b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -898,7 +898,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { // This is very likely incomplete in some way. // Root shouldn't be the only one to need full coordinate fix - joint_mat = coord_system_rotation * joint_mat; + joint_mat = coord_system_rotation; if (mApplyXYRotation) { joint_mat = coord_system_rotationxy * joint_mat; -- cgit v1.2.3 From 1132b19c06aa0b154991256e5063c78ad423bdc3 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 11 Jun 2025 12:31:47 +0300 Subject: #4114 Improve GLTF mesh uploader log 2 --- indra/newview/gltf/llgltfloader.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3e7b14919b..ffe574f4b1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -309,6 +309,21 @@ bool LLGLTFLoader::parseMeshes() return false; } + // Check total model count against limit + U32 total_models = static_cast(mModelList.size()); + if (total_models > mGeneratedModelLimit) + { + LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models + << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL; + + LLSD args; + args["Message"] = "TooManyMeshParts"; + args["PART_COUNT"] = static_cast(total_models); + args["LIMIT"] = static_cast(mGeneratedModelLimit); + mWarningsArray.append(args); + return false; + } + return true; } @@ -843,13 +858,12 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName << "' primitive " << prim_idx - << " due to 16-bit index limits. Vertex count: " - << prim.getVertexCount() << " exceeds limit: " << USHRT_MAX << LL_ENDL; + << " due to 65,534 vertex limit. Vertex count: " + << prim.getVertexCount() << LL_ENDL; LLSD args; args["Message"] = "ErrorIndexLimit"; args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); - args["LIMIT"] = USHRT_MAX; mWarningsArray.append(args); return false; } -- cgit v1.2.3 From e2c76ec70d643727d510f2aafac1e918655dfa44 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 11 Jun 2025 18:52:41 +0300 Subject: #4147 Move mAlternateBindMatrices For reduced log spam and calculutions and to make further modifications easier. --- indra/newview/gltf/llgltfloader.cpp | 38 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index ffe574f4b1..40b9334e63 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -903,25 +903,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // In scope of same skin multiple meshes reuse same bind matrices skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[skinIdx][i]); - // For alternate bind matrix, use the ORIGINAL joint transform (before rotation) - // Get the original joint node and use its matrix directly - // Todo: this seems blatantly wrong, it should have been rotated - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJointNode(joint, gltf_skin); // skeleton can have multiple real roots - if (root_joint == joint) - { - // This is very likely incomplete in some way. - // Root shouldn't be the only one to need full coordinate fix - joint_mat = coord_system_rotation; - if (mApplyXYRotation) - { - joint_mat = coord_system_rotationxy * joint_mat; - } - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); - - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; - skin_info.mAlternateBindMatrix.push_back(LLMatrix4a(original_joint_transform)); + skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); } } @@ -992,7 +974,23 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } - // todo: prepare mAlternateBindMatrix here + + // Todo: this seems blatantly wrong + glm::mat4 joint_mat = jointNode.mMatrix; + S32 root_joint = findValidRootJointNode(joint, skin); // skeleton can have multiple real roots and one gltf root to group them + if (root_joint == joint) + { + // This is very likely incomplete in some way. + joint_mat = coord_system_rotation; + if (mApplyXYRotation) + { + joint_mat = coord_system_rotationxy * joint_mat; + } + } + LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; + mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); if (!legal_joint) { -- cgit v1.2.3 From 8322a9a61e951275278fbf80b0a46880f5318107 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 12 Jun 2025 14:21:40 +0300 Subject: #4147 Joint Overrides #2 --- indra/newview/gltf/llgltfloader.cpp | 126 +++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 58 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 40b9334e63..292fd09035 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -95,7 +95,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_rest_map_t jointRestMatrices) //, + joint_viewer_rest_map_t jointRestMatrices, + joint_viewer_parent_map_t jointParentMap) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -107,12 +108,13 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ), + maxJointsPerMesh ) //mPreprocessGLTF(preprocess), - mMeshesLoaded(false), - mMaterialsLoaded(false), - mGeneratedModelLimit(modelLimit), - mJointRestMatrices(jointRestMatrices) + , mMeshesLoaded(false) + , mMaterialsLoaded(false) + , mGeneratedModelLimit(modelLimit) + , mJointViewerRestMatrices(jointRestMatrices) + , mJointViewerParentMap(jointParentMap) { } @@ -929,6 +931,16 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) if (mInverseBindMatrices.size() <= skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); + mAlternateBindMatrices.resize(skin_idx + 1); + } + + // Build gltf rest matrices + // This is very inefficienct, todo: run from root to children, not from children to root + joint_node_mat4_map_t gltf_rest_map; + for (S32 i = 0; i < joint_count; i++) + { + S32 joint = skin.mJoints[i]; + gltf_rest_map[joint] = buildGltfRestMatrix(joint, skin); } for (S32 i = 0; i < joint_count; i++) @@ -943,86 +955,84 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) legal_joint = true; } + // Compute bind matrices + if (!legal_joint) { - // add placeholder to not break index + // Add placeholder to not break index. + // Not going to be used by viewer, will be stripped from skin_info. LLMatrix4 gltf_transform; gltf_transform.setIdentity(); mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else if (inverse_count > i) { + // Transalte existing bind matrix to viewer's skeleton + // todo: probably should be 'to viewer's overriden skeleton' glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); glm::mat4 tranlated_original = skeleton_transform * rotated_original; glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); - - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else { + // If bind matrices aren't present (they are optional in gltf), + // assume an identy matrix + // todo: find a model with this, might need to use rotated matrix glm::mat4 inv_bind(1.0f); - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(skin, i, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); inv_bind = glm::inverse(skeleton_transform * inv_bind); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); - gltf_transform.setIdentity(); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL; + LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } - // Todo: this seems blatantly wrong - glm::mat4 joint_mat = jointNode.mMatrix; - S32 root_joint = findValidRootJointNode(joint, skin); // skeleton can have multiple real roots and one gltf root to group them - if (root_joint == joint) + // Compute Alternative matrices also known as overrides + + glm::mat4 joint_mat = glm::mat4(1.f); + if (legal_joint) { - // This is very likely incomplete in some way. - joint_mat = coord_system_rotation; - if (mApplyXYRotation) + joint_viewer_parent_map_t::const_iterator found = mJointViewerParentMap.find(legal_name); + if (found != mJointViewerParentMap.end() && !found->second.empty()) { - joint_mat = coord_system_rotationxy * joint_mat; + glm::mat4 gltf_joint_rest_pose = coord_system_rotation * buildGltfRestMatrix(joint, skin); + if (mApplyXYRotation) + { + gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; + } + // Compute viewer's override by moving joint to viewer's base + // This might be overused or somewhat incorect for regular cases + // But this logic should be solid for cases where model lacks + // parent joints that viewer has. + // ex: like boots that have only knees and feet, but no pelvis, + // or anything else, in which case we take viewer's pelvis + glm::mat4 viewer_rest = mJointViewerRestMatrices[found->second]; + joint_mat = glm::inverse(viewer_rest) * gltf_joint_rest_pose; } } LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + // Viewer seems to care only about translation part, + // but for parity with collada taking original value + LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); + newInverse.setTranslation(original_joint_transform.getTranslation()); + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); - if (!legal_joint) - { - LL_DEBUGS("GLTF") << "Ignoring unrecognized joint: " << legal_name << LL_ENDL; - continue; - } - - // Debug: Log original joint matrix - glm::mat4 gltf_joint_matrix = jointNode.mMatrix; - LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' original matrix:" << LL_ENDL; - for(int i = 0; i < 4; i++) - { - LL_INFOS("GLTF_DEBUG") << " [" << gltf_joint_matrix[i][0] << ", " << gltf_joint_matrix[i][1] - << ", " << gltf_joint_matrix[i][2] << ", " << gltf_joint_matrix[i][3] << "]" << LL_ENDL; - } - - // Apply coordinate system rotation to joint transform - glm::mat4 rotated_joint_matrix = coord_system_rotation * gltf_joint_matrix; - - // Debug: Log rotated joint matrix - LL_INFOS("GLTF_DEBUG") << "Joint '" << legal_name << "' rotated matrix:" << LL_ENDL; - for(int i = 0; i < 4; i++) + if (legal_joint) { - LL_INFOS("GLTF_DEBUG") << " [" << rotated_joint_matrix[i][0] << ", " << rotated_joint_matrix[i][1] - << ", " << rotated_joint_matrix[i][2] << ", " << rotated_joint_matrix[i][3] << "]" << LL_ENDL; + // Might be needed for uploader UI to correctly identify overriden joints + // but going to be incorrect if multiple skins are present + mJointList[legal_name] = newInverse; + mJointsFromNode.push_front(legal_name); } - - LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_joint_matrix)); - mJointList[legal_name] = gltf_transform; - mJointsFromNode.push_front(legal_name); - - LL_INFOS("GLTF_DEBUG") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL; } } @@ -1164,10 +1174,11 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF // This function computes the transformation matrix needed to convert from GLTF skeleton space // to viewer skeleton space for a specific joint -glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Skin& gltf_skin, S32 gltf_joint_index, const std::string& joint_name) const + +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t& gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) const { - joint_rest_map_t::const_iterator found = mJointRestMatrices.find(joint_name); - if (found == mJointRestMatrices.end()) + joint_viewer_rest_map_t::const_iterator found = mJointViewerRestMatrices.find(joint_name); + if (found == mJointViewerRestMatrices.end()) { // For now assume they are identical and return an identity (for ease of debuging) // But there should be no joints viewer isn't aware about @@ -1177,19 +1188,18 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const LL::GLTF::Ski glm::mat4 viewer_joint_rest_pose = found->second; // Get the GLTF joint's rest pose (in GLTF coordinate system) - S32 joint_node_index = gltf_skin.mJoints[gltf_joint_index]; - glm::mat4 gltf_joint_rest_pose = buildGltfRestMatrix(joint_node_index, gltf_skin); - gltf_joint_rest_pose = coord_system_rotation * gltf_joint_rest_pose; + const glm::mat4 &gltf_joint_rest_pose = gltf_rest_map.at(gltf_node_index); + glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; - LLMatrix4 transform(glm::value_ptr(gltf_joint_rest_pose)); + LLMatrix4 transform(glm::value_ptr(rest_pose)); LL_CONT << transform << LL_ENDL; // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - return viewer_joint_rest_pose * glm::inverse(gltf_joint_rest_pose); + return viewer_joint_rest_pose * glm::inverse(rest_pose); } bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) -- cgit v1.2.3 From 54660c8931593ceb465605acf872d5227e1d2d63 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 13 Jun 2025 18:33:49 +0300 Subject: #4147 Joint Overrides #3 Remande skeleton translation from default skeleton to overriden skeleton --- indra/newview/gltf/llgltfloader.cpp | 162 ++++++++++++++++++++++++++---------- 1 file changed, 119 insertions(+), 43 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 292fd09035..c6d2bec238 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -95,8 +95,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map &jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, - joint_viewer_rest_map_t jointRestMatrices, - joint_viewer_parent_map_t jointParentMap) //, + std::vector viewer_skeleton) //, //bool preprocess) : LLModelLoader( filename, lod, @@ -113,8 +112,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, , mMeshesLoaded(false) , mMaterialsLoaded(false) , mGeneratedModelLimit(modelLimit) - , mJointViewerRestMatrices(jointRestMatrices) - , mJointViewerParentMap(jointParentMap) + , mViewerJointData(viewer_skeleton) { } @@ -934,13 +932,45 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) mAlternateBindMatrices.resize(skin_idx + 1); } - // Build gltf rest matrices - // This is very inefficienct, todo: run from root to children, not from children to root - joint_node_mat4_map_t gltf_rest_map; + // fill up joints related data + joints_data_map_t joints_data; + joints_name_to_node_map_t names_to_nodes; for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; - gltf_rest_map[joint] = buildGltfRestMatrix(joint, skin); + LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + JointNodeData& data = joints_data[joint]; + data.mNodeIdx = joint; + data.mJointListIdx = i; + data.mGltfRestMatrix = buildGltfRestMatrix(joint, skin); + data.mGltfMatrix = jointNode.mMatrix; + data.mOverrideMatrix = glm::mat4(1.f); + + if (mJointMap.find(jointNode.mName) != mJointMap.end()) + { + data.mName = mJointMap[jointNode.mName]; + data.mIsValidViewerJoint = true; + } + else + { + data.mName = jointNode.mName; + data.mIsValidViewerJoint = false; + } + names_to_nodes[data.mName] = joint; + + for (S32 child : jointNode.mChildren) + { + JointNodeData& child_data = joints_data[child]; + child_data.mParentNodeIdx = joint; + child_data.mIsParentValidViewerJoint = data.mIsValidViewerJoint; + } + } + + // Go over viewer joints and build overrides + glm::mat4 ident(1.0); + for (auto &viewer_data : mViewerJointData) + { + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); } for (S32 i = 0; i < joint_count; i++) @@ -971,7 +1001,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) // todo: probably should be 'to viewer's overriden skeleton' glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); glm::mat4 tranlated_original = skeleton_transform * rotated_original; glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); @@ -985,7 +1015,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) // assume an identy matrix // todo: find a model with this, might need to use rotated matrix glm::mat4 inv_bind(1.0f); - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(gltf_rest_map, joint, legal_name); + glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); inv_bind = glm::inverse(skeleton_transform * inv_bind); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); @@ -994,37 +1024,15 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } // Compute Alternative matrices also known as overrides - - glm::mat4 joint_mat = glm::mat4(1.f); - if (legal_joint) - { - joint_viewer_parent_map_t::const_iterator found = mJointViewerParentMap.find(legal_name); - if (found != mJointViewerParentMap.end() && !found->second.empty()) - { - glm::mat4 gltf_joint_rest_pose = coord_system_rotation * buildGltfRestMatrix(joint, skin); - if (mApplyXYRotation) - { - gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; - } - // Compute viewer's override by moving joint to viewer's base - // This might be overused or somewhat incorect for regular cases - // But this logic should be solid for cases where model lacks - // parent joints that viewer has. - // ex: like boots that have only knees and feet, but no pelvis, - // or anything else, in which case we take viewer's pelvis - glm::mat4 viewer_rest = mJointViewerRestMatrices[found->second]; - joint_mat = glm::inverse(viewer_rest) * gltf_joint_rest_pose; - } - } - LLMatrix4 original_joint_transform(glm::value_ptr(joint_mat)); + LLMatrix4 original_joint_transform(glm::value_ptr(joints_data[joint].mOverrideMatrix)); // Viewer seems to care only about translation part, // but for parity with collada taking original value LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); newInverse.setTranslation(original_joint_transform.getTranslation()); - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << original_joint_transform << LL_ENDL; - mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(original_joint_transform)); + LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; + mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); if (legal_joint) { @@ -1139,6 +1147,57 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& leftover) const +{ + glm::mat4 new_lefover(1.f); + glm::mat4 rest(1.f); + joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName); + if (found_node != names_to_nodes.end()) + { + S32 gltf_node_idx = found_node->second; + JointNodeData& node = gltf_nodes[gltf_node_idx]; + node.mIsOverrideValid = true; + + glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; + if (mApplyXYRotation) + { + gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; + } + node.mOverrideMatrix = glm::inverse(parent_rest) * gltf_joint_rest_pose; + + glm::vec3 override; + glm::vec3 skew; + glm::vec3 scale; + glm::vec4 perspective; + glm::quat rotation; + glm::decompose(node.mOverrideMatrix, scale, rotation, override, skew, perspective); + glm::vec3 translate; + glm::decompose(viewer_data.mJointMatrix, scale, rotation, translate, skew, perspective); + glm::mat4 viewer_joint = glm::recompose(scale, rotation, override, skew, perspective); + + node.mOverrideMatrix = viewer_joint; + rest = parent_rest * viewer_joint; + node.mOverrideRestMatrix = rest; + if (viewer_data.mName == "mPelvis") + { + // Todo: This is wrong, but this is a temporary + // solution for parts staying behind. + // Something is still missing with override mechanics + node.mOverrideMatrix = glm::mat4(1.f); + } + } + else + { + // No override for this joint + new_lefover = leftover * viewer_data.mJointMatrix; + rest = parent_rest * viewer_data.mJointMatrix; + } + for (LLJointData& child_data : viewer_data.mChildren) + { + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, new_lefover); + } +} + glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const { // This is inefficient since we are recalculating some joints multiple times over @@ -1172,23 +1231,40 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF return node.mMatrix; } +glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const +{ + // This is inefficient since we are recalculating some joints multiple times over + // Todo: cache it? + + if (joint_node_index < 0 || joint_node_index >= static_cast(mGLTFAsset.mNodes.size())) + { + return glm::mat4(1.0f); + } + + auto& data = joint_data.at(joint_node_index); + + if (data.mParentNodeIdx >=0) + { + return buildGltfRestMatrix(data.mParentNodeIdx, joint_data) * data.mGltfMatrix; + } + // Should we return armature or stop earlier? + return data.mGltfMatrix; +} + // This function computes the transformation matrix needed to convert from GLTF skeleton space // to viewer skeleton space for a specific joint -glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_mat4_map_t& gltf_rest_map, S32 gltf_node_index, const std::string& joint_name) const +glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const { - joint_viewer_rest_map_t::const_iterator found = mJointViewerRestMatrices.find(joint_name); - if (found == mJointViewerRestMatrices.end()) + const JointNodeData& node_data = joints_data_map.at(gltf_node_index); + if (!node_data.mIsOverrideValid) { // For now assume they are identical and return an identity (for ease of debuging) - // But there should be no joints viewer isn't aware about - // Warn or assert about missing joints return glm::mat4(1.0f); } - glm::mat4 viewer_joint_rest_pose = found->second; // Get the GLTF joint's rest pose (in GLTF coordinate system) - const glm::mat4 &gltf_joint_rest_pose = gltf_rest_map.at(gltf_node_index); + const glm::mat4 &gltf_joint_rest_pose = node_data.mGltfRestMatrix; glm::mat4 rest_pose = coord_system_rotation * gltf_joint_rest_pose; LL_INFOS("GLTF_DEBUG") << "rest matrix for joint " << joint_name << ": "; @@ -1199,7 +1275,7 @@ glm::mat4 LLGLTFLoader::computeGltfToViewerSkeletonTransform(const joint_node_ma // Compute transformation from GLTF space to viewer space // This assumes both skeletons are in rest pose initially - return viewer_joint_rest_pose * glm::inverse(rest_pose); + return node_data.mOverrideRestMatrix * glm::inverse(rest_pose); } bool LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx) -- cgit v1.2.3 From 46aeaf48034fead1dd69060f07494d53ade1ae80 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 17 Jun 2025 22:39:58 +0300 Subject: #4242 Support splitting of gltf faces that are over 16bit limit --- indra/newview/gltf/llgltfloader.cpp | 623 ++++++++++++++++++++---------------- 1 file changed, 352 insertions(+), 271 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c6d2bec238..33639cf93c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -514,13 +514,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Mark unsuported joints with '-1' so that they won't get added into weights // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones. - std::vector gltf_joint_index_valid; + std::vector gltf_joint_index_use; if (skinIdx >= 0 && mGLTFAsset.mSkins.size() > skinIdx) { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; size_t jointCnt = gltf_skin.mJoints.size(); - gltf_joint_index_valid.resize(jointCnt); + gltf_joint_index_use.resize(jointCnt); S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) @@ -532,7 +532,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::string legal_name(jointNode.mName); if (mJointMap.find(legal_name) == mJointMap.end()) { - gltf_joint_index_valid[i] = -1; // mark as unsupported + // This might need to hold a substitute index + gltf_joint_index_use[i] = -1; // mark as unsupported } } } @@ -540,332 +541,420 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (size_t prim_idx = 0; prim_idx < mesh.mPrimitives.size(); ++prim_idx) { const LL::GLTF::Primitive& prim = mesh.mPrimitives[prim_idx]; - // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit. - if (prim.getVertexCount() < USHRT_MAX) - { - // So primitives already have all of the data we need for a given face in SL land. - // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call - // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 - LLVolumeFace face; - std::vector vertices; - std::vector indices; - LLImportMaterial impMat; - impMat.mDiffuseColor = LLColor4::white; // Default color + // So primitives already have all of the data we need for a given face in SL land. + // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call + // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 + LLVolumeFace face; + std::vector vertices; + + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color - // Process material if available - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + // Process material if available + if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + + // Set diffuse color from base color factor + impMat.mDiffuseColor = LLColor4( + material->mPbrMetallicRoughness.mBaseColorFactor[0], + material->mPbrMetallicRoughness.mBaseColorFactor[1], + material->mPbrMetallicRoughness.mBaseColorFactor[2], + material->mPbrMetallicRoughness.mBaseColorFactor[3] + ); + + // Process base color texture if it exists + if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - - // Set diffuse color from base color factor - impMat.mDiffuseColor = LLColor4( - material->mPbrMetallicRoughness.mBaseColorFactor[0], - material->mPbrMetallicRoughness.mBaseColorFactor[1], - material->mPbrMetallicRoughness.mBaseColorFactor[2], - material->mPbrMetallicRoughness.mBaseColorFactor[3] - ); - - // Process base color texture if it exists - if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) + S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; + if (texIndex < mGLTFAsset.mTextures.size()) { - S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; - if (texIndex < mGLTFAsset.mTextures.size()) + S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; + if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) { - S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; - if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + + // Use URI as texture file name + if (!image.mUri.empty()) { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + // URI might be a remote URL or a local path + std::string filename = image.mUri; - // Use URI as texture file name - if (!image.mUri.empty()) + // Extract just the filename from the URI + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) { - // URI might be a remote URL or a local path - std::string filename = image.mUri; - - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) - { - filename = filename.substr(pos + 1); - } + filename = filename.substr(pos + 1); + } - // Store the texture filename - impMat.mDiffuseMapFilename = filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; + // Store the texture filename + impMat.mDiffuseMapFilename = filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename - << " for material: " << material->mName << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename + << " for material: " << material->mName << LL_ENDL; - LLSD args; - args["Message"] = "TextureFound"; - args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; - args["MATERIAL_NAME"] = material->mName; - mWarningsArray.append(args); + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; + args["MATERIAL_NAME"] = material->mName; + mWarningsArray.append(args); - // If the image has a texture loaded already, use it - if (image.mTexture.notNull()) - { - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; - } - else - { - // Let the model preview know we need to load this texture - mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL; - } - } - else if (image.mTexture.notNull()) + // If the image has a texture loaded already, use it + if (image.mTexture.notNull()) { - // No URI but we have a texture, use it directly impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; } - else if (image.mBufferView >= 0) + else { - // For embedded textures (no URI but has buffer data) - // Create a pseudo filename for the embedded texture - std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; - impMat.mDiffuseMapFilename = pseudo_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; - - // Mark for loading + // Let the model preview know we need to load this texture mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL; } } + else if (image.mTexture.notNull()) + { + // No URI but we have a texture, use it directly + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + } + else if (image.mBufferView >= 0) + { + // For embedded textures (no URI but has buffer data) + // Create a pseudo filename for the embedded texture + std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; + impMat.mDiffuseMapFilename = pseudo_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; + + // Mark for loading + mNumOfFetchingTextures++; + LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + } } } } + } - // Apply the global scale and center offset to all vertices - for (U32 i = 0; i < prim.getVertexCount(); i++) - { - // Use pre-computed final_transform - glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); - glm::vec4 transformed_pos = final_transform * pos; - - GLTFVertex vert; - vert.position = glm::vec3(transformed_pos); - - if (!prim.mNormals.empty()) - { - // Use pre-computed normal_transform - glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); - vert.normal = glm::normalize(normal_transform * normal_vec); - } - else - { - // Use default normal (pointing up in model space) - vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); - LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; - } + if (prim.getIndexCount() % 3 != 0) + { + LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx + << ": Invalid index count " << prim.getIndexCount() + << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; - vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + LLSD args; + args["Message"] = "InvalidGeometryNonTriangulated"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); + mWarningsArray.append(args); + return false; // Skip this primitive + } - if (skinIdx >= 0) - { - vert.weights = glm::vec4(prim.mWeights[i]); + // Apply the global scale and center offset to all vertices + for (U32 i = 0; i < prim.getVertexCount(); i++) + { + // Use pre-computed final_transform + glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f); + glm::vec4 transformed_pos = final_transform * pos; - auto accessorIdx = prim.mAttributes.at("JOINTS_0"); - LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; - if (accessorIdx >= 0) - { - auto accessor = mGLTFAsset.mAccessors[accessorIdx]; - componentType = accessor.mComponentType; - } + GLTFVertex vert; + vert.position = glm::vec3(transformed_pos); - // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. - // Detect and unpack accordingly. - if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) - { - auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); - vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); - } - else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) - { - vert.joints = glm::unpackUint4x16(prim.mJoints[i]); - } - else - { - vert.joints = glm::zero(); - vert.weights = glm::zero(); - } - } - vertices.push_back(vert); + if (!prim.mNormals.empty()) + { + // Use pre-computed normal_transform + glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); + vert.normal = glm::normalize(normal_transform * normal_vec); } - - if (prim.getIndexCount() % 3 != 0) + else { - LL_WARNS("GLTF_IMPORT") << "Mesh '" << mesh.mName << "' primitive " << prim_idx - << ": Invalid index count " << prim.getIndexCount() - << " (not divisible by 3). GLTF files must contain triangulated geometry." << LL_ENDL; - - LLSD args; - args["Message"] = "InvalidGeometryNonTriangulated"; - args["MESH_NAME"] = mesh.mName; - args["PRIMITIVE_INDEX"] = static_cast(prim_idx); - args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); - mWarningsArray.append(args); - continue; // Skip this primitive + // Use default normal (pointing up in model space) + vert.normal = glm::normalize(normal_transform * glm::vec3(0.0f, 0.0f, 1.0f)); + LL_DEBUGS("GLTF_IMPORT") << "No normals found for primitive, using default normal." << LL_ENDL; } - // When processing indices, flip winding order if needed - for (U32 i = 0; i < prim.getIndexCount(); i += 3) + vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]); + + if (skinIdx >= 0) { - if (hasNegativeScale) + vert.weights = glm::vec4(prim.mWeights[i]); + + auto accessorIdx = prim.mAttributes.at("JOINTS_0"); + LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; + if (accessorIdx >= 0) { - // Flip winding order for negative scale - indices.push_back(prim.mIndexArray[i]); - indices.push_back(prim.mIndexArray[i + 2]); // Swap these two - indices.push_back(prim.mIndexArray[i + 1]); + auto accessor = mGLTFAsset.mAccessors[accessorIdx]; + componentType = accessor.mComponentType; + } + + // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. + // Detect and unpack accordingly. + if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) + { + auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); + vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); + } + else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) + { + vert.joints = glm::unpackUint4x16(prim.mJoints[i]); } else { - indices.push_back(prim.mIndexArray[i]); - indices.push_back(prim.mIndexArray[i + 1]); - indices.push_back(prim.mIndexArray[i + 2]); + vert.joints = glm::zero(); + vert.weights = glm::zero(); } } + vertices.push_back(vert); + } - // Check for empty vertex array before processing - if (vertices.empty()) + // Check for empty vertex array before processing + if (vertices.empty()) + { + LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive " << prim_idx << " in model " << mesh.mName << LL_ENDL; + LLSD args; + args["Message"] = "EmptyVertexArray"; + args["MESH_NAME"] = mesh.mName; + args["PRIMITIVE_INDEX"] = static_cast(prim_idx); + args["INDEX_COUNT"] = static_cast(prim.getIndexCount()); + mWarningsArray.append(args); + return false; // Skip this primitive + } + + std::vector faceVertices; + glm::vec3 min = glm::vec3(FLT_MAX); + glm::vec3 max = glm::vec3(-FLT_MAX); + + for (U32 i = 0; i < vertices.size(); i++) + { + LLVolumeFace::VertexData vert; + + // Update min/max bounds + if (i == 0) { - LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL; - continue; // Skip this primitive + min = max = vertices[i].position; + } + else + { + min.x = std::min(min.x, vertices[i].position.x); + min.y = std::min(min.y, vertices[i].position.y); + min.z = std::min(min.z, vertices[i].position.z); + max.x = std::max(max.x, vertices[i].position.x); + max.y = std::max(max.y, vertices[i].position.y); + max.z = std::max(max.z, vertices[i].position.z); } - std::vector faceVertices; - glm::vec3 min = glm::vec3(FLT_MAX); - glm::vec3 max = glm::vec3(-FLT_MAX); + LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); + LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); + vert.setPosition(position); + vert.setNormal(normal); + vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); + faceVertices.push_back(vert); - for (U32 i = 0; i < vertices.size(); i++) + if (skinIdx >= 0) { - LLVolumeFace::VertexData vert; - - // Update min/max bounds - if (i == 0) + // create list of weights that influence this vertex + LLModel::weight_list weight_list; + + // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count) + // don't reindex them yet, more indexes will be removed + // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be + // 'empty' ones + if (gltf_joint_index_use[vertices[i].joints.x] >= 0 + && vertices[i].weights.x > 0.f) { - min = max = vertices[i].position; + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); } - else + if (gltf_joint_index_use[vertices[i].joints.y] >= 0 + && vertices[i].weights.y > 0.f) { - min.x = std::min(min.x, vertices[i].position.x); - min.y = std::min(min.y, vertices[i].position.y); - min.z = std::min(min.z, vertices[i].position.z); - max.x = std::max(max.x, vertices[i].position.x); - max.y = std::max(max.y, vertices[i].position.y); - max.z = std::max(max.z, vertices[i].position.z); + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); } - - LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); - LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); - vert.setPosition(position); - vert.setNormal(normal); - vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); - faceVertices.push_back(vert); - - if (skinIdx >= 0) + if (gltf_joint_index_use[vertices[i].joints.z] >= 0 + && vertices[i].weights.z > 0.f) { - // create list of weights that influence this vertex - LLModel::weight_list weight_list; - - // Drop joints that viewer doesn't support (negative in gltf_joint_index_use_count) - // don't reindex them yet, more indexes will be removed - // Also drop joints that have no weight. GLTF stores 4 per vertex, so there might be - // 'empty' ones - if (gltf_joint_index_valid[vertices[i].joints.x] >= 0 - && vertices[i].weights.x > 0.f) - { - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); - } - if (gltf_joint_index_valid[vertices[i].joints.y] >= 0 - && vertices[i].weights.y > 0.f) - { - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); - } - if (gltf_joint_index_valid[vertices[i].joints.z] >= 0 - && vertices[i].weights.z > 0.f) - { - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); - } - if (gltf_joint_index_valid[vertices[i].joints.w] >= 0 - && vertices[i].weights.w > 0.f) - { - weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); - } + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + } + if (gltf_joint_index_use[vertices[i].joints.w] >= 0 + && vertices[i].weights.w > 0.f) + { + weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + } - std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); - std::vector wght; - F32 total = 0.f; + std::vector wght; + F32 total = 0.f; - for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j) - { - // take up to 4 most significant weights - // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. - wght.push_back(weight_list[j]); - total += weight_list[j].mWeight; - } + for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j) + { + // take up to 4 most significant weights + // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. + wght.push_back(weight_list[j]); + total += weight_list[j].mWeight; + } - if (total != 0.f) - { - F32 scale = 1.f / total; - if (scale != 1.f) - { // normalize weights - for (U32 j = 0; j < wght.size(); ++j) - { - wght[j].mWeight *= scale; - } + if (total != 0.f) + { + F32 scale = 1.f / total; + if (scale != 1.f) + { // normalize weights + for (U32 j = 0; j < wght.size(); ++j) + { + wght[j].mWeight *= scale; } } + } - if (wght.size() > 0) - { - pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; - } + if (wght.size() > 0) + { + pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; } } + } - face.fillFromLegacyData(faceVertices, indices); - face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); - face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + // Create a unique material name for this primitive + std::string materialName; + if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; + materialName = material->mName; - pModel->getVolumeFaces().push_back(face); + if (materialName.empty()) + { + materialName = "mat" + std::to_string(prim.mMaterial); + } + } + else + { + materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + } + mats[materialName] = impMat; + + // Indices handling + if (faceVertices.size() >= USHRT_MAX) + { + // Will have to remap 32 bit indices into 16 bit indices + // For the sake of simplicity build vector of 32 bit indices first + std::vector indices_32; + for (U32 i = 0; i < prim.getIndexCount(); i += 3) + { + // When processing indices, flip winding order if needed + if (hasNegativeScale) + { + // Flip winding order for negative scale + indices_32.push_back(prim.mIndexArray[i]); + indices_32.push_back(prim.mIndexArray[i + 2]); // Swap these two + indices_32.push_back(prim.mIndexArray[i + 1]); + } + else + { + indices_32.push_back(prim.mIndexArray[i]); + indices_32.push_back(prim.mIndexArray[i + 1]); + indices_32.push_back(prim.mIndexArray[i + 2]); + } + } - // Create a unique material name for this primitive - std::string materialName; - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) + // remap 32 bit into multiple 16 bit ones + std::vector indices_16; + std::vector vertices_remap; // should it be a point map? + vertices_remap.resize(faceVertices.size(), -1); + std::vector face_verts; + min = glm::vec3(FLT_MAX); + max = glm::vec3(-FLT_MAX); + for (size_t idx = 0; idx < indices_32.size(); idx++) { - LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial]; - materialName = material->mName; + size_t vert_index = indices_32[idx]; + if (vertices_remap[vert_index] == -1) + { + // First encounter, add it + size_t new_vert_idx = face_verts.size(); + vertices_remap[vert_index] = (S64)new_vert_idx; + face_verts.push_back(faceVertices[vert_index]); + vert_index = new_vert_idx; + + // Update min/max bounds + const LLVector4a& vec = face_verts[new_vert_idx].getPosition(); + if (new_vert_idx == 0) + { + min.x = vec[0]; + min.y = vec[1]; + min.z = vec[2]; + max = min; + } + else + { + min.x = std::min(min.x, vec[0]); + min.y = std::min(min.y, vec[1]); + min.z = std::min(min.z, vec[2]); + max.x = std::max(max.x, vec[0]); + max.y = std::max(max.y, vec[1]); + max.z = std::max(max.z, vec[2]); + } + } + else + { + // already in vector, get position + vert_index = (size_t)vertices_remap[vert_index]; + } + indices_16.push_back((U16)vert_index); - if (materialName.empty()) + if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532) { - materialName = "mat" + std::to_string(prim.mMaterial); + LLVolumeFace face; + face.fillFromLegacyData(face_verts, indices_16); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); + + std::fill(vertices_remap.begin(), vertices_remap.end(), -1); + indices_16.clear(); + face_verts.clear(); + + min = glm::vec3(FLT_MAX); + max = glm::vec3(-FLT_MAX); } } - else + if (indices_16.size() > 0 && face_verts.size() > 0) { - materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1); + LLVolumeFace face; + face.fillFromLegacyData(face_verts, indices_16); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); } - - pModel->getMaterialList().push_back(materialName); - mats[materialName] = impMat; } else { - LL_INFOS("GLTF_IMPORT") << "Unable to process mesh '" << mesh.mName - << "' primitive " << prim_idx - << " due to 65,534 vertex limit. Vertex count: " - << prim.getVertexCount() << LL_ENDL; - LLSD args; - args["Message"] = "ErrorIndexLimit"; - args["MESH_NAME"] = mesh.mName.empty() ? ("mesh_" + std::to_string(&mesh - &mGLTFAsset.mMeshes[0])) : mesh.mName; - args["VERTEX_COUNT"] = static_cast(prim.getVertexCount()); - mWarningsArray.append(args); - return false; + // can use indices directly + std::vector indices; + for (U32 i = 0; i < prim.getIndexCount(); i += 3) + { + // When processing indices, flip winding order if needed + if (hasNegativeScale) + { + // Flip winding order for negative scale + indices.push_back(prim.mIndexArray[i]); + indices.push_back(prim.mIndexArray[i + 2]); // Swap these two + indices.push_back(prim.mIndexArray[i + 1]); + } + else + { + indices.push_back(prim.mIndexArray[i]); + indices.push_back(prim.mIndexArray[i + 1]); + indices.push_back(prim.mIndexArray[i + 2]); + } + } + + face.fillFromLegacyData(faceVertices, indices); + face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); + face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + + pModel->getVolumeFaces().push_back(face); + pModel->getMaterialList().push_back(materialName); } } @@ -892,7 +981,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - // else thanks to gltf_joint_index_valid any illegal + // else thanks to gltf_joint_index_usage any illegal // joint should have zero uses. // Add them anyway to preserve order, remapSkinWeightsAndJoints // will sort them out later @@ -970,7 +1059,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) { - buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident); } for (S32 i = 0; i < joint_count; i++) @@ -1147,7 +1236,7 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } -void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& leftover) const +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest) const { glm::mat4 new_lefover(1.f); glm::mat4 rest(1.f); @@ -1176,25 +1265,17 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map glm::mat4 viewer_joint = glm::recompose(scale, rotation, override, skew, perspective); node.mOverrideMatrix = viewer_joint; - rest = parent_rest * viewer_joint; + rest = parent_rest * node.mOverrideMatrix; node.mOverrideRestMatrix = rest; - if (viewer_data.mName == "mPelvis") - { - // Todo: This is wrong, but this is a temporary - // solution for parts staying behind. - // Something is still missing with override mechanics - node.mOverrideMatrix = glm::mat4(1.f); - } } else { // No override for this joint - new_lefover = leftover * viewer_data.mJointMatrix; rest = parent_rest * viewer_data.mJointMatrix; } for (LLJointData& child_data : viewer_data.mChildren) { - buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, new_lefover); + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest); } } -- cgit v1.2.3 From bb45bfae0d5f3a7c4d588a2727a86172b12e7c40 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 18 Jun 2025 15:57:11 +0300 Subject: #4204 Fix GLTF texture loading to match DAE loader behavior --- indra/newview/gltf/llgltfloader.cpp | 72 +++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 11 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 33639cf93c..1f65435728 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -52,11 +52,14 @@ #include "llsdserialize.h" #include "lljoint.h" +#include "llbase64.h" +#include "lldir.h" #include "llmatrix4a.h" #include #include +#include static const std::string lod_suffix[LLModel::NUM_LODS] = { @@ -609,9 +612,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } else { - // Let the model preview know we need to load this texture - mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding texture to load queue: " << impMat.mDiffuseMapFilename << LL_ENDL; + // Texture will be loaded later through the callback system + LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; } } else if (image.mTexture.notNull()) @@ -623,14 +625,62 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else if (image.mBufferView >= 0) { // For embedded textures (no URI but has buffer data) - // Create a pseudo filename for the embedded texture - std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; - impMat.mDiffuseMapFilename = pseudo_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? pseudo_filename : material->mName; - - // Mark for loading - mNumOfFetchingTextures++; - LL_INFOS("GLTF_IMPORT") << "Adding embedded texture to load queue: " << pseudo_filename << LL_ENDL; + // Extract the texture to a temporary file immediately + if (image.mBufferView < mGLTFAsset.mBufferViews.size()) + { + const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; + if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) + { + const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + + if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) + { + // Extract image data + const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; + U32 data_size = buffer_view.mByteLength; + + // Determine the file extension + std::string extension = ".png"; // Default + if (!image.mMimeType.empty()) + { + if (image.mMimeType == "image/jpeg") + extension = ".jpg"; + else if (image.mMimeType == "image/png") + extension = ".png"; + } + else if (data_size >= 4) + { + if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) + extension = ".jpg"; // JPEG magic bytes + else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) + extension = ".png"; // PNG magic bytes + } + + // Create a temporary file + std::string temp_dir = gDirUtilp->getTempDir(); + std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + + "gltf_embedded_" + std::to_string(sourceIndex) + extension; + + // Write the image data to the temporary file + std::ofstream temp_file(temp_filename, std::ios::binary); + if (temp_file.is_open()) + { + temp_file.write(reinterpret_cast(data_ptr), data_size); + temp_file.close(); + + // Use the temp file as the texture filename + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; + + LL_INFOS("GLTF_IMPORT") << "Extracted embedded texture to: " << temp_filename << LL_ENDL; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; + } + } + } + } } } } -- cgit v1.2.3 From 5099401a5364ebfe4ec908fc0481ed3568778651 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 18 Jun 2025 17:18:48 +0300 Subject: #4204 Log embedded texture extraction failure --- indra/newview/gltf/llgltfloader.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1f65435728..50ce075d65 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -677,6 +677,12 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else { LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; + + LLSD args; + args["Message"] = "FailedToCreateTempFile"; + args["TEXTURE_INDEX"] = sourceIndex; + args["TEMP_FILE"] = temp_filename; + mWarningsArray.append(args); } } } -- cgit v1.2.3 From f5320302d4b81825b1037ca0074a65ad9eac14ac Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 19 Jun 2025 19:20:35 +0300 Subject: #4214 Revert and remake "weights remap" This reverts commits fe10a83f69bb26eb581e143fb99c1250c355938b and 08f6f5c697fce4ccbfba357ab9ce5af915dd0574.. --- indra/newview/gltf/llgltfloader.cpp | 79 ++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 6 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 50ce075d65..e495abfe75 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -225,8 +225,6 @@ void LLGLTFLoader::addModelToScene( { // remove unused/redundant vertices model->remapVolumeFaces(); - // remove unused/redundant weights and joints - model->remapSkinWeightsAndJoints(); mModelList.push_back(model); @@ -821,21 +819,25 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& && vertices[i].weights.x > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + gltf_joint_index_use[vertices[i].joints.x]++; } if (gltf_joint_index_use[vertices[i].joints.y] >= 0 && vertices[i].weights.y > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + gltf_joint_index_use[vertices[i].joints.y]++; } if (gltf_joint_index_use[vertices[i].joints.z] >= 0 && vertices[i].weights.z > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + gltf_joint_index_use[vertices[i].joints.z]++; } if (gltf_joint_index_use[vertices[i].joints.w] >= 0 && vertices[i].weights.w > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + gltf_joint_index_use[vertices[i].joints.w]++; } std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); @@ -1022,14 +1024,23 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; + S32 valid_joints_count = mValidJointsCount[skinIdx]; + std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); + gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; + S32 stripped_valid_joints = 0; for (size_t i = 0; i < jointCnt; ++i) { // Process joint name and idnex S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] < 0) + { + // unsupported (-1) joint, drop it + continue; + } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); @@ -1037,10 +1048,28 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - // else thanks to gltf_joint_index_usage any illegal - // joint should have zero uses. - // Add them anyway to preserve order, remapSkinWeightsAndJoints - // will sort them out later + else + { + llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 + continue; + } + + if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT + && gltf_joint_index_use[i] == 0 + && legal_name != "mPelvis") + { + // Unused (0) joint + // It's perfectly valid to have more joints than is in use + // Ex: sandals that make your legs digitigrade despite not skining to + // knees or the like. + // But if model is over limid, drop extras sans pelvis. + // Keeping 'pelvis' is a workaround to keep preview whole. + // Todo: consider improving this, either take as much as possible or + // ensure common parents/roots are included + continue; + } + + gltfindex_to_joitindex_map[i] = replacement_index++; skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); @@ -1050,6 +1079,28 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); } + + if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel + << " Count: " << (S32)skin_info.mInvBindMatrix.size() + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + LLSD args; + args["Message"] = "ModelTooManyJoint"; + args["MODEL_NAME"] = pModel->mLabel; + args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + + // Remap indices for pModel->mSkinWeights + for (auto& weights : pModel->mSkinWeights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; + } + } } return true; @@ -1075,6 +1126,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); + mValidJointsCount.resize(skin_idx + 1, 0); } // fill up joints related data @@ -1095,6 +1147,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { data.mName = mJointMap[jointNode.mName]; data.mIsValidViewerJoint = true; + mValidJointsCount[skin_idx]++; } else { @@ -1111,6 +1164,20 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } } + if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" + << " Count: " << mValidJointsCount[skin_idx] + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + + LLSD args; + args["Message"] = "SkinJointsOverLimit"; + args["SKIN_INDEX"] = (S32)skin_idx; + args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + // Go over viewer joints and build overrides glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) -- cgit v1.2.3 From 2083e652feec3d1627ad9b7c1ae415a5c44ab9bf Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 19 Jun 2025 20:57:44 +0300 Subject: #4204 Unused code cleanup (#4278) --- indra/newview/gltf/llgltfloader.cpp | 377 ++++++------------------------------ 1 file changed, 62 insertions(+), 315 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index e495abfe75..056d3df1a6 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -623,67 +623,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& else if (image.mBufferView >= 0) { // For embedded textures (no URI but has buffer data) - // Extract the texture to a temporary file immediately - if (image.mBufferView < mGLTFAsset.mBufferViews.size()) + std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); + if (!temp_filename.empty()) { - const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; - if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) - { - const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; - - if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) - { - // Extract image data - const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; - U32 data_size = buffer_view.mByteLength; - - // Determine the file extension - std::string extension = ".png"; // Default - if (!image.mMimeType.empty()) - { - if (image.mMimeType == "image/jpeg") - extension = ".jpg"; - else if (image.mMimeType == "image/png") - extension = ".png"; - } - else if (data_size >= 4) - { - if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) - extension = ".jpg"; // JPEG magic bytes - else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) - extension = ".png"; // PNG magic bytes - } - - // Create a temporary file - std::string temp_dir = gDirUtilp->getTempDir(); - std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + - "gltf_embedded_" + std::to_string(sourceIndex) + extension; - - // Write the image data to the temporary file - std::ofstream temp_file(temp_filename, std::ios::binary); - if (temp_file.is_open()) - { - temp_file.write(reinterpret_cast(data_ptr), data_size); - temp_file.close(); - - // Use the temp file as the texture filename - impMat.mDiffuseMapFilename = temp_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; - - LL_INFOS("GLTF_IMPORT") << "Extracted embedded texture to: " << temp_filename << LL_ENDL; - } - else - { - LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for embedded texture" << LL_ENDL; - - LLSD args; - args["Message"] = "FailedToCreateTempFile"; - args["TEXTURE_INDEX"] = sourceIndex; - args["TEMP_FILE"] = temp_filename; - mWarningsArray.append(args); - } - } - } + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; } } } @@ -1730,286 +1674,89 @@ void LLGLTFLoader::uploadMeshes() // convert raw image buffers to texture UUIDs & assemble into a render material void LLGLTFLoader::uploadMaterials() { - LL_INFOS("GLTF_IMPORT") << "Uploading materials, count: " << mMaterials.size() << LL_ENDL; - - for (gltf_render_material& mat : mMaterials) - { - LL_INFOS("GLTF_IMPORT") << "Processing material: " << mat.name << LL_ENDL; - - // Process base color texture - if (mat.hasBaseTex && mat.baseColorTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.baseColorTexIdx]; - if (gtex.imageUuid.isNull()) - { - LL_INFOS("GLTF_IMPORT") << "Loading base color texture for material " << mat.name << LL_ENDL; - gtex.imageUuid = imageBufferToTextureUUID(gtex); - - if (gtex.imageUuid.notNull()) - { - LL_INFOS("GLTF_IMPORT") << "Base color texture loaded, ID: " << gtex.imageUuid.asString() << LL_ENDL; - } - else - { - LL_WARNS("GLTF_IMPORT") << "Failed to load base color texture for material " << mat.name << LL_ENDL; - } - } - } - - // Process other textures similarly - if (mat.hasMRTex && mat.metalRoughTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.metalRoughTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasNormalTex && mat.normalTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.normalTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasOcclusionTex && mat.occlusionTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.occlusionTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - - if (mat.hasEmissiveTex && mat.emissiveTexIdx < mTextures.size()) - { - gltf_texture& gtex = mTextures[mat.emissiveTexIdx]; - if (gtex.imageUuid.isNull()) - { - gtex.imageUuid = imageBufferToTextureUUID(gtex); - } - } - } - - // Update material map for all model instances to ensure textures are properly associated - // mScene is a std::map, not an array, so we need to iterate through it correctly - for (auto& scene_entry : mScene) - { - for (LLModelInstance& instance : scene_entry.second) - { - LLModel* model = instance.mModel; - - if (model) - { - for (size_t i = 0; i < model->getMaterialList().size(); ++i) - { - const std::string& matName = model->getMaterialList()[i]; - if (!matName.empty()) - { - // Ensure this material exists in the instance's material map - if (instance.mMaterial.find(matName) == instance.mMaterial.end()) - { - // Find material in our render materials - for (const auto& renderMat : mMaterials) - { - if (renderMat.name == matName) - { - // Create an import material from the render material - LLImportMaterial impMat; - impMat.mDiffuseColor = renderMat.baseColor; - - // Set diffuse texture if available - if (renderMat.hasBaseTex && renderMat.baseColorTexIdx < mTextures.size()) - { - const gltf_texture& gtex = mTextures[renderMat.baseColorTexIdx]; - if (!gtex.imageUuid.isNull()) - { - impMat.setDiffuseMap(gtex.imageUuid); - LL_INFOS("GLTF_IMPORT") << "Setting texture " << gtex.imageUuid.asString() << " for material " << matName << LL_ENDL; - } - } - - // Add material to instance's material map - instance.mMaterial[matName] = impMat; - LL_INFOS("GLTF_IMPORT") << "Added material " << matName << " to instance" << LL_ENDL; - break; - } - } - } - } - } - } - } - } } - -LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex) +std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Invalid texture indices in imageBufferToTextureUUID" << LL_ENDL; - return LLUUID::null; - } - - gltf_image& image = mImages[tex.imageIdx]; - gltf_sampler& sampler = mSamplers[tex.samplerIdx]; + if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) + return ""; - S32 sourceIndex = tex.imageIdx; + S32 sourceIndex = mGLTFAsset.mTextures[textureIndex].mSource; if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) - { - LL_WARNS("GLTF_IMPORT") << "Invalid image index: " << sourceIndex << LL_ENDL; - return LLUUID::null; - } + return ""; - LL::GLTF::Image& source_image = mGLTFAsset.mImages[sourceIndex]; + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - // If the image already has a texture loaded, use it - if (source_image.mTexture.notNull()) + // Handle URI-based textures + if (!image.mUri.empty()) { - LL_INFOS("GLTF_IMPORT") << "Using already loaded texture ID: " << source_image.mTexture->getID().asString() << LL_ENDL; - return source_image.mTexture->getID(); + return image.mUri; // Return URI directly } - // Create an import material to pass to the texture load function - LLImportMaterial material; - - // Try to get the texture filename from the URI - if (!source_image.mUri.empty()) + // Handle embedded textures + if (image.mBufferView >= 0) { - std::string filename = source_image.mUri; - - // Extract just the filename from the URI - size_t pos = filename.find_last_of("/\\"); - if (pos != std::string::npos) + if (image.mBufferView < mGLTFAsset.mBufferViews.size()) { - filename = filename.substr(pos + 1); - } - - material.mDiffuseMapFilename = filename; - material.mDiffuseMapLabel = filename; - } - else if (source_image.mBufferView >= 0) - { - // For embedded textures, create a pseudo-filename - std::string pseudo_filename = "gltf_embedded_texture_" + std::to_string(sourceIndex) + ".png"; - material.mDiffuseMapFilename = pseudo_filename; - material.mDiffuseMapLabel = pseudo_filename; - } - else - { - LL_WARNS("GLTF_IMPORT") << "No URI or buffer data for image" << LL_ENDL; - return LLUUID::null; - } - - // Create LLSD container with image and sampler data for texture upload - LLSD texture_data = LLSD::emptyMap(); - - // Image data - texture_data["width"] = LLSD::Integer(image.width); - texture_data["height"] = LLSD::Integer(image.height); - texture_data["components"] = LLSD::Integer(image.numChannels); - texture_data["bytes_per_component"] = LLSD::Integer(image.bytesPerChannel); - texture_data["pixel_type"] = LLSD::Integer(image.pixelType); - - // Sampler data - texture_data["min_filter"] = LLSD::Integer(sampler.minFilter); - texture_data["mag_filter"] = LLSD::Integer(sampler.magFilter); - texture_data["wrap_s"] = LLSD::Integer(sampler.wrapS); - texture_data["wrap_t"] = LLSD::Integer(sampler.wrapT); - - // Add URI for reference - if (!source_image.mUri.empty()) - { - texture_data["uri"] = source_image.mUri; - } - - // Check if we have a buffer view for embedded data - if (source_image.mBufferView >= 0) - { - texture_data["has_embedded_data"] = LLSD::Boolean(true); - texture_data["buffer_view"] = LLSD::Integer(source_image.mBufferView); - - // Extract embedded data for texture loading - if (source_image.mBufferView < mGLTFAsset.mBufferViews.size()) - { - const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[source_image.mBufferView]; + const LL::GLTF::BufferView& buffer_view = mGLTFAsset.mBufferViews[image.mBufferView]; if (buffer_view.mBuffer < mGLTFAsset.mBuffers.size()) { const LL::GLTF::Buffer& buffer = mGLTFAsset.mBuffers[buffer_view.mBuffer]; + if (buffer_view.mByteOffset + buffer_view.mByteLength <= buffer.mData.size()) { - // Add embedded data reference to texture_data - texture_data["buffer_index"] = LLSD::Integer(buffer_view.mBuffer); - texture_data["byte_offset"] = LLSD::Integer(buffer_view.mByteOffset); - texture_data["byte_length"] = LLSD::Integer(buffer_view.mByteLength); + // Extract image data + const U8* data_ptr = &buffer.mData[buffer_view.mByteOffset]; + U32 data_size = buffer_view.mByteLength; - LL_INFOS("GLTF_IMPORT") << "Found embedded texture data: offset=" << buffer_view.mByteOffset - << " length=" << buffer_view.mByteLength << LL_ENDL; - } - } - } - } - - // Store the texture metadata in the binding field - std::ostringstream ostr; - LLSDSerialize::toXML(texture_data, ostr); - material.mBinding = ostr.str(); - - LL_INFOS("GLTF_IMPORT") << "Loading texture: " << material.mDiffuseMapFilename << LL_ENDL; - - // Flag to track if texture was loaded immediately - bool texture_loaded = false; - - // Call texture loading function with our import material - if (mTextureLoadFunc) - { - // Increment textures to fetch counter BEFORE calling load function - mNumOfFetchingTextures++; - - U32 result = mTextureLoadFunc(material, mOpaqueData); + // Determine the file extension + std::string extension = ".png"; // Default + if (!image.mMimeType.empty()) + { + if (image.mMimeType == "image/jpeg") + extension = ".jpg"; + else if (image.mMimeType == "image/png") + extension = ".png"; + } + else if (data_size >= 4) + { + if (data_ptr[0] == 0xFF && data_ptr[1] == 0xD8) + extension = ".jpg"; // JPEG magic bytes + else if (data_ptr[0] == 0x89 && data_ptr[1] == 0x50 && data_ptr[2] == 0x4E && data_ptr[3] == 0x47) + extension = ".png"; // PNG magic bytes + } - // If result is 0, texture is being loaded asynchronously - // If result is >0, texture was loaded immediately - if (result > 0) - { - // Texture was loaded immediately, so decrement counter - mNumOfFetchingTextures--; - texture_loaded = true; + // Create a temporary file + std::string temp_dir = gDirUtilp->getTempDir(); + std::string temp_filename = temp_dir + gDirUtilp->getDirDelimiter() + + "gltf_embedded_" + texture_type + "_" + std::to_string(sourceIndex) + extension; - if (material.getDiffuseMap().notNull()) - { - LL_INFOS("GLTF_IMPORT") << "Texture loaded successfully, ID: " << material.getDiffuseMap().asString() << LL_ENDL; + // Write the image data to the temporary file + std::ofstream temp_file(temp_filename, std::ios::binary); + if (temp_file.is_open()) + { + temp_file.write(reinterpret_cast(data_ptr), data_size); + temp_file.close(); - // Store the texture in the source image for future reference - if (source_image.mTexture.isNull()) - { - // Create and store a texture object using the UUID - source_image.mTexture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap()); + LL_INFOS("GLTF_IMPORT") << "Extracted embedded " << texture_type << " texture to: " << temp_filename << LL_ENDL; + return temp_filename; + } + else + { + LL_WARNS("GLTF_IMPORT") << "Failed to create temporary file for " << texture_type << " texture: " << temp_filename << LL_ENDL; + + LLSD args; + args["Message"] = "FailedToCreateTempFile"; + args["TEXTURE_INDEX"] = sourceIndex; + args["TEXTURE_TYPE"] = texture_type; + args["TEMP_FILE"] = temp_filename; + mWarningsArray.append(args); + } } - - return material.getDiffuseMap(); } } - else if (result == 0) - { - LL_INFOS("GLTF_IMPORT") << "Texture loading queued asynchronously for " << material.mDiffuseMapFilename << LL_ENDL; - } - else // result < 0, indicating error - { - // Texture loading failed, decrement counter - mNumOfFetchingTextures--; - LL_WARNS("GLTF_IMPORT") << "Texture loading failed for " << material.mDiffuseMapFilename << LL_ENDL; - } - } - else - { - LL_WARNS("GLTF_IMPORT") << "No texture loading function available" << LL_ENDL; } - return LLUUID::null; + return ""; } void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) -- cgit v1.2.3 From 1a6e3286110f9e54611715b44c5f8b47d08dc310 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 19 Jun 2025 22:31:38 +0300 Subject: #4204 Remove more unused code --- indra/newview/gltf/llgltfloader.cpp | 200 +----------------------------------- 1 file changed, 2 insertions(+), 198 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 056d3df1a6..7a1ebf0610 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -111,9 +111,6 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointsFromNodes, jointAliasMap, maxJointsPerMesh ) - //mPreprocessGLTF(preprocess), - , mMeshesLoaded(false) - , mMaterialsLoaded(false) , mGeneratedModelLimit(modelLimit) , mViewerJointData(viewer_skeleton) { @@ -137,15 +134,11 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) notifyUnsupportedExtension(false); - mMeshesLoaded = parseMeshes(); - if (mMeshesLoaded) uploadMeshes(); - - mMaterialsLoaded = parseMaterials(); - if (mMaterialsLoaded) uploadMaterials(); + bool meshesLoaded = parseMeshes(); setLoadState(DONE); - return (mMeshesLoaded); + return meshesLoaded; } void LLGLTFLoader::addModelToScene( @@ -1486,195 +1479,6 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) } } -bool LLGLTFLoader::parseMaterials() -{ - if (!mGltfLoaded) return false; - - // fill local texture data structures - mSamplers.clear(); - for (auto& in_sampler : mGLTFAsset.mSamplers) - { - gltf_sampler sampler; - sampler.magFilter = in_sampler.mMagFilter > 0 ? in_sampler.mMagFilter : GL_LINEAR; - sampler.minFilter = in_sampler.mMinFilter > 0 ? in_sampler.mMinFilter : GL_LINEAR; - sampler.wrapS = in_sampler.mWrapS; - sampler.wrapT = in_sampler.mWrapT; - sampler.name = in_sampler.mName; - mSamplers.push_back(sampler); - } - - mImages.clear(); - for (auto& in_image : mGLTFAsset.mImages) - { - gltf_image image; - image.numChannels = in_image.mComponent; - image.bytesPerChannel = in_image.mBits >> 3; // Convert bits to bytes - image.pixelType = in_image.mPixelType; - image.size = 0; // We'll calculate this below if we have valid dimensions - - // Get dimensions from the texture if available - if (in_image.mTexture && in_image.mTexture->getDiscardLevel() >= 0) - { - image.height = in_image.mTexture->getHeight(); - image.width = in_image.mTexture->getWidth(); - // Since we don't have direct access to the raw data, we'll use the dimensions to calculate size - if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) - { - image.size = static_cast(image.height * image.width * image.numChannels * image.bytesPerChannel); - } - } - else - { - // Fallback to provided dimensions - image.height = in_image.mHeight; - image.width = in_image.mWidth; - if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0) - { - image.size = static_cast(image.height * image.width * image.numChannels * image.bytesPerChannel); - } - } - - // If we couldn't determine the size, skip this image - if (image.size == 0) - { - LL_WARNS("GLTF_IMPORT") << "Image size could not be determined" << LL_ENDL; - continue; - } - - // We don't have direct access to the image data, so data pointer remains nullptr - image.data = nullptr; - mImages.push_back(image); - } - - mTextures.clear(); - for (auto& in_tex : mGLTFAsset.mTextures) - { - gltf_texture tex; - tex.imageIdx = in_tex.mSource; - tex.samplerIdx = in_tex.mSampler; - tex.imageUuid.setNull(); - - if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size()) - { - LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL; - return false; - } - - mTextures.push_back(tex); - } - - // parse each material - mMaterials.clear(); - for (const auto& gltf_material : mGLTFAsset.mMaterials) - { - gltf_render_material mat; - mat.name = gltf_material.mName; - - // PBR Metallic Roughness properties - mat.hasPBR = true; - - // Base color factor - mat.baseColor = LLColor4( - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[0], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[1], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[2], - gltf_material.mPbrMetallicRoughness.mBaseColorFactor[3] - ); - - // Base color texture - mat.hasBaseTex = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0; - mat.baseColorTexIdx = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mIndex; - mat.baseColorTexCoords = gltf_material.mPbrMetallicRoughness.mBaseColorTexture.mTexCoord; - - // Metalness and roughness - mat.metalness = gltf_material.mPbrMetallicRoughness.mMetallicFactor; - mat.roughness = gltf_material.mPbrMetallicRoughness.mRoughnessFactor; - - // Metallic-roughness texture - mat.hasMRTex = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex >= 0; - mat.metalRoughTexIdx = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mIndex; - mat.metalRoughTexCoords = gltf_material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTexCoord; - - // Normal texture - mat.normalScale = gltf_material.mNormalTexture.mScale; - mat.hasNormalTex = gltf_material.mNormalTexture.mIndex >= 0; - mat.normalTexIdx = gltf_material.mNormalTexture.mIndex; - mat.normalTexCoords = gltf_material.mNormalTexture.mTexCoord; - - // Occlusion texture - mat.occlusionScale = gltf_material.mOcclusionTexture.mStrength; - mat.hasOcclusionTex = gltf_material.mOcclusionTexture.mIndex >= 0; - mat.occlusionTexIdx = gltf_material.mOcclusionTexture.mIndex; - mat.occlusionTexCoords = gltf_material.mOcclusionTexture.mTexCoord; - - // Emissive texture and color - mat.emissiveColor = LLColor4( - gltf_material.mEmissiveFactor[0], - gltf_material.mEmissiveFactor[1], - gltf_material.mEmissiveFactor[2], - 1.0f - ); - mat.hasEmissiveTex = gltf_material.mEmissiveTexture.mIndex >= 0; - mat.emissiveTexIdx = gltf_material.mEmissiveTexture.mIndex; - mat.emissiveTexCoords = gltf_material.mEmissiveTexture.mTexCoord; - - // Convert AlphaMode enum to string - switch (gltf_material.mAlphaMode) - { - case LL::GLTF::Material::AlphaMode::OPAQUE: - mat.alphaMode = "OPAQUE"; - break; - case LL::GLTF::Material::AlphaMode::MASK: - mat.alphaMode = "MASK"; - break; - case LL::GLTF::Material::AlphaMode::BLEND: - mat.alphaMode = "BLEND"; - break; - default: - mat.alphaMode = "OPAQUE"; - break; - } - - mat.alphaMask = gltf_material.mAlphaCutoff; - - // Verify that all referenced textures are valid - if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) || - (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) || - (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) || - (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) || - (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size()))) - { - LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL; - return false; - } - - // Verify texture coordinate sets are valid (mesh can have up to 3 sets of UV) - if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || - (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) || - (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) || - (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) || - (mat.hasMRTex && (mat.metalRoughTexCoords > 2))) - { - LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL; - return false; - } - - mMaterials.push_back(mat); - } - - return true; -} - -// TODO: convert raw vertex buffers to UUIDs -void LLGLTFLoader::uploadMeshes() -{ - //llassert(0); -} - -// convert raw image buffers to texture UUIDs & assemble into a render material -void LLGLTFLoader::uploadMaterials() -{ -} std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) -- cgit v1.2.3 From 9e87757a5b89cda3ee2fd08f7954b7c6796227ab Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 24 Jun 2025 00:39:25 +0300 Subject: #4257 Use filename as model initial name --- indra/newview/gltf/llgltfloader.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7a1ebf0610..2631b7fefe 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -465,6 +465,9 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) { + // Set the requested label for the floater display and uploading + pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + // Create unique model name std::string base_name = mesh.mName; if (base_name.empty()) -- cgit v1.2.3 From c404b9375923e0e4260536329b3da7d566a2d259 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 24 Jun 2025 17:07:42 +0300 Subject: #4148 Fix collision bones --- indra/newview/gltf/llgltfloader.cpp | 49 ++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 2631b7fefe..1af53311c6 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1122,7 +1122,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) { - buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident); + buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); } for (S32 i = 0; i < joint_count; i++) @@ -1299,7 +1299,7 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } -void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest) const +void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const { glm::mat4 new_lefover(1.f); glm::mat4 rest(1.f); @@ -1309,26 +1309,42 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map S32 gltf_node_idx = found_node->second; JointNodeData& node = gltf_nodes[gltf_node_idx]; node.mIsOverrideValid = true; + node.mViewerRestMatrix = viewer_data.mRestMatrix; glm::mat4 gltf_joint_rest_pose = coord_system_rotation * node.mGltfRestMatrix; if (mApplyXYRotation) { gltf_joint_rest_pose = coord_system_rotationxy * gltf_joint_rest_pose; } - node.mOverrideMatrix = glm::inverse(parent_rest) * gltf_joint_rest_pose; - glm::vec3 override; + glm::mat4 translated_joint; + // Example: + // Viewer has pelvis->spine1->spine2->torso. + // gltf example model has pelvis->torso + // By doing glm::inverse(transalted_rest_spine2) * gltf_rest_torso + // We get what torso would have looked like if gltf had a spine2 + if (viewer_data.mIsJoint) + { + translated_joint = glm::inverse(parent_rest) * gltf_joint_rest_pose; + } + else + { + translated_joint = glm::inverse(parent_support_rest) * gltf_joint_rest_pose; + } + + glm::vec3 translation_override; glm::vec3 skew; glm::vec3 scale; glm::vec4 perspective; glm::quat rotation; - glm::decompose(node.mOverrideMatrix, scale, rotation, override, skew, perspective); - glm::vec3 translate; - glm::decompose(viewer_data.mJointMatrix, scale, rotation, translate, skew, perspective); - glm::mat4 viewer_joint = glm::recompose(scale, rotation, override, skew, perspective); + glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + + node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); + + glm::mat4 override_joint = node.mOverrideMatrix; + override_joint = glm::scale(override_joint, viewer_data.mScale); - node.mOverrideMatrix = viewer_joint; - rest = parent_rest * node.mOverrideMatrix; + rest = parent_rest * override_joint; node.mOverrideRestMatrix = rest; } else @@ -1336,9 +1352,20 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map // No override for this joint rest = parent_rest * viewer_data.mJointMatrix; } + + glm::mat4 support_rest(1.f); + if (viewer_data.mSupport == LLJointData::SUPPORT_BASE) + { + support_rest = rest; + } + else + { + support_rest = parent_support_rest; + } + for (LLJointData& child_data : viewer_data.mChildren) { - buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest); + buildOverrideMatrix(child_data, gltf_nodes, names_to_nodes, rest, support_rest); } } -- cgit v1.2.3 From 8c9d0c61459ebdfa3d17c9ef7ccfba9793bf4d3a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 25 Jun 2025 20:19:23 +0300 Subject: #4142 Joint grouping and stripping Viewer only supports 110 joints at a time, when model has more bones than that importer has to strip some, use groups for stripping. --- indra/newview/gltf/llgltfloader.cpp | 177 ++++++++++++++++++++++++++---------- 1 file changed, 129 insertions(+), 48 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1af53311c6..039c0b561e 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -258,13 +258,14 @@ bool LLGLTFLoader::parseMeshes() if (mGLTFAsset.mSkins.size() > 0) { checkForXYrotation(mGLTFAsset.mSkins[0]); + populateJointGroups(); } // Populate the joints from skins first. // There's not many skins - and you can pretty easily iterate through the nodes from that. for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) { - populateJointFromSkin(i); + populateJointsFromSkin(i); } // Track how many times each mesh name has been used @@ -463,6 +464,24 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 } } +bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const +{ + const std::string& legal_name = mJointNames[gltf_skin_idx][gltf_joint_idx]; + if (legal_name.empty()) + { + llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 + return false; + } + skin_info.mJointNames.push_back(legal_name); + skin_info.mJointNums.push_back(-1); + + // In scope of same skin multiple meshes reuse same bind matrices + skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[gltf_skin_idx][gltf_joint_idx]); + skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[gltf_skin_idx][gltf_joint_idx]); + + return true; +} + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) { // Set the requested label for the floater display and uploading @@ -519,15 +538,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& size_t jointCnt = gltf_skin.mJoints.size(); gltf_joint_index_use.resize(jointCnt); - S32 replacement_index = 0; for (size_t i = 0; i < jointCnt; ++i) { - // Process joint name and idnex - S32 joint = gltf_skin.mJoints[i]; - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - - std::string legal_name(jointNode.mName); - if (mJointMap.find(legal_name) == mJointMap.end()) + if (mJointNames[i].empty()) { // This might need to hold a substitute index gltf_joint_index_use[i] = -1; // mark as unsupported @@ -959,65 +972,106 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // Call normalizeVolumeFacesAndWeights to compute proper extents pModel->normalizeVolumeFacesAndWeights(); - // Fill joint names, bind matrices and prepare to remap weight indices + // Fill joint names, bind matrices and remap weight indices if (skinIdx >= 0) { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; S32 valid_joints_count = mValidJointsCount[skinIdx]; + S32 replacement_index = 0; std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt); - S32 replacement_index = 0; - S32 stripped_valid_joints = 0; - for (size_t i = 0; i < jointCnt; ++i) + if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) { - // Process joint name and idnex - S32 joint = gltf_skin.mJoints[i]; - if (gltf_joint_index_use[i] < 0) + std::map goup_use_count; + // Assume that 'Torso' group is always in use since that's what everything else is attached to + goup_use_count["Torso"] = 1; + // Note that Collisions and Extra groups are all over the place, might want to include them from the start + // or add individual when parents are added + + // Check which groups are in use + for (size_t i = 0; i < jointCnt; ++i) { - // unsupported (-1) joint, drop it - continue; + std::string& joint_name = mJointNames[skinIdx][i]; + if (!joint_name.empty()) + { + if (gltf_joint_index_use[i] > 0) + { + const JointGroups &group = mJointGroups[joint_name]; + // Joint in use, increment it's groups + goup_use_count[group.mGroup]++; + goup_use_count[group.mParentGroup]++; + } + } } - LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::string legal_name(jointNode.mName); - if (mJointMap.find(legal_name) != mJointMap.end()) + // 1. add joints that are in use directly + for (size_t i = 0; i < jointCnt; ++i) { - legal_name = mJointMap[legal_name]; - } - else - { - llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 - continue; + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] <= 0) + { + // unsupported (-1) joint, drop it + // unused (0) joint, drop it + continue; + } + + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } } - if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT - && gltf_joint_index_use[i] == 0 - && legal_name != "mPelvis") + // 2. add joints from groups that this model's joints belong to + // It's perfectly valid to have more joints than is in use + // Ex: sandals that make your legs digitigrade despite not skining to + // knees or the like. + // Todo: sort and add by usecount + for (size_t i = 0; i < jointCnt; ++i) { - // Unused (0) joint - // It's perfectly valid to have more joints than is in use - // Ex: sandals that make your legs digitigrade despite not skining to - // knees or the like. - // But if model is over limid, drop extras sans pelvis. - // Keeping 'pelvis' is a workaround to keep preview whole. - // Todo: consider improving this, either take as much as possible or - // ensure common parents/roots are included - continue; + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] != 0) + { + // this step needs only joints that have zero uses + continue; + } + if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + break; + } + const std::string& legal_name = mJointNames[skinIdx][i]; + std::string group_name = mJointGroups[legal_name].mGroup; + if (goup_use_count[group_name] > 0) + { + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } + } } + } + else + { + // Less than 110, just add every valid joint + for (size_t i = 0; i < jointCnt; ++i) + { + // Process joint name and idnex + S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] < 0) + { + // unsupported (-1) joint, drop it + continue; + } - gltfindex_to_joitindex_map[i] = replacement_index++; - - skin_info.mJointNames.push_back(legal_name); - skin_info.mJointNums.push_back(-1); - - // In scope of same skin multiple meshes reuse same bind matrices - skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[skinIdx][i]); - - skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); + if (addJointToModelSkin(skin_info, skinIdx, i)) + { + gltfindex_to_joitindex_map[i] = replacement_index++; + } + } } if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) @@ -1046,7 +1100,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& return true; } -void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) +void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) { const LL::GLTF::Skin& skin = mGLTFAsset.mSkins[skin_idx]; @@ -1066,6 +1120,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); + mJointNames.resize(skin_idx + 1); mValidJointsCount.resize(skin_idx + 1, 0); } @@ -1135,6 +1190,11 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { legal_name = mJointMap[legal_name]; legal_joint = true; + mJointNames[skin_idx].push_back(legal_name); + } + else + { + mJointNames[skin_idx].emplace_back(); } // Compute bind matrices @@ -1196,6 +1256,15 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } } +void LLGLTFLoader::populateJointGroups() +{ + std::string parent; + for (auto& viewer_data : mViewerJointData) + { + buildJointGroup(viewer_data, parent); + } +} + S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const { @@ -1299,6 +1368,18 @@ S32 LLGLTFLoader::findParentNode(S32 node) const return -1; } +void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group) +{ + JointGroups& jount_group_data = mJointGroups[viewer_data.mName]; + jount_group_data.mGroup = viewer_data.mGroup; + jount_group_data.mParentGroup = parent_group; + + for (LLJointData& child_data : viewer_data.mChildren) + { + buildJointGroup(child_data, viewer_data.mGroup); + } +} + void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const { glm::mat4 new_lefover(1.f); -- cgit v1.2.3 From 895db142ee9224e1b8e4d2fc6348dc1801bea0dc Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 26 Jun 2025 20:43:06 +0300 Subject: #4291 Uploading without a .bin silently fails --- indra/newview/gltf/llgltfloader.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 039c0b561e..7ae255e054 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -129,6 +129,20 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) if (!mGltfLoaded) { notifyUnsupportedExtension(true); + + for (const auto& buffer : mGLTFAsset.mBuffers) + { + if (buffer.mByteLength > 0 && buffer.mData.empty()) + { + bool bin_file = buffer.mUri.ends_with(".bin"); + LLSD args; + args["Message"] = bin_file ? "ParsingErrorMissingBufferBin" : "ParsingErrorMissingBuffer"; + args["BUFFER_NAME"] = buffer.mName; + args["BUFFER_URI"] = buffer.mUri; + mWarningsArray.append(args); + } + } + setLoadState(ERROR_PARSING); return false; } -- cgit v1.2.3 From 75db5e8b6b911e312ebdb9bc10a55d048a70e0be Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 28 Jun 2025 19:31:02 +0200 Subject: Revert "Fix LLCharacter base class constness." and restore improvements from 10a324a1034c177b95545ac7ffaa6aa6abed65ff instead --- indra/newview/gltf/llgltfloader.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 7ae255e054..bd3c1a3833 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -86,20 +86,20 @@ static const glm::mat4 coord_system_rotationxy( 0.f, 0.f, 0.f, 1.f ); -LLGLTFLoader::LLGLTFLoader(std::string filename, - S32 lod, - LLModelLoader::load_callback_t load_cb, - LLModelLoader::joint_lookup_func_t joint_lookup_func, - LLModelLoader::texture_load_func_t texture_load_func, - LLModelLoader::state_callback_t state_cb, - void * opaque_userdata, - JointTransformMap & jointTransformMap, - JointNameSet & jointsFromNodes, - std::map &jointAliasMap, - U32 maxJointsPerMesh, - U32 modelLimit, - std::vector viewer_skeleton) //, - //bool preprocess) +LLGLTFLoader::LLGLTFLoader(std::string filename, + S32 lod, + LLModelLoader::load_callback_t load_cb, + LLModelLoader::joint_lookup_func_t joint_lookup_func, + LLModelLoader::texture_load_func_t texture_load_func, + LLModelLoader::state_callback_t state_cb, + void * opaque_userdata, + JointTransformMap & jointTransformMap, + JointNameSet & jointsFromNodes, + std::map> & jointAliasMap, + U32 maxJointsPerMesh, + U32 modelLimit, + std::vector viewer_skeleton) //, + //bool preprocess) : LLModelLoader( filename, lod, load_cb, -- cgit v1.2.3 From 64b56d7ca98201f4cb5a31c21c0ff82220b7c730 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 1 Jul 2025 11:48:22 +0300 Subject: #4190 Provide unsupported extension info in log file --- indra/newview/gltf/llgltfloader.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index bd3c1a3833..fd0276766c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1705,6 +1705,8 @@ void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) } args["EXT"] = ext; mWarningsArray.append(args); + + LL_WARNS("GLTF_IMPORT") << "Model uses unsupported extension: " << ext << LL_ENDL; } } -- cgit v1.2.3 From fcd9a9e0deb91c194a662550c6fd3ac406320067 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Jul 2025 18:05:49 +0300 Subject: #4242 Better issue logging --- indra/newview/gltf/llgltfloader.cpp | 246 ++++++++++++++++-------------------- 1 file changed, 106 insertions(+), 140 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index fd0276766c..71bfc021d3 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -175,6 +175,16 @@ void LLGLTFLoader::addModelToScene( U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES; if (face_limit < volume_faces) { + LL_WARNS("GLTF_IMPORT") << "Model contains " << volume_faces + << " faces, exceeding the limit of " << face_limit << LL_ENDL; + + LLSD args; + args["Message"] = "ModelTooManySubmodels"; + args["MODEL_NAME"] = pModel->mLabel; + args["SUBMODEL_COUNT"] = static_cast(llfloor((F32)volume_faces / LL_SCULPT_MESH_MAX_FACES)); + args["SUBMODEL_LIMIT"] = static_cast(submodel_limit); + mWarningsArray.append(args); + pModel->setNumVolumeFaces(face_limit); } @@ -284,7 +294,9 @@ bool LLGLTFLoader::parseMeshes() // Track how many times each mesh name has been used std::map mesh_name_counts; - U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0; + + // For now use mesh count, but might be better to do 'mNodes.size() - joints count'. + U32 submodel_limit = mGLTFAsset.mMeshes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mMeshes.size() : 0; // Check if we have scenes defined if (!mGLTFAsset.mScenes.empty()) @@ -318,20 +330,7 @@ bool LLGLTFLoader::parseMeshes() return false; } - // Check total model count against limit - U32 total_models = static_cast(mModelList.size()); - if (total_models > mGeneratedModelLimit) - { - LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models - << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL; - - LLSD args; - args["Message"] = "TooManyMeshParts"; - args["PART_COUNT"] = static_cast(total_models); - args["LIMIT"] = static_cast(mGeneratedModelLimit); - mWarningsArray.append(args); - return false; - } + checkGlobalJointUsage(); return true; } @@ -478,7 +477,7 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3 } } -bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const +bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) { const std::string& legal_name = mJointNames[gltf_skin_idx][gltf_joint_idx]; if (legal_name.empty()) @@ -493,6 +492,9 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[gltf_skin_idx][gltf_joint_idx]); skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[gltf_skin_idx][gltf_joint_idx]); + // Track joint usage for this skin, for the sake of unused joints detection + mJointUsage[gltf_skin_idx][gltf_joint_idx]++; + return true; } @@ -550,7 +552,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; size_t jointCnt = gltf_skin.mJoints.size(); - gltf_joint_index_use.resize(jointCnt); + gltf_joint_index_use.resize(jointCnt, 0); for (size_t i = 0; i < jointCnt; ++i) { @@ -885,6 +887,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& std::vector indices_16; std::vector vertices_remap; // should it be a point map? vertices_remap.resize(faceVertices.size(), -1); + S32 created_faces = 0; std::vector face_verts; min = glm::vec3(FLT_MAX); max = glm::vec3(-FLT_MAX); @@ -933,6 +936,7 @@ 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(materialName); + created_faces++; std::fill(vertices_remap.begin(), vertices_remap.end(), -1); indices_16.clear(); @@ -950,7 +954,17 @@ 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(materialName); + created_faces++; } + + LL_INFOS("GLTF_IMPORT") << "Primitive " << pModel->mLabel + << " is over vertices limit, it was split into " << created_faces + << " faces" << LL_ENDL; + LLSD args; + args["Message"] = "ModelSplitPrimitive"; + args["MODEL_NAME"] = pModel->mLabel; + args["FACE_COUNT"] = created_faces; + mWarningsArray.append(args); } else { @@ -996,7 +1010,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& S32 replacement_index = 0; std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); - gltfindex_to_joitindex_map.resize(jointCnt); + gltfindex_to_joitindex_map.resize(jointCnt, -1); if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) { @@ -1094,7 +1108,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& << " Count: " << (S32)skin_info.mInvBindMatrix.size() << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; LLSD args; - args["Message"] = "ModelTooManyJoint"; + args["Message"] = "ModelTooManyJoints"; args["MODEL_NAME"] = pModel->mLabel; args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; @@ -1135,6 +1149,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); mJointNames.resize(skin_idx + 1); + mJointUsage.resize(skin_idx + 1); mValidJointsCount.resize(skin_idx + 1, 0); } @@ -1173,20 +1188,6 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } } - if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) - { - LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" - << " Count: " << mValidJointsCount[skin_idx] - << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; - - LLSD args; - args["Message"] = "SkinJointsOverLimit"; - args["SKIN_INDEX"] = (S32)skin_idx; - args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; - args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; - mWarningsArray.append(args); - } - // Go over viewer joints and build overrides glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) @@ -1210,6 +1211,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) { mJointNames[skin_idx].emplace_back(); } + mJointUsage[skin_idx].push_back(0); // Compute bind matrices @@ -1268,6 +1270,21 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) mJointsFromNode.push_front(legal_name); } } + + S32 valid_joints = mValidJointsCount[skin_idx]; + if (valid_joints < joint_count) + { + LL_WARNS("GLTF_IMPORT") << "Skin " << skin_idx + << " defines " << joint_count + << " joints, but only " << valid_joints + << " were recognized and are compatible." << LL_ENDL; + LLSD args; + args["Message"] = "SkinUsupportedJoints"; + args["SKIN_INDEX"] = skin_idx; + args["JOINT_COUNT"] = joint_count; + args["LEGAL_COUNT"] = valid_joints; + mWarningsArray.append(args); + } } void LLGLTFLoader::populateJointGroups() @@ -1279,109 +1296,6 @@ void LLGLTFLoader::populateJointGroups() } } - -S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const -{ - S32 source_joint_node = gltf_skin.mJoints[source_joint]; - S32 root_node = source_joint_node; - S32 found_node = source_joint_node; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - if (mJointMap.find(jointNode.mName) != mJointMap.end()) - { - return i; - } - break; - } - } - } while (root_node != found_node); - - return -1; -} - -S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const -{ - S32 root_node = 0; - S32 found_node = source_joint_node; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - 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_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - break; - } - } - } - } while (root_node != found_node); - - return root_node; -} - -S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const -{ - S32 root_node = 0; - S32 found_node = 0; - S32 size = (S32)gltf_skin.mJoints.size(); - do - { - root_node = found_node; - for (S32 i = 0; i < size; i++) - { - S32 joint = gltf_skin.mJoints[i]; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node); - if (it != jointNode.mChildren.end()) - { - // Found node's parent - found_node = joint; - break; - } - } - } while (root_node != found_node); - - LL_INFOS("GLTF_DEBUG") << "mJointList name: "; - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node]; - LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL; - return root_node; -} - -S32 LLGLTFLoader::findParentNode(S32 node) const -{ - S32 size = (S32)mGLTFAsset.mNodes.size(); - for (S32 i = 0; i < size; i++) - { - const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i]; - std::vector::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node); - if (it != jointNode.mChildren.end()) - { - return i; - } - } - return -1; -} - void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group) { JointGroups& jount_group_data = mJointGroups[viewer_data.mName]; @@ -1436,11 +1350,22 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); - glm::mat4 override_joint = node.mOverrideMatrix; - override_joint = glm::scale(override_joint, viewer_data.mScale); + glm::mat4 overriden_joint = node.mOverrideMatrix; + // This is incomplete or even wrong. + // Viewer allows overrides, which are base joint with applied translation override. + // So we should be taking viewer joint matrix and replacing translation part with an override. + // Or should rebuild the matrix from viewer_data.scale, viewer_data.rotation, translation_override parts. + overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); - rest = parent_rest * override_joint; - node.mOverrideRestMatrix = rest; + rest = parent_rest * overriden_joint; + if (viewer_data.mIsJoint) + { + node.mOverrideRestMatrix = rest; + } + else + { + node.mOverrideRestMatrix = parent_support_rest * overriden_joint; + } } else { @@ -1604,6 +1529,47 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) } } +void LLGLTFLoader::checkGlobalJointUsage() +{ + // Check if some joints remained unused + for (S32 skin_idx = 0; skin_idx < (S32)mGLTFAsset.mSkins.size(); ++skin_idx) + { + const LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skin_idx]; + S32 joint_count = (S32)gltf_skin.mJoints.size(); + S32 used_joints = 0; + for (S32 i = 0; i < joint_count; ++i) + { + S32 joint = gltf_skin.mJoints[i]; + if (mJointUsage[skin_idx][i] == 0) + { + // Joint is unused, log it + LL_INFOS("GLTF_DEBUG") << "Joint " << mJointNames[skin_idx][i] + << " in skin " << skin_idx << " is unused." << LL_ENDL; + } + else + { + used_joints++; + } + } + + S32 valid_joints = mValidJointsCount[skin_idx]; + if (valid_joints > used_joints) + { + S32 unsed_joints = valid_joints - used_joints; + LL_INFOS("GLTF_IMPORT") << "Skin " << skin_idx + << " declares " << valid_joints + << " valid joints, of them " << unsed_joints + << " remained unused" << LL_ENDL; + LLSD args; + args["Message"] = "SkinUnusedJoints"; + args["SKIN_INDEX"] = (S32)skin_idx; + args["JOINT_COUNT"] = valid_joints; + args["USED_COUNT"] = used_joints; + mWarningsArray.append(args); + } + } +} + std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type) { if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size()) -- cgit v1.2.3 From 76dd9385f9354fd661d1c7cc1e7c3d9c39355675 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Jul 2025 23:14:24 +0300 Subject: #4315 Crash in GLTF uploader Properly handle importer's crashes in general --- indra/newview/gltf/llgltfloader.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 71bfc021d3..bb6a0a590c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -124,7 +124,32 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); - mGltfLoaded = mGLTFAsset.load(filename, false); + try + { + mGltfLoaded = mGLTFAsset.load(filename, false); + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorException"; + args["FILENAME"] = filename; + args["EXCEPTION"] = e.what(); + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + return false; + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("LLGLTFLoader"); + LLSD args; + args["Message"] = "ParsingErrorException"; + args["FILENAME"] = filename; + args["EXCEPTION"] = "Unknown exception"; + mWarningsArray.append(args); + setLoadState(ERROR_PARSING); + return false; + } if (!mGltfLoaded) { -- cgit v1.2.3 From 900516a449c618c87517d295db7f76662e555d00 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Jul 2025 23:14:24 +0300 Subject: #4142 Fix missed index --- indra/newview/gltf/llgltfloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index bb6a0a590c..dd9e0d9b3a 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -581,7 +581,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& for (size_t i = 0; i < jointCnt; ++i) { - if (mJointNames[i].empty()) + if (mJointNames[skinIdx][i].empty()) { // This might need to hold a substitute index gltf_joint_index_use[i] = -1; // mark as unsupported -- cgit v1.2.3 From b79feb60f55efe93ce08609e9c2916d1938dad9e Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 2 Jul 2025 18:01:00 +0300 Subject: #4242 Clarify some operations and make scale application a bit more valid --- indra/newview/gltf/llgltfloader.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index dd9e0d9b3a..be3b9f0c4d 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1373,15 +1373,14 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map glm::quat rotation; glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + // Viewer allows overrides, which are base joint with applied translation override. + // fortunately normal bones use only translation, without rotation or scale node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); glm::mat4 overriden_joint = node.mOverrideMatrix; - // This is incomplete or even wrong. - // Viewer allows overrides, which are base joint with applied translation override. - // So we should be taking viewer joint matrix and replacing translation part with an override. - // Or should rebuild the matrix from viewer_data.scale, viewer_data.rotation, translation_override parts. - overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); + // todo: if gltf bone had rotation or scale, they probably should be saved here + // then applied to bind matrix rest = parent_rest * overriden_joint; if (viewer_data.mIsJoint) { @@ -1389,6 +1388,12 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map } else { + // This is likely incomplete or even wrong. + // Viewer Collision bones specify rotation and scale. + // Importer should apply rotation and scale to this matrix and save as needed + // then subsctruct them from bind matrix + + overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); node.mOverrideRestMatrix = parent_support_rest * overriden_joint; } } -- cgit v1.2.3 From 5a0bbdc510d3aef452b30aa932588aa7dc630d22 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 2 Jul 2025 23:18:05 +0300 Subject: #4242 Debug dump improvement for better comparison with collada output --- indra/newview/gltf/llgltfloader.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index be3b9f0c4d..48343aa169 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -98,6 +98,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map> & jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, std::vector viewer_skeleton) //, //bool preprocess) : LLModelLoader( filename, @@ -110,8 +111,9 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ) - , mGeneratedModelLimit(modelLimit) + maxJointsPerMesh, + modelLimit, + debugMode) , mViewerJointData(viewer_skeleton) { } @@ -1037,7 +1039,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt, -1); - if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (valid_joints_count > (S32)mMaxJointsPerMesh) { std::map goup_use_count; // Assume that 'Torso' group is always in use since that's what everything else is attached to @@ -1092,7 +1094,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // this step needs only joints that have zero uses continue; } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { break; } @@ -1127,16 +1129,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { + // mMaxJointsPerMesh ususlly is equal to LL_MAX_JOINTS_PER_MESH_OBJECT + // and is 110. LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel << " Count: " << (S32)skin_info.mInvBindMatrix.size() - << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + << " Limit:" << (S32)mMaxJointsPerMesh << LL_ENDL; LLSD args; args["Message"] = "ModelTooManyJoints"; args["MODEL_NAME"] = pModel->mLabel; args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); - args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + args["MAX"] = (S32)mMaxJointsPerMesh; mWarningsArray.append(args); } -- cgit v1.2.3 From f1701d3373959d023e35d39dabb2aa5ea44a3fb0 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 3 Jul 2025 19:45:49 +0300 Subject: #4242 Make sure group array is initialized MacOS doesn't seem to like missing init. --- indra/newview/gltf/llgltfloader.cpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 48343aa169..5abd4cf5ae 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -115,6 +115,8 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, modelLimit, debugMode) , mViewerJointData(viewer_skeleton) + , mGltfLoaded(false) + , mApplyXYRotation(false) { } @@ -313,7 +315,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. + // Multiple meshes can share the same skin, so preparing skins beforehand. for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++) { populateJointsFromSkin(i); @@ -367,7 +369,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map if (node_idx < 0 || node_idx >= static_cast(mGLTFAsset.mNodes.size())) return; - auto& node = mGLTFAsset.mNodes[node_idx]; + const LL::GLTF::Node& node = mGLTFAsset.mNodes[node_idx]; LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") @@ -380,7 +382,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map material_map mats; LLModel* pModel = new LLModel(volume_params, 0.f); - auto& mesh = mGLTFAsset.mMeshes[node.mMesh]; + const LL::GLTF::Mesh& mesh = mGLTFAsset.mMeshes[node.mMesh]; // Get base mesh name and track usage std::string base_name = mesh.mName; @@ -1042,6 +1044,13 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (valid_joints_count > (S32)mMaxJointsPerMesh) { std::map goup_use_count; + + for (const auto& elem : mJointGroups) + { + goup_use_count[elem.second.mGroup] = 0; + goup_use_count[elem.second.mParentGroup] = 0; + } + // Assume that 'Torso' group is always in use since that's what everything else is attached to goup_use_count["Torso"] = 1; // Note that Collisions and Extra groups are all over the place, might want to include them from the start @@ -1188,7 +1197,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; - LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint]; JointNodeData& data = joints_data[joint]; data.mNodeIdx = joint; data.mJointListIdx = i; @@ -1218,6 +1227,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } // Go over viewer joints and build overrides + // This is needed because gltf skeleton doesn't necessarily match viewer's skeleton. glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) { @@ -1227,8 +1237,10 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; - LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); + + // Viewer supports a limited set of joints, mark them as legal bool legal_joint = false; if (mJointMap.find(legal_name) != mJointMap.end()) { @@ -1254,8 +1266,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } else if (inverse_count > i) { - // Transalte existing bind matrix to viewer's skeleton - // todo: probably should be 'to viewer's overriden skeleton' + // Transalte existing bind matrix to viewer's overriden skeleton glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); @@ -1270,7 +1281,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) { // If bind matrices aren't present (they are optional in gltf), // assume an identy matrix - // todo: find a model with this, might need to use rotated matrix + // todo: find a model with this, might need to use YZ rotated matrix glm::mat4 inv_bind(1.0f); glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); inv_bind = glm::inverse(skeleton_transform * inv_bind); @@ -1303,7 +1314,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) S32 valid_joints = mValidJointsCount[skin_idx]; if (valid_joints < joint_count) { - LL_WARNS("GLTF_IMPORT") << "Skin " << skin_idx + LL_INFOS("GLTF_IMPORT") << "Skin " << skin_idx << " defines " << joint_count << " joints, but only " << valid_joints << " were recognized and are compatible." << LL_ENDL; @@ -1339,7 +1350,6 @@ void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string & void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const { - glm::mat4 new_lefover(1.f); glm::mat4 rest(1.f); joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName); if (found_node != names_to_nodes.end()) @@ -1396,6 +1406,7 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map // Viewer Collision bones specify rotation and scale. // Importer should apply rotation and scale to this matrix and save as needed // then subsctruct them from bind matrix + // Todo: get models that use collision bones, made by different programs overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); node.mOverrideRestMatrix = parent_support_rest * overriden_joint; @@ -1533,7 +1544,7 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin) for (S32 i= 0; i < size; i++) { S32 joint = gltf_skin.mJoints[i]; - auto joint_node = mGLTFAsset.mNodes[joint]; + const LL::GLTF::Node &joint_node = mGLTFAsset.mNodes[joint]; // todo: we are doing this search thing everywhere, // just pre-translate every joint -- cgit v1.2.3 From b6343d4169a1a3c10c287f32a86db6c1a52bca4d Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Fri, 4 Jul 2025 20:37:29 +0300 Subject: #4323 fix for transparent meshes --- indra/newview/gltf/llgltfloader.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5abd4cf5ae..3bee909951 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -86,6 +86,8 @@ static const glm::mat4 coord_system_rotationxy( 0.f, 0.f, 0.f, 1.f ); +static const S32 VERTICIES_LIMIT = USHRT_MAX - 2; + LLGLTFLoader::LLGLTFLoader(std::string filename, S32 lod, LLModelLoader::load_callback_t load_cb, @@ -889,7 +891,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mats[materialName] = impMat; // Indices handling - if (faceVertices.size() >= USHRT_MAX) + if (faceVertices.size() >= VERTICIES_LIMIT) { // Will have to remap 32 bit indices into 16 bit indices // For the sake of simplicity build vector of 32 bit indices first @@ -957,7 +959,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } indices_16.push_back((U16)vert_index); - if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532) + if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTICIES_LIMIT - 1) { LLVolumeFace face; face.fillFromLegacyData(face_verts, indices_16); -- cgit v1.2.3 From b7dbe0e49e49fd047f701c39006750dec4814395 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 8 Jul 2025 00:06:32 +0300 Subject: #4290 Unresponsive viewer when uploading models Fix severe log spam --- indra/newview/gltf/llgltfloader.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3bee909951..c850663579 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -373,7 +373,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map const LL::GLTF::Node& node = mGLTFAsset.mNodes[node_idx]; - LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" + LL_DEBUGS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")" << " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no") << " - children: " << node.mChildren.size() << LL_ENDL; @@ -542,7 +542,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& base_name = "mesh_" + std::to_string(mesh_index); } - LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; if (instance_count > 0) { @@ -988,7 +988,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& created_faces++; } - LL_INFOS("GLTF_IMPORT") << "Primitive " << pModel->mLabel + LL_INFOS("GLTF_IMPORT") << "Primitive " << (S32)prim_idx << " from model " << pModel->mLabel << " is over vertices limit, it was split into " << created_faces << " faces" << LL_ENDL; LLSD args; @@ -1276,7 +1276,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } else @@ -1289,7 +1289,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) inv_bind = glm::inverse(skeleton_transform * inv_bind); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind)); - LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL; mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform)); } @@ -1301,7 +1301,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr()); newInverse.setTranslation(original_joint_transform.getTranslation()); - LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; + LL_DEBUGS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL; mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse)); if (legal_joint) -- cgit v1.2.3 From 60425f69f7500f55013a6d4f1c02d0b6612a731b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 8 Jul 2025 21:32:07 +0300 Subject: #4314 Fix model suffixes Usecase: Unable to use the same GLB model for physics --- indra/newview/gltf/llgltfloader.cpp | 66 +++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 24 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c850663579..a211dad5fa 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -188,6 +188,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) void LLGLTFLoader::addModelToScene( LLModel* pModel, + const std::string& model_name, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, @@ -239,7 +240,7 @@ void LLGLTFLoader::addModelToScene( LLModel* next = new LLModel(volume_params, 0.f); next->ClearFacesAndMaterials(); next->mSubmodelID = ++submodelID; - next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; + next->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; next->getVolumeFaces() = remainder; next->mNormalizedScale = current_model->mNormalizedScale; next->mNormalizedTranslation = current_model->mNormalizedTranslation; @@ -289,7 +290,12 @@ void LLGLTFLoader::addModelToScene( materials[model->mMaterialList[i]] = LLImportMaterial(); } } - mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); + std::string base_name = model_name; + if (model->mSubmodelID > 0) + { + base_name += (char)((int)'a' + model->mSubmodelID); + } + mScene[transformation].push_back(LLModelInstance(model, base_name, transformation, materials)); stretch_extents(model, transformation); } } @@ -387,7 +393,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map const LL::GLTF::Mesh& mesh = mGLTFAsset.mMeshes[node.mMesh]; // Get base mesh name and track usage - std::string base_name = mesh.mName; + std::string base_name = getLodlessLabel(mesh); if (base_name.empty()) { base_name = "mesh_" + std::to_string(node.mMesh); @@ -395,7 +401,13 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map S32 instance_count = mesh_name_counts[base_name]++; - if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) && + // make name unique + if (instance_count > 0) + { + base_name = base_name + "_copy_" + std::to_string(instance_count); + } + + if (populateModelFromMesh(pModel, base_name, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) { @@ -443,7 +455,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map mWarningsArray.append(args); } - addModelToScene(pModel, submodel_limit, transformation, volume_params, mats); + addModelToScene(pModel, base_name, submodel_limit, transformation, volume_params, mats); mats.clear(); } else @@ -529,29 +541,16 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ return true; } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count) +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { // Set the requested label for the floater display and uploading pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); + // Set name and suffix. Suffix is nessesary for model matching logic + // because sometimes higher lod can be used as a lower one, so they + // need unique names not just in scope of one lod, but across lods. + pModel->mLabel = base_name + lod_suffix[mLod]; - // Create unique model name - std::string base_name = mesh.mName; - if (base_name.empty()) - { - S32 mesh_index = static_cast(&mesh - &mGLTFAsset.mMeshes[0]); - base_name = "mesh_" + std::to_string(mesh_index); - } - - LL_DEBUGS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL; - - if (instance_count > 0) - { - pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count); - } - else - { - pModel->mLabel = base_name; - } + LL_DEBUGS("GLTF_DEBUG") << "Processing model " << pModel->mLabel << LL_ENDL; pModel->ClearFacesAndMaterials(); @@ -1723,3 +1722,22 @@ void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported) } } +size_t LLGLTFLoader::getSuffixPosition(const std::string &label) +{ + if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1)) + { + return label.rfind('_'); + } + return -1; +} + +std::string LLGLTFLoader::getLodlessLabel(const LL::GLTF::Mesh& mesh) +{ + size_t ext_pos = getSuffixPosition(mesh.mName); + if (ext_pos != -1) + { + return mesh.mName.substr(0, ext_pos); + } + return mesh.mName; +} + -- cgit v1.2.3 From 5a8b94bea6bd984e046d1f61034ba92f324c1a83 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 9 Jul 2025 13:03:34 +0300 Subject: #4324 sort indices before splits --- indra/newview/gltf/llgltfloader.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index a211dad5fa..c2edf4ab1c 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -913,23 +913,40 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } } - // remap 32 bit into multiple 16 bit ones + // Generates a vertex remap table with no gaps in the resulting sequence + std::vector remap(faceVertices.size()); + size_t vertex_count = meshopt_generateVertexRemap(&remap[0], &indices_32[0], indices_32.size(), &faceVertices[0], faceVertices.size(), sizeof(LLVolumeFace::VertexData)); + + // Manually remap vertices + std::vector optimized_vertices(vertex_count); + for (size_t i = 0; i < vertex_count; ++i) + { + optimized_vertices[i] = faceVertices[remap[i]]; + } + + std::vector optimized_indices(indices_32.size()); + meshopt_remapIndexBuffer(&optimized_indices[0], &indices_32[0], indices_32.size(), &remap[0]); + + // Sort indices to improve mesh splits (reducing amount of duplicated indices) + meshopt_optimizeVertexCache(&optimized_indices[0], &optimized_indices[0], indices_32.size(), vertex_count); + std::vector indices_16; - std::vector vertices_remap; // should it be a point map? - vertices_remap.resize(faceVertices.size(), -1); + std::vector vertices_remap; + vertices_remap.resize(vertex_count, -1); S32 created_faces = 0; std::vector face_verts; min = glm::vec3(FLT_MAX); max = glm::vec3(-FLT_MAX); - for (size_t idx = 0; idx < indices_32.size(); idx++) + + for (size_t idx = 0; idx < optimized_indices.size(); idx++) { - size_t vert_index = indices_32[idx]; + size_t vert_index = optimized_indices[idx]; if (vertices_remap[vert_index] == -1) { // First encounter, add it size_t new_vert_idx = face_verts.size(); vertices_remap[vert_index] = (S64)new_vert_idx; - face_verts.push_back(faceVertices[vert_index]); + face_verts.push_back(optimized_vertices[vert_index]); vert_index = new_vert_idx; // Update min/max bounds -- cgit v1.2.3 From e77e74ffbb7cc9a8935d5b296a51a9c4ea94ea59 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 9 Jul 2025 00:22:20 +0300 Subject: #4314 Follow-up: duplicate logic for mSubmodelID --- indra/newview/gltf/llgltfloader.cpp | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index c2edf4ab1c..d625ec1261 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -223,6 +223,7 @@ void LLGLTFLoader::addModelToScene( LLVolume::face_list_t remainder; std::vector ready_models; LLModel* current_model = pModel; + do { current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); @@ -240,7 +241,26 @@ void LLGLTFLoader::addModelToScene( LLModel* next = new LLModel(volume_params, 0.f); next->ClearFacesAndMaterials(); next->mSubmodelID = ++submodelID; - next->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod]; + + std::string instance_name = model_name; + if (next->mSubmodelID > 0) + { + instance_name += (char)((int)'a' + next->mSubmodelID); + } + // Check for duplicates and add copy suffix if needed + int duplicate_count = 0; + for (const auto& inst : mScene[transformation]) + { + if (inst.mLabel == instance_name) + { + ++duplicate_count; + } + } + if (duplicate_count > 0) { + instance_name += "_copy_" + std::to_string(duplicate_count); + } + next->mLabel = instance_name; + next->getVolumeFaces() = remainder; next->mNormalizedScale = current_model->mNormalizedScale; next->mNormalizedTranslation = current_model->mNormalizedTranslation; @@ -290,12 +310,10 @@ void LLGLTFLoader::addModelToScene( materials[model->mMaterialList[i]] = LLImportMaterial(); } } - std::string base_name = model_name; - if (model->mSubmodelID > 0) - { - base_name += (char)((int)'a' + model->mSubmodelID); - } - mScene[transformation].push_back(LLModelInstance(model, base_name, transformation, materials)); + // Keep base name for scene instance, add LOD suffix to model label for matching + std::string instance_name = model->mLabel; + model->mLabel += lod_suffix[mLod]; + mScene[transformation].push_back(LLModelInstance(model, instance_name, transformation, materials)); stretch_extents(model, transformation); } } -- cgit v1.2.3 From edaf157e19ec5886fa58ba6341db8a91d89e14eb Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 9 Jul 2025 15:52:23 +0300 Subject: #4314 Fix model suffixes #2 --- indra/newview/gltf/llgltfloader.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index d625ec1261..a181c97ac0 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -310,8 +310,11 @@ void LLGLTFLoader::addModelToScene( materials[model->mMaterialList[i]] = LLImportMaterial(); } } - // Keep base name for scene instance, add LOD suffix to model label for matching + // Keep base name for scene instance. std::string instance_name = model->mLabel; + // Add suffix. Suffix is nessesary for model matching logic + // because sometimes higher lod can be used as a lower one, so models + // need unique names not just in scope of one lod, but across lods. model->mLabel += lod_suffix[mLod]; mScene[transformation].push_back(LLModelInstance(model, instance_name, transformation, materials)); stretch_extents(model, transformation); @@ -563,10 +566,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas { // Set the requested label for the floater display and uploading pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true); - // Set name and suffix. Suffix is nessesary for model matching logic - // because sometimes higher lod can be used as a lower one, so they - // need unique names not just in scope of one lod, but across lods. - pModel->mLabel = base_name + lod_suffix[mLod]; + // Set only name, suffix will be added later + pModel->mLabel = base_name; LL_DEBUGS("GLTF_DEBUG") << "Processing model " << pModel->mLabel << LL_ENDL; -- cgit v1.2.3 From 63134f79434481a450a146eae76f90a9f86091a4 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 14 Jul 2025 15:23:52 +0300 Subject: #4204 Cache processed materials --- indra/newview/gltf/llgltfloader.cpp | 190 ++++++++++++++++++++---------------- 1 file changed, 105 insertions(+), 85 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index a181c97ac0..f2361f58ba 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -126,6 +126,9 @@ LLGLTFLoader::~LLGLTFLoader() {} bool LLGLTFLoader::OpenFile(const std::string &filename) { + // Clear the material cache for new file + mMaterialCache.clear(); + tinygltf::TinyGLTF loader; std::string filename_lc(filename); LLStringUtil::toLower(filename_lc); @@ -562,6 +565,106 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ return true; } +LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) +{ + // Check cache first + auto cached = mMaterialCache.find(material_index); + if (cached != mMaterialCache.end()) + { + return cached->second; + } + + LLImportMaterial impMat; + impMat.mDiffuseColor = LLColor4::white; // Default color + + // Process material if available + if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[material_index]; + + // 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 + << " for material: " << material->mName << LL_ENDL; + + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; + args["MATERIAL_NAME"] = material->mName; + mWarningsArray.append(args); + + // If the image has a texture loaded already, use it + if (image.mTexture.notNull()) + { + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; + } + else + { + // Texture will be loaded later through the callback system + LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; + } + } + else if (image.mTexture.notNull()) + { + // No URI but we have a texture, use it directly + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; + } + else if (image.mBufferView >= 0) + { + // For embedded textures (no URI but has buffer data) + std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); + if (!temp_filename.empty()) + { + impMat.mDiffuseMapFilename = temp_filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; + } + } + } + } + } + } + + // Cache the processed material + mMaterialCache[material_index] = impMat; + return impMat; +} + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { // Set the requested label for the floater display and uploading @@ -623,91 +726,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas LLVolumeFace face; std::vector vertices; - LLImportMaterial impMat; - impMat.mDiffuseColor = LLColor4::white; // Default color - - // Process material if available - if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size()) - { - 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 - << " for material: " << material->mName << LL_ENDL; - - LLSD args; - args["Message"] = "TextureFound"; - args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; - args["MATERIAL_NAME"] = material->mName; - mWarningsArray.append(args); - - // If the image has a texture loaded already, use it - if (image.mTexture.notNull()) - { - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; - } - else - { - // Texture will be loaded later through the callback system - LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; - } - } - else if (image.mTexture.notNull()) - { - // No URI but we have a texture, use it directly - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; - } - else if (image.mBufferView >= 0) - { - // For embedded textures (no URI but has buffer data) - std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); - if (!temp_filename.empty()) - { - impMat.mDiffuseMapFilename = temp_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; - } - } - } - } - } - } + // Use cached material processing + LLImportMaterial impMat = processMaterial(prim.mMaterial); if (prim.getIndexCount() % 3 != 0) { -- cgit v1.2.3 From d033bbacf0d38079c1879acd04e54e341e78146e Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 14 Jul 2025 17:21:17 +0300 Subject: #4204 Refactor material name and texture handling in GLTF loader --- indra/newview/gltf/llgltfloader.cpp | 137 +++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 64 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index f2361f58ba..3d46db9e6a 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -594,40 +594,20 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0) { S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex; - if (texIndex < mGLTFAsset.mTextures.size()) + std::string filename = processTexture(texIndex, "base_color", material->mName); + + if (!filename.empty()) { - S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; - if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) - { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + impMat.mDiffuseMapFilename = filename; + impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; - // Use URI as texture file name - if (!image.mUri.empty()) + // Check if the texture is already loaded + if (texIndex < mGLTFAsset.mTextures.size()) + { + S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; + if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) { - // 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 - << " for material: " << material->mName << LL_ENDL; - - LLSD args; - args["Message"] = "TextureFound"; - args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename; - args["MATERIAL_NAME"] = material->mName; - mWarningsArray.append(args); - - // If the image has a texture loaded already, use it + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; if (image.mTexture.notNull()) { impMat.setDiffuseMap(image.mTexture->getID()); @@ -635,26 +615,9 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) } else { - // Texture will be loaded later through the callback system LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; } } - else if (image.mTexture.notNull()) - { - // No URI but we have a texture, use it directly - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL; - } - else if (image.mBufferView >= 0) - { - // For embedded textures (no URI but has buffer data) - std::string temp_filename = extractTextureToTempFile(texIndex, "base_color"); - if (!temp_filename.empty()) - { - impMat.mDiffuseMapFilename = temp_filename; - impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName; - } - } } } } @@ -665,6 +628,66 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) return impMat; } +std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name) +{ + if (texture_index < 0 || texture_index >= mGLTFAsset.mTextures.size()) + return ""; + + S32 sourceIndex = mGLTFAsset.mTextures[texture_index].mSource; + if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) + return ""; + + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + + // Process URI-based textures + if (!image.mUri.empty()) + { + std::string filename = image.mUri; + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + { + filename = filename.substr(pos + 1); + } + + LL_INFOS("GLTF_IMPORT") << "Found texture: " << filename << " for material: " << material_name << LL_ENDL; + + LLSD args; + args["Message"] = "TextureFound"; + args["TEXTURE_NAME"] = filename; + args["MATERIAL_NAME"] = material_name; + mWarningsArray.append(args); + + return filename; + } + + // Process embedded textures + if (image.mBufferView >= 0) + { + return extractTextureToTempFile(texture_index, texture_type); + } + + return ""; +} + +std::string LLGLTFLoader::generateMaterialName(S32 material_index, S32 fallback_index) +{ + if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) + { + LL::GLTF::Material* material = &mGLTFAsset.mMaterials[material_index]; + std::string materialName = material->mName; + + if (materialName.empty()) + { + materialName = "mat" + std::to_string(material_index); + } + return materialName; + } + else + { + return fallback_index >= 0 ? "mat_default" + std::to_string(fallback_index) : "mat_default"; + } +} + bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats) { // Set the requested label for the floater display and uploading @@ -910,22 +933,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } } - // 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); - } + // Generate material name using helper method + std::string materialName = generateMaterialName(prim.mMaterial, pModel->getNumVolumeFaces() - 1); mats[materialName] = impMat; // Indices handling -- cgit v1.2.3 From b046a8b72ab7959c789b1b5dfb7da42e97044ba4 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 15 Jul 2025 19:06:28 +0300 Subject: #4204 Cache material names, centralize texture index validation --- indra/newview/gltf/llgltfloader.cpp | 63 ++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 26 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3d46db9e6a..3019a12446 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -565,7 +565,7 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_ return true; } -LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) +LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_index, S32 fallback_index) { // Check cache first auto cached = mMaterialCache.find(material_index); @@ -577,6 +577,9 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) LLImportMaterial impMat; impMat.mDiffuseColor = LLColor4::white; // Default color + // Generate material name + std::string materialName = generateMaterialName(material_index, fallback_index); + // Process material if available if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) { @@ -602,39 +605,36 @@ LLImportMaterial LLGLTFLoader::processMaterial(S32 material_index) impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName; // Check if the texture is already loaded - if (texIndex < mGLTFAsset.mTextures.size()) + S32 sourceIndex; + if (validateTextureIndex(texIndex, sourceIndex)) { - S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource; - if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size()) + LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; + if (image.mTexture.notNull()) { - LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; - if (image.mTexture.notNull()) - { - impMat.setDiffuseMap(image.mTexture->getID()); - LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; - } - else - { - LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; - } + impMat.setDiffuseMap(image.mTexture->getID()); + LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; + } + else + { + LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL; } } } } } + // Create cached material with both material and name + LLGLTFImportMaterial cachedMat(impMat, materialName); + // Cache the processed material - mMaterialCache[material_index] = impMat; - return impMat; + mMaterialCache[material_index] = cachedMat; + return cachedMat; } std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name) { - if (texture_index < 0 || texture_index >= mGLTFAsset.mTextures.size()) - return ""; - - S32 sourceIndex = mGLTFAsset.mTextures[texture_index].mSource; - if (sourceIndex < 0 || sourceIndex >= mGLTFAsset.mImages.size()) + S32 sourceIndex; + if (!validateTextureIndex(texture_index, sourceIndex)) return ""; LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; @@ -669,6 +669,18 @@ std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& t return ""; } +bool LLGLTFLoader::validateTextureIndex(S32 texture_index, S32& source_index) +{ + if (texture_index < 0 || texture_index >= mGLTFAsset.mTextures.size()) + return false; + + source_index = mGLTFAsset.mTextures[texture_index].mSource; + if (source_index < 0 || source_index >= mGLTFAsset.mImages.size()) + return false; + + return true; +} + std::string LLGLTFLoader::generateMaterialName(S32 material_index, S32 fallback_index) { if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size()) @@ -750,7 +762,10 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas std::vector vertices; // Use cached material processing - LLImportMaterial impMat = processMaterial(prim.mMaterial); + LLGLTFImportMaterial cachedMat = processMaterial(prim.mMaterial, pModel->getNumVolumeFaces() - 1); + LLImportMaterial impMat = cachedMat; + std::string materialName = cachedMat.name; + mats[materialName] = impMat; if (prim.getIndexCount() % 3 != 0) { @@ -933,10 +948,6 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } } - // Generate material name using helper method - std::string materialName = generateMaterialName(prim.mMaterial, pModel->getNumVolumeFaces() - 1); - mats[materialName] = impMat; - // Indices handling if (faceVertices.size() >= VERTICIES_LIMIT) { -- cgit v1.2.3 From d84897967e836cd2dadf8f609eb98946f500d5b1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 22 Jul 2025 01:54:33 +0300 Subject: #4318 Warn or log when texture gets scaled down for material and model upload --- indra/newview/gltf/llgltfloader.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3019a12446..9f6caf4198 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -611,6 +611,7 @@ LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_in LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex]; if (image.mTexture.notNull()) { + mTexturesNeedScaling |= image.mHeight > LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT || image.mWidth > LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT; impMat.setDiffuseMap(image.mTexture->getID()); LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL; } -- cgit v1.2.3 From 472ea3b49a00c2988e519cd5cdb1947677edc04b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 22 Jul 2025 19:14:18 +0300 Subject: #4393 Handle unknown exceptions in uploader better Output is highly technical, but better than nothing --- indra/newview/gltf/llgltfloader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 9f6caf4198..950e98c96d 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -59,6 +59,7 @@ #include #include +#include #include static const std::string lod_suffix[LLModel::NUM_LODS] = @@ -154,7 +155,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename) LLSD args; args["Message"] = "ParsingErrorException"; args["FILENAME"] = filename; - args["EXCEPTION"] = "Unknown exception"; + args["EXCEPTION"] = boost::current_exception_diagnostic_information(); mWarningsArray.append(args); setLoadState(ERROR_PARSING); return false; -- cgit v1.2.3 From 8240af03b9c64572ce0594052e18d374d6e8941c Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 31 Jul 2025 19:55:28 +0300 Subject: #4465 modify vertex limit threshold for starting splitting --- indra/newview/gltf/llgltfloader.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'indra/newview/gltf/llgltfloader.cpp') diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 950e98c96d..dd1d327683 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -87,7 +87,8 @@ static const glm::mat4 coord_system_rotationxy( 0.f, 0.f, 0.f, 1.f ); -static const S32 VERTICIES_LIMIT = USHRT_MAX - 2; +static const S32 VERTEX_SPLIT_SAFETY_MARGIN = 3 * 3 + 1; // 10 vertices: 3 complete triangles plus remapping overhead +static const S32 VERTEX_LIMIT = USHRT_MAX - VERTEX_SPLIT_SAFETY_MARGIN; LLGLTFLoader::LLGLTFLoader(std::string filename, S32 lod, @@ -951,7 +952,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } // Indices handling - if (faceVertices.size() >= VERTICIES_LIMIT) + if (faceVertices.size() >= VERTEX_LIMIT) { // Will have to remap 32 bit indices into 16 bit indices // For the sake of simplicity build vector of 32 bit indices first @@ -1036,7 +1037,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas } indices_16.push_back((U16)vert_index); - if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTICIES_LIMIT - 1) + if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTEX_LIMIT) { LLVolumeFace face; face.fillFromLegacyData(face_verts, indices_16); -- cgit v1.2.3