summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2025-05-30 17:55:17 +0800
committerErik Kundiman <erik@megapahit.org>2025-05-30 17:55:17 +0800
commitd1353441f91d5776e1f4e72666f7c9de96eecca5 (patch)
treecd22d6ac18a1b64cb5c2a94f793e7d7cf27a8b4b
parent2c8521de6d357d74f5b256cc2f94e9951e654d83 (diff)
parent136149d1a196d2c0c15b9977937e64ccd26c1a49 (diff)
Merge remote-tracking branch 'secondlife/project/gltf-mesh-import' into gltf-mesh-import
-rw-r--r--indra/llprimitive/CMakeLists.txt2
-rw-r--r--indra/llprimitive/llgltfloader.cpp404
-rw-r--r--indra/llprimitive/llmodel.cpp156
-rw-r--r--indra/llprimitive/llmodel.h1
-rw-r--r--indra/llprimitive/llmodelloader.cpp2
-rw-r--r--indra/llprimitive/llmodelloader.h6
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/gltf/asset.cpp198
-rw-r--r--indra/newview/gltf/asset.h13
-rw-r--r--indra/newview/gltf/buffer_util.h6
-rw-r--r--indra/newview/gltf/llgltfloader.cpp1350
-rw-r--r--indra/newview/gltf/llgltfloader.h (renamed from indra/llprimitive/llgltfloader.h)20
-rw-r--r--indra/newview/gltfscenemanager.cpp2
-rw-r--r--indra/newview/llfilepicker.cpp4
-rw-r--r--indra/newview/llfloatermodelpreview.cpp6
-rw-r--r--indra/newview/llmaterialeditor.cpp3
-rw-r--r--indra/newview/llmodelpreview.cpp43
-rw-r--r--indra/newview/llskinningutil.cpp6
-rw-r--r--indra/newview/llvovolume.cpp7
-rw-r--r--indra/newview/skins/default/xui/en/floater_model_preview.xml4
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml5
21 files changed, 1726 insertions, 514 deletions
diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt
index 00d821c470..21d412baeb 100644
--- a/indra/llprimitive/CMakeLists.txt
+++ b/indra/llprimitive/CMakeLists.txt
@@ -34,7 +34,6 @@ endif(LINUX OR CMAKE_SYSTEM_NAME MATCHES FreeBSD )
set(llprimitive_SOURCE_FILES
lldaeloader.cpp
- llgltfloader.cpp
llgltfmaterial.cpp
llmaterialid.cpp
llmaterial.cpp
@@ -54,7 +53,6 @@ set(llprimitive_SOURCE_FILES
set(llprimitive_HEADER_FILES
CMakeLists.txt
lldaeloader.h
- llgltfloader.h
llgltfmaterial.h
llgltfmaterial_templates.h
legacy_object_types.h
diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp
deleted file mode 100644
index 480012699a..0000000000
--- a/indra/llprimitive/llgltfloader.cpp
+++ /dev/null
@@ -1,404 +0,0 @@
-/**
- * @file LLGLTFLoader.cpp
- * @brief LLGLTFLoader class implementation
- *
- * $LicenseInfo:firstyear=2022&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2022, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llgltfloader.h"
-
-// Import & define single-header gltf import/export lib
-#define TINYGLTF_IMPLEMENTATION
-#define TINYGLTF_USE_CPP14 // default is C++ 11
-
-// tinygltf by default loads image files using STB
-#define STB_IMAGE_IMPLEMENTATION
-// to use our own image loading:
-// 1. replace this definition with TINYGLTF_NO_STB_IMAGE
-// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)
-
-// tinygltf saves image files using STB
-#define STB_IMAGE_WRITE_IMPLEMENTATION
-// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data)
-
-// Additionally, disable inclusion of STB header files entirely with
-// TINYGLTF_NO_INCLUDE_STB_IMAGE
-// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE
-#include "tinygltf/tiny_gltf.h"
-
-
-// TODO: includes inherited from dae loader. Validate / prune
-
-#include "llsdserialize.h"
-#include "lljoint.h"
-
-#include "llmatrix4a.h"
-
-#include <boost/regex.hpp>
-#include <boost/algorithm/string/replace.hpp>
-
-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<std::string, std::string> &jointAliasMap,
- U32 maxJointsPerMesh,
- U32 modelLimit) //,
- //bool preprocess)
- : LLModelLoader( filename,
- lod,
- load_cb,
- joint_lookup_func,
- texture_load_func,
- state_cb,
- opaque_userdata,
- jointTransformMap,
- jointsFromNodes,
- jointAliasMap,
- maxJointsPerMesh ),
- //mPreprocessGLTF(preprocess),
- mMeshesLoaded(false),
- mMaterialsLoaded(false)
-{
-}
-
-LLGLTFLoader::~LLGLTFLoader() {}
-
-bool LLGLTFLoader::OpenFile(const std::string &filename)
-{
- tinygltf::TinyGLTF loader;
- std::string error_msg;
- std::string warn_msg;
- std::string filename_lc(filename);
- LLStringUtil::toLower(filename_lc);
-
- // Load a tinygltf model fom a file. Assumes that the input filename has already been
- // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish.
- if (std::string::npos == filename_lc.rfind(".gltf"))
- { // file is binary
- mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
- }
- else
- { // file is ascii
- mGltfLoaded = loader.LoadASCIIFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
- }
-
- if (!mGltfLoaded)
- {
- if (!warn_msg.empty())
- LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL;
- if (!error_msg.empty())
- LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL;
- return false;
- }
-
- mMeshesLoaded = parseMeshes();
- if (mMeshesLoaded) uploadMeshes();
-
- mMaterialsLoaded = parseMaterials();
- if (mMaterialsLoaded) uploadMaterials();
-
- return (mMeshesLoaded || mMaterialsLoaded);
-}
-
-bool LLGLTFLoader::parseMeshes()
-{
- if (!mGltfLoaded) return false;
-
- // 2022-04 DJH Volume params from dae example. TODO understand PCODE
- LLVolumeParams volume_params;
- volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
-
- for (tinygltf::Mesh mesh : mGltfModel.meshes)
- {
- LLModel *pModel = new LLModel(volume_params, 0.f);
-
- if (populateModelFromMesh(pModel, mesh) &&
- (LLModel::NO_ERRORS == pModel->getStatus()) &&
- validate_model(pModel))
- {
- mModelList.push_back(pModel);
- }
- else
- {
- setLoadState(ERROR_MODEL + pModel->getStatus());
- delete(pModel);
- return false;
- }
- }
- return true;
-}
-
-bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh)
-{
- pModel->mLabel = mesh.name;
- int pos_idx;
- tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a;
-
- auto prims = mesh.primitives;
- for (auto prim : prims)
- {
- if (prim.indices >= 0) indices_a = mGltfModel.accessors[prim.indices];
-
- pos_idx = (prim.attributes.count("POSITION") > 0) ? prim.attributes.at("POSITION") : -1;
- if (pos_idx >= 0)
- {
- positions_a = mGltfModel.accessors[pos_idx];
- if (TINYGLTF_COMPONENT_TYPE_FLOAT != positions_a.componentType)
- continue;
- auto positions_bv = mGltfModel.bufferViews[positions_a.bufferView];
- auto positions_buf = mGltfModel.buffers[positions_bv.buffer];
- //auto type = positions_vb.
- //if (positions_buf.name
- }
-
-#if 0
- int norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx;
- norm_idx = (prim.attributes.count("NORMAL") > 0) ? prim.attributes.at("NORMAL") : -1;
- tan_idx = (prim.attributes.count("TANGENT") > 0) ? prim.attributes.at("TANGENT") : -1;
- uv0_idx = (prim.attributes.count("TEXCOORDS_0") > 0) ? prim.attributes.at("TEXCOORDS_0") : -1;
- uv1_idx = (prim.attributes.count("TEXCOORDS_1") > 0) ? prim.attributes.at("TEXCOORDS_1") : -1;
- color0_idx = (prim.attributes.count("COLOR_0") > 0) ? prim.attributes.at("COLOR_0") : -1;
- color1_idx = (prim.attributes.count("COLOR_1") > 0) ? prim.attributes.at("COLOR_1") : -1;
-#endif
-
- if (prim.mode == TINYGLTF_MODE_TRIANGLES)
- {
- //auto pos = mesh. TODO resume here DJH 2022-04
- }
- }
-
- //pModel->addFace()
- return false;
-}
-
-bool LLGLTFLoader::parseMaterials()
-{
- if (!mGltfLoaded) return false;
-
- // fill local texture data structures
- mSamplers.clear();
- for (auto in_sampler : mGltfModel.samplers)
- {
- gltf_sampler sampler;
- sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR;
- sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;;
- sampler.wrapS = in_sampler.wrapS;
- sampler.wrapT = in_sampler.wrapT;
- sampler.name = in_sampler.name; // unused
- mSamplers.push_back(sampler);
- }
-
- mImages.clear();
- for (auto in_image : mGltfModel.images)
- {
- gltf_image image;
- image.numChannels = in_image.component;
- image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes
- image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc
- image.size = static_cast<U32>(in_image.image.size());
- image.height = in_image.height;
- image.width = in_image.width;
- image.data = in_image.image.data();
-
- if (in_image.as_is)
- {
- LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL;
- return false;
- }
-
- if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel)
- {
- LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL;
- return false;
- }
-
- mImages.push_back(image);
- }
-
- mTextures.clear();
- for (auto in_tex : mGltfModel.textures)
- {
- gltf_texture tex;
- tex.imageIdx = in_tex.source;
- tex.samplerIdx = in_tex.sampler;
- tex.imageUuid.setNull();
-
- if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size())
- {
- LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL;
- return false;
- }
-
- mTextures.push_back(tex);
- }
-
- // parse each material
- for (tinygltf::Material gltf_material : mGltfModel.materials)
- {
- gltf_render_material mat;
- mat.name = gltf_material.name;
-
- tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness;
- mat.hasPBR = true; // Always true, for now
-
- mat.baseColor.set(pbr.baseColorFactor.data());
- mat.hasBaseTex = pbr.baseColorTexture.index >= 0;
- mat.baseColorTexIdx = pbr.baseColorTexture.index;
- mat.baseColorTexCoords = pbr.baseColorTexture.texCoord;
-
- mat.metalness = pbr.metallicFactor;
- mat.roughness = pbr.roughnessFactor;
- mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0;
- mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index;
- mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord;
-
- mat.normalScale = gltf_material.normalTexture.scale;
- mat.hasNormalTex = gltf_material.normalTexture.index >= 0;
- mat.normalTexIdx = gltf_material.normalTexture.index;
- mat.normalTexCoords = gltf_material.normalTexture.texCoord;
-
- mat.occlusionScale = gltf_material.occlusionTexture.strength;
- mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0;
- mat.occlusionTexIdx = gltf_material.occlusionTexture.index;
- mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord;
-
- mat.emissiveColor.set(gltf_material.emissiveFactor.data());
- mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0;
- mat.emissiveTexIdx = gltf_material.emissiveTexture.index;
- mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord;
-
- mat.alphaMode = gltf_material.alphaMode;
- mat.alphaMask = gltf_material.alphaCutoff;
-
- if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) ||
- (mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) ||
- (mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) ||
- (mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) ||
- (mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size())))
- {
- LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL;
- return false;
- }
-
- if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV
- (mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) ||
- (mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) ||
- (mat.hasBaseTex && (mat.baseColorTexCoords > 2)) ||
- (mat.hasMRTex && (mat.metalRoughTexCoords > 2)))
- {
- LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL;
- return false;
- }
-
- mMaterials.push_back(mat);
- }
-
- return true;
-}
-
-// TODO: convert raw vertex buffers to UUIDs
-void LLGLTFLoader::uploadMeshes()
-{
- llassert(0);
-}
-
-// convert raw image buffers to texture UUIDs & assemble into a render material
-void LLGLTFLoader::uploadMaterials()
-{
- for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple
- {
- if (mat.hasBaseTex)
- {
- gltf_texture& gtex = mTextures[mat.baseColorTexIdx];
- if (gtex.imageUuid.isNull())
- {
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
- }
- }
-
- if (mat.hasMRTex)
- {
- gltf_texture& gtex = mTextures[mat.metalRoughTexIdx];
- if (gtex.imageUuid.isNull())
- {
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
- }
- }
-
- if (mat.hasNormalTex)
- {
- gltf_texture& gtex = mTextures[mat.normalTexIdx];
- if (gtex.imageUuid.isNull())
- {
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
- }
- }
-
- if (mat.hasOcclusionTex)
- {
- gltf_texture& gtex = mTextures[mat.occlusionTexIdx];
- if (gtex.imageUuid.isNull())
- {
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
- }
- }
-
- if (mat.hasEmissiveTex)
- {
- gltf_texture& gtex = mTextures[mat.emissiveTexIdx];
- if (gtex.imageUuid.isNull())
- {
- gtex.imageUuid = imageBufferToTextureUUID(gtex);
- }
- }
- }
-}
-
-LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex)
-{
- //gltf_image& image = mImages[tex.imageIdx];
- //gltf_sampler& sampler = mSamplers[tex.samplerIdx];
-
- // fill an LLSD container with image+sampler data
-
- // upload texture
-
- // retrieve UUID
-
- return LLUUID::null;
-}
diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp
index 6b4bb3a8b0..0bc7846023 100644
--- a/indra/llprimitive/llmodel.cpp
+++ b/indra/llprimitive/llmodel.cpp
@@ -334,6 +334,162 @@ void LLModel::normalizeVolumeFaces()
}
}
+void LLModel::normalizeVolumeFacesAndWeights()
+{
+ if (!mVolumeFaces.empty())
+ {
+ LLVector4a min, max;
+
+ // For all of the volume faces
+ // in the model, loop over
+ // them and see what the extents
+ // of the volume along each axis.
+ min = mVolumeFaces[0].mExtents[0];
+ max = mVolumeFaces[0].mExtents[1];
+
+ for (U32 i = 1; i < mVolumeFaces.size(); ++i)
+ {
+ LLVolumeFace& face = mVolumeFaces[i];
+
+ update_min_max(min, max, face.mExtents[0]);
+ update_min_max(min, max, face.mExtents[1]);
+
+ if (face.mTexCoords)
+ {
+ LLVector2& min_tc = face.mTexCoordExtents[0];
+ LLVector2& max_tc = face.mTexCoordExtents[1];
+
+ min_tc = face.mTexCoords[0];
+ max_tc = face.mTexCoords[0];
+
+ for (S32 j = 1; j < face.mNumVertices; ++j)
+ {
+ update_min_max(min_tc, max_tc, face.mTexCoords[j]);
+ }
+ }
+ else
+ {
+ face.mTexCoordExtents[0].set(0, 0);
+ face.mTexCoordExtents[1].set(1, 1);
+ }
+ }
+
+ // Now that we have the extents of the model
+ // we can compute the offset needed to center
+ // the model at the origin.
+
+ // Compute center of the model
+ // and make it negative to get translation
+ // needed to center at origin.
+ LLVector4a trans;
+ trans.setAdd(min, max);
+ trans.mul(-0.5f);
+
+ // Compute the total size along all
+ // axes of the model.
+ LLVector4a size;
+ size.setSub(max, min);
+
+ // Prevent division by zero.
+ F32 x = size[0];
+ F32 y = size[1];
+ F32 z = size[2];
+ F32 w = size[3];
+ if (fabs(x) < F_APPROXIMATELY_ZERO)
+ {
+ x = 1.0;
+ }
+ if (fabs(y) < F_APPROXIMATELY_ZERO)
+ {
+ y = 1.0;
+ }
+ if (fabs(z) < F_APPROXIMATELY_ZERO)
+ {
+ z = 1.0;
+ }
+ size.set(x, y, z, w);
+
+ // Compute scale as reciprocal of size
+ LLVector4a scale;
+ scale.splat(1.f);
+ scale.div(size);
+
+ LLVector4a inv_scale(1.f);
+ inv_scale.div(scale);
+
+ for (U32 i = 0; i < mVolumeFaces.size(); ++i)
+ {
+ LLVolumeFace& face = mVolumeFaces[i];
+
+ // We shrink the extents so
+ // that they fall within
+ // the unit cube.
+ // VFExtents change
+ face.mExtents[0].add(trans);
+ face.mExtents[0].mul(scale);
+
+ face.mExtents[1].add(trans);
+ face.mExtents[1].mul(scale);
+
+ // For all the positions, we scale
+ // the positions to fit within the unit cube.
+ LLVector4a* pos = (LLVector4a*)face.mPositions;
+ LLVector4a* norm = (LLVector4a*)face.mNormals;
+ LLVector4a* t = (LLVector4a*)face.mTangents;
+
+ for (S32 j = 0; j < face.mNumVertices; ++j)
+ {
+ pos[j].add(trans);
+ pos[j].mul(scale);
+ if (norm && !norm[j].equals3(LLVector4a::getZero()))
+ {
+ norm[j].mul(inv_scale);
+ norm[j].normalize3();
+ }
+
+ if (t)
+ {
+ F32 w = t[j].getF32ptr()[3];
+ t[j].mul(inv_scale);
+ t[j].normalize3();
+ t[j].getF32ptr()[3] = w;
+ }
+ }
+ }
+
+ weight_map old_weights = mSkinWeights;
+ mSkinWeights.clear();
+ mPosition.clear();
+
+ for (auto& weights : old_weights)
+ {
+ LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]);
+ pos.add(trans);
+ pos.mul(scale);
+ LLVector3 scaled_pos(pos.getF32ptr());
+ mPosition.push_back(scaled_pos);
+ mSkinWeights[scaled_pos] = weights.second;
+ }
+
+ // mNormalizedScale is the scale at which
+ // we would need to multiply the model
+ // by to get the original size of the
+ // model instead of the normalized size.
+ LLVector4a normalized_scale;
+ normalized_scale.splat(1.f);
+ normalized_scale.div(scale);
+ mNormalizedScale.set(normalized_scale.getF32ptr());
+ mNormalizedTranslation.set(trans.getF32ptr());
+ mNormalizedTranslation *= -1.f;
+
+ // remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation)
+ for (auto& face : mVolumeFaces)
+ {
+ face.mNormalizedScale = mNormalizedScale;
+ }
+ }
+}
+
void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const
{
scale_out = mNormalizedScale;
diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h
index 4b5d079b48..521bd54fd1 100644
--- a/indra/llprimitive/llmodel.h
+++ b/indra/llprimitive/llmodel.h
@@ -204,6 +204,7 @@ public:
void sortVolumeFacesByMaterialName();
void normalizeVolumeFaces();
+ void normalizeVolumeFacesAndWeights();
void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL);
void remapVolumeFaces();
void optimizeVolumeFaces();
diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp
index 84adec4da5..dee39175f8 100644
--- a/indra/llprimitive/llmodelloader.cpp
+++ b/indra/llprimitive/llmodelloader.cpp
@@ -150,6 +150,8 @@ void LLModelLoader::run()
{
mWarningsArray.clear();
doLoadModel();
+ // todo: we are inside of a thread, push this into main thread worker,
+ // not into doOnIdleOneTime that laks tread safety
doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this));
}
diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h
index 530e61e2b8..73ec0ed1f4 100644
--- a/indra/llprimitive/llmodelloader.h
+++ b/indra/llprimitive/llmodelloader.h
@@ -111,6 +111,7 @@ public:
bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info)
model_list mModelList;
+ // The scene is pretty much what ends up getting loaded for upload. Basically assign things to this guy if you want something uploaded.
scene mScene;
typedef std::queue<LLPointer<LLModel> > model_queue;
@@ -119,9 +120,14 @@ public:
model_queue mPhysicsQ;
//map of avatar joints as named in COLLADA assets to internal joint names
+ // Do not use this for anything other than looking up the name of a joint. This is populated elsewhere.
JointMap mJointMap;
+
+ // The joint list is what you want to use to actually setup the specific joint transformations.
JointTransformMap& mJointList;
JointNameSet& mJointsFromNode;
+
+
U32 mMaxJointsPerMesh;
LLModelLoader(
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 80f0b60f98..764f1f6b21 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -81,6 +81,7 @@ set(viewer_SOURCE_FILES
gltf/accessor.cpp
gltf/primitive.cpp
gltf/animation.cpp
+ gltf/llgltfloader.cpp
groupchatlistener.cpp
llaccountingcostmanager.cpp
llaisapi.cpp
@@ -762,6 +763,7 @@ set(viewer_HEADER_FILES
gltf/buffer_util.h
gltf/primitive.h
gltf/animation.h
+ gltf/llgltfloader.h
llaccountingcost.h
llaccountingcostmanager.h
llaisapi.h
diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp
index a399a59f40..f40cc58440 100644
--- a/indra/newview/gltf/asset.cpp
+++ b/indra/newview/gltf/asset.cpp
@@ -50,6 +50,10 @@ namespace LL
"KHR_texture_transform"
};
+ static std::unordered_set<std::string> ExtensionsIgnored = {
+ "KHR_materials_pbrSpecularGlossiness"
+ };
+
Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode)
{
if (alpha_mode == "OPAQUE")
@@ -472,11 +476,14 @@ void Asset::update()
for (auto& image : mImages)
{
- if (image.mTexture.notNull())
- { // HACK - force texture to be loaded full rez
- // TODO: calculate actual vsize
- image.mTexture->addTextureStats(2048.f * 2048.f);
- image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH);
+ if (image.mLoadIntoTexturePipe)
+ {
+ if (image.mTexture.notNull())
+ { // HACK - force texture to be loaded full rez
+ // TODO: calculate actual vsize
+ image.mTexture->addTextureStats(2048.f * 2048.f);
+ image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH);
+ }
}
}
}
@@ -486,18 +493,23 @@ void Asset::update()
bool Asset::prep()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
- // check required extensions and fail if not supported
- bool unsupported = false;
+ // check required extensions
for (auto& extension : mExtensionsRequired)
{
if (ExtensionsSupported.find(extension) == ExtensionsSupported.end())
{
- LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL;
- unsupported = true;
+ if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end())
+ {
+ LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL;
+ mUnsupportedExtensions.push_back(extension);
+ }
+ else
+ {
+ mIgnoredExtensions.push_back(extension);
+ }
}
}
-
- if (unsupported)
+ if (mUnsupportedExtensions.size() > 0)
{
return false;
}
@@ -513,7 +525,7 @@ bool Asset::prep()
for (auto& image : mImages)
{
- if (!image.prep(*this))
+ if (!image.prep(*this, mLoadIntoVRAM))
{
return false;
}
@@ -542,102 +554,106 @@ bool Asset::prep()
return false;
}
}
+ if (mLoadIntoVRAM)
+ {
+ // prepare vertex buffers
- // prepare vertex buffers
-
- // material count is number of materials + 1 for default material
- U32 mat_count = (U32) mMaterials.size() + 1;
-
- if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
- { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
- gDebugProgram.bind();
- }
+ // material count is number of materials + 1 for default material
+ U32 mat_count = (U32) mMaterials.size() + 1;
- for (S32 double_sided = 0; double_sided < 2; ++double_sided)
- {
- RenderData& rd = mRenderData[double_sided];
- for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)
- {
- rd.mBatches[i].resize(mat_count);
+ if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
+ { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
+ gDebugProgram.bind();
}
- // for each material
- for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id)
+ for (S32 double_sided = 0; double_sided < 2; ++double_sided)
{
- // for each shader variant
- U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
- U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
-
- S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided;
- if (ds_mat != double_sided)
+ RenderData& rd = mRenderData[double_sided];
+ for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)
{
- continue;
+ rd.mBatches[i].resize(mat_count);
}
- for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant)
+ // for each material
+ for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id)
{
- U32 attribute_mask = 0;
- // for each mesh
- for (auto& mesh : mMeshes)
+ // for each shader variant
+ U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
+ U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
+
+ S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided;
+ if (ds_mat != double_sided)
{
- // for each primitive
- for (auto& primitive : mesh.mPrimitives)
+ continue;
+ }
+
+ for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant)
+ {
+ U32 attribute_mask = 0;
+ // for each mesh
+ for (auto& mesh : mMeshes)
{
- if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
+ // for each primitive
+ for (auto& primitive : mesh.mPrimitives)
{
- // accumulate vertex and index counts
- primitive.mVertexOffset = vertex_count[variant];
- primitive.mIndexOffset = index_count[variant];
+ if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
+ {
+ // accumulate vertex and index counts
+ primitive.mVertexOffset = vertex_count[variant];
+ primitive.mIndexOffset = index_count[variant];
- vertex_count[variant] += primitive.getVertexCount();
- index_count[variant] += primitive.getIndexCount();
+ vertex_count[variant] += primitive.getVertexCount();
+ index_count[variant] += primitive.getIndexCount();
- // all primitives of a given variant and material should all have the same attribute mask
- llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask);
- attribute_mask |= primitive.mAttributeMask;
+ // all primitives of a given variant and material should all have the same attribute mask
+ llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask);
+ attribute_mask |= primitive.mAttributeMask;
+ }
}
}
- }
- // allocate vertex buffer and pack it
- if (vertex_count[variant] > 0)
- {
- U32 mat_idx = mat_id + 1;
- LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);
+ // allocate vertex buffer and pack it
+ if (vertex_count[variant] > 0)
+ {
+ U32 mat_idx = mat_id + 1;
+ #if 0
+ LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);
- rd.mBatches[variant][mat_idx].mVertexBuffer = vb;
- vb->allocateBuffer(vertex_count[variant],
- index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used
- vb->setBuffer();
+ rd.mBatches[variant][mat_idx].mVertexBuffer = vb;
+ vb->allocateBuffer(vertex_count[variant],
+ index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used
+ vb->setBuffer();
- for (auto& mesh : mMeshes)
- {
- for (auto& primitive : mesh.mPrimitives)
+ for (auto& mesh : mMeshes)
{
- if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
+ for (auto& primitive : mesh.mPrimitives)
{
- primitive.upload(vb);
+ if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
+ {
+ primitive.upload(vb);
+ }
}
}
- }
- vb->unmapBuffer();
+ vb->unmapBuffer();
- vb->unbind();
+ vb->unbind();
+ #endif
+ }
}
}
}
- }
- // sanity check that all primitives have a vertex buffer
- for (auto& mesh : mMeshes)
- {
- for (auto& primitive : mesh.mPrimitives)
+ // sanity check that all primitives have a vertex buffer
+ for (auto& mesh : mMeshes)
{
- llassert(primitive.mVertexBuffer.notNull());
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ //llassert(primitive.mVertexBuffer.notNull());
+ }
}
}
-
+ #if 0
// build render batches
for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)
{
@@ -664,6 +680,7 @@ bool Asset::prep()
}
}
}
+ #endif
return true;
}
@@ -672,9 +689,10 @@ Asset::Asset(const Value& src)
*this = src;
}
-bool Asset::load(std::string_view filename)
+bool Asset::load(std::string_view filename, bool loadIntoVRAM)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ mLoadIntoVRAM = loadIntoVRAM;
mFilename = filename;
std::string ext = gDirUtilp->getExtension(mFilename);
@@ -692,7 +710,7 @@ bool Asset::load(std::string_view filename)
}
else if (ext == "glb")
{
- return loadBinary(str);
+ return loadBinary(str, mLoadIntoVRAM);
}
else
{
@@ -709,8 +727,9 @@ bool Asset::load(std::string_view filename)
return false;
}
-bool Asset::loadBinary(const std::string& data)
+bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM)
{
+ mLoadIntoVRAM = loadIntoVRAM;
// load from binary gltf
const U8* ptr = (const U8*)data.data();
const U8* end = ptr + data.size();
@@ -935,8 +954,9 @@ void Asset::eraseBufferView(S32 bufferView)
LLViewerFetchedTexture* fetch_texture(const LLUUID& id);
-bool Image::prep(Asset& asset)
+bool Image::prep(Asset& asset, bool loadIntoVRAM)
{
+ mLoadIntoTexturePipe = loadIntoVRAM;
LLUUID id;
if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull())
{ // loaded from an asset, fetch the texture from the asset system
@@ -951,12 +971,12 @@ bool Image::prep(Asset& asset)
{ // embedded in a buffer, load the texture from the buffer
BufferView& bufferView = asset.mBufferViews[mBufferView];
Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
-
- U8* data = buffer.mData.data() + bufferView.mByteOffset;
-
- mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType);
-
- if (mTexture.isNull())
+ if (mLoadIntoTexturePipe)
+ {
+ U8* data = buffer.mData.data() + bufferView.mByteOffset;
+ mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType);
+ }
+ else if (mTexture.isNull() && mLoadIntoTexturePipe)
{
LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL;
LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
@@ -971,12 +991,12 @@ bool Image::prep(Asset& asset)
std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri;
LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file);
- if (tracking_id.notNull())
+ if (tracking_id.notNull() && mLoadIntoTexturePipe)
{
LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id);
mTexture = LLViewerTextureManager::getFetchedTexture(world_id);
}
- else
+ else if (mLoadIntoTexturePipe)
{
LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL;
LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
@@ -991,7 +1011,7 @@ bool Image::prep(Asset& asset)
return false;
}
- if (!asset.mFilename.empty())
+ if (!asset.mFilename.empty() && mLoadIntoTexturePipe)
{ // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload
mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW);
mTexture->forceToSaveRawImage(0, F32_MAX);
diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h
index 27821659db..b9554d753c 100644
--- a/indra/newview/gltf/asset.h
+++ b/indra/newview/gltf/asset.h
@@ -286,6 +286,7 @@ namespace LL
void serialize(boost::json::object& dst) const;
};
+ // Image is for images that we want to load for the given asset. This acts as an interface into the viewer's texture pipe.
class Image
{
public:
@@ -301,6 +302,8 @@ namespace LL
S32 mBits = -1;
S32 mPixelType = -1;
+ bool mLoadIntoTexturePipe = false;
+
LLPointer<LLViewerFetchedTexture> mTexture;
const Image& operator=(const Value& src);
@@ -316,7 +319,7 @@ namespace LL
// preserve only uri and name
void clearData(Asset& asset);
- bool prep(Asset& asset);
+ bool prep(Asset& asset, bool loadIntoVRAM);
};
// Render Batch -- vertex buffer and list of primitives to render using
@@ -391,6 +394,10 @@ namespace LL
// UBO for storing material data
U32 mMaterialsUBO = 0;
+ bool mLoadIntoVRAM = false;
+
+ std::vector<std::string> mUnsupportedExtensions;
+ std::vector<std::string> mIgnoredExtensions;
// prepare for first time use
bool prep();
@@ -428,12 +435,12 @@ namespace LL
// accepts .gltf and .glb files
// Any existing data will be lost
// returns result of prep() on success
- bool load(std::string_view filename);
+ bool load(std::string_view filename, bool loadIntoVRAM);
// load .glb contents from memory
// data - binary contents of .glb file
// returns result of prep() on success
- bool loadBinary(const std::string& data);
+ bool loadBinary(const std::string& data, bool loadIntoVRAM);
const Asset& operator=(const Value& src);
void serialize(boost::json::object& dst) const;
diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h
index ef9bba8128..2632a0f263 100644
--- a/indra/newview/gltf/buffer_util.h
+++ b/indra/newview/gltf/buffer_util.h
@@ -159,6 +159,12 @@ namespace LL
}
template<>
+ inline void copyVec3<F32, LLColor4U>(F32* src, LLColor4U& dst)
+ {
+ dst.set((U8)(src[0] * 255.f), (U8)(src[1] * 255.f), (U8)(src[2] * 255.f), 255);
+ }
+
+ template<>
inline void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)
{
dst.set((U8)(src[0]), (U8)(src[1]), (U8)(src[2]), 255);
diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp
new file mode 100644
index 0000000000..5cdd7f09e0
--- /dev/null
+++ b/indra/newview/gltf/llgltfloader.cpp
@@ -0,0 +1,1350 @@
+/**
+ * @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"
+#include "meshoptimizer.h"
+#include <glm/gtc/packing.hpp>
+
+// 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 <boost/regex.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+static const std::string lod_suffix[LLModel::NUM_LODS] =
+{
+ "_LOD0",
+ "_LOD1",
+ "_LOD2",
+ "",
+ "_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,
+ 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<std::string, std::string> &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 filename_lc(filename);
+ LLStringUtil::toLower(filename_lc);
+
+ mGltfLoaded = mGLTFAsset.load(filename, false);
+
+ if (!mGltfLoaded)
+ {
+ notifyUnsupportedExtension(true);
+ return false;
+ }
+
+ notifyUnsupportedExtension(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);
+
+ 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)
+ {
+ populateJointFromSkin(skin);
+ }
+
+ // Track how many times each mesh name has been used
+ std::map<std::string, S32> mesh_name_counts;
+
+ // Process each node
+ for (auto& node : mGLTFAsset.mNodes)
+ {
+ LLMatrix4 transformation;
+ material_map mats;
+ auto meshidx = node.mMesh;
+
+ if (meshidx >= 0)
+ {
+ if (mGLTFAsset.mMeshes.size() > meshidx)
+ {
+ 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())
+ {
+ 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))
+ {
+ mModelList.push_back(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);
+ }
+
+ mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats));
+ stretch_extents(pModel, transformation);
+ }
+ else
+ {
+ setLoadState(ERROR_MODEL + pModel->getStatus());
+ delete pModel;
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const
+{
+ if (node_index < 0 || node_index >= static_cast<S32>(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<LL::GLTF::Node&>(node).makeMatrixValid();
+
+ // Start with this node's transform
+ combined_transform = node.mMatrix;
+
+ // Find and apply parent transform if it exists
+ for (size_t i = 0; i < asset.mNodes.size(); ++i)
+ {
+ const auto& potential_parent = asset.mNodes[i];
+ auto it = std::find(potential_parent.mChildren.begin(), potential_parent.mChildren.end(), node_index);
+
+ if (it != potential_parent.mChildren.end())
+ {
+ // Found parent - recursively get its combined transform and apply it
+ glm::mat4 parent_transform;
+ computeCombinedNodeTransform(asset, static_cast<S32>(i), parent_transform);
+ combined_transform = parent_transform * combined_transform;
+ return; // Early exit - a node can only have one parent
+ }
+ }
+}
+
+bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count)
+{
+ // Create unique model name
+ std::string base_name = mesh.mName;
+ if (base_name.empty())
+ {
+ S32 mesh_index = static_cast<S32>(&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;
+
+ // Compute final combined transform matrix (hierarchy + coordinate rotation)
+ S32 node_index = static_cast<S32>(&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;
+
+ // 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)));
+
+ // Mark unsuported joints with '-1' so that they won't get added into weights
+ // GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones.
+ std::vector<S32> gltf_joint_index_use_count;
+ 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];
+
+ 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)
+ {
+ // Unfortunately, SLM does not support 32 bit indices. Filter out anything that goes beyond 16 bit.
+ if (prim.getVertexCount() < USHRT_MAX)
+ {
+ // So primitives already have all of the data we need for a given face in SL land.
+ // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call
+ // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07
+ LLVolumeFace face;
+ std::vector<GLTFVertex> vertices;
+ std::vector<U16> indices;
+
+ 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 << 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;
+ }
+ }
+ }
+ }
+ }
+
+ // 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);
+
+ // Use pre-computed normal_transform
+ glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]);
+ vert.normal = glm::normalize(normal_transform * normal_vec);
+
+ vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]);
+
+ if (skinIdx >= 0)
+ {
+ vert.weights = glm::vec4(prim.mWeights[i]);
+
+ auto accessorIdx = prim.mAttributes["JOINTS_0"];
+ LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE;
+ if (accessorIdx >= 0)
+ {
+ auto accessor = mGLTFAsset.mAccessors[accessorIdx];
+ componentType = accessor.mComponentType;
+ }
+
+ // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short.
+ // Detect and unpack accordingly.
+ if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE)
+ {
+ auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF));
+ vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w);
+ }
+ else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT)
+ {
+ vert.joints = glm::unpackUint4x16(prim.mJoints[i]);
+ }
+ else
+ {
+ vert.joints = glm::zero<glm::u16vec4>();
+ vert.weights = glm::zero<glm::vec4>();
+ }
+ }
+ vertices.push_back(vert);
+ }
+
+ if (prim.getIndexCount() % 3 != 0)
+ {
+ 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)
+ {
+ 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
+ if (vertices.empty())
+ {
+ LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL;
+ continue; // Skip this primitive
+ }
+
+ std::vector<LLVolumeFace::VertexData> faceVertices;
+ glm::vec3 min = glm::vec3(FLT_MAX);
+ glm::vec3 max = glm::vec3(-FLT_MAX);
+
+ for (U32 i = 0; i < vertices.size(); i++)
+ {
+ LLVolumeFace::VertexData vert;
+
+ // Update min/max bounds
+ if (i == 0)
+ {
+ min = max = vertices[i].position;
+ }
+ else
+ {
+ 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);
+ vert.setNormal(normal);
+ vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y);
+ faceVertices.push_back(vert);
+
+ 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::vector<LLModel::JointWeight> wght;
+ F32 total = 0.f;
+
+ for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j)
+ {
+ // take up to 4 most significant weights
+ // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex.
+ wght.push_back(weight_list[j]);
+ total += weight_list[j].mWeight;
+ }
+
+ 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;
+ }
+ }
+ }
+
+ 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);
+
+ // 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("GLTF_IMPORT") << "Unable to process mesh due to 16-bit index limits" << LL_ENDL;
+ LLSD args;
+ args["Message"] = "ParsingErrorBadElement";
+ mWarningsArray.append(args);
+ return false;
+ }
+ }
+
+ // Call normalizeVolumeFacesAndWeights to compute proper extents
+ pModel->normalizeVolumeFacesAndWeights();
+
+ // 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_IMPORT") << "Bind matrices count mismatch joints count" << LL_ENDL;
+ LLSD args;
+ args["Message"] = "InvBindCountMismatch";
+ mWarningsArray.append(args);
+ }
+
+ std::vector<S32> 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];
+
+ 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())
+ {
+ // 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;
+
+ // 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 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]));
+ }
+ }
+
+ // 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;
+}
+
+void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin)
+{
+ LL_INFOS("GLTF_DEBUG") << "populateJointFromSkin: Processing " << skin.mJoints.size() << " joints" << LL_ENDL;
+
+ 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
+ {
+ // ignore unrecognized joint
+ LL_DEBUGS("GLTF") << "Ignoring 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++)
+ {
+ 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_INFOS("GLTF_DEBUG") << "mJointList name: " << legal_name << " val: " << gltf_transform << LL_ENDL;
+ }
+}
+
+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<S32>::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<S32>::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;
+
+ // 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<U32>(image.height * image.width * image.numChannels * image.bytesPerChannel);
+ }
+ }
+ else
+ {
+ // Fallback to provided dimensions
+ image.height = in_image.mHeight;
+ image.width = in_image.mWidth;
+ if (image.height > 0 && image.width > 0 && image.numChannels > 0 && image.bytesPerChannel > 0)
+ {
+ image.size = static_cast<U32>(image.height * image.width * image.numChannels * image.bytesPerChannel);
+ }
+ }
+
+ // 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()
+{
+ 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<LLMatrix4, model_instance_list>, not an array, so we need to iterate through it correctly
+ for (auto& scene_entry : mScene)
+ {
+ for (LLModelInstance& instance : scene_entry.second)
+ {
+ LLModel* model = instance.mModel;
+
+ 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)
+{
+ 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];
+
+ 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;
+
+ // 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;
+}
+
+void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported)
+{
+ std::vector<std::string> extensions = unsupported ? mGLTFAsset.mUnsupportedExtensions : mGLTFAsset.mIgnoredExtensions;
+ if (extensions.size() > 0)
+ {
+ LLSD args;
+ args["Message"] = unsupported ? "UnsupportedExtension" : "IgnoredExtension";
+ std::string del;
+ std::string ext;
+ for (auto& extension : extensions)
+ {
+ ext += del;
+ ext += extension;
+ del = ",";
+ }
+ args["EXT"] = ext;
+ mWarningsArray.append(args);
+ }
+}
+
diff --git a/indra/llprimitive/llgltfloader.h b/indra/newview/gltf/llgltfloader.h
index 66671d1c5a..6e0fe2b32c 100644
--- a/indra/llprimitive/llgltfloader.h
+++ b/indra/newview/gltf/llgltfloader.h
@@ -29,6 +29,8 @@
#include "tinygltf/tiny_gltf.h"
+#include "asset.h"
+
#include "llglheaders.h"
#include "llmodelloader.h"
@@ -137,7 +139,17 @@ class LLGLTFLoader : public LLModelLoader
virtual bool OpenFile(const std::string &filename);
+ struct GLTFVertex
+ {
+ glm::vec3 position;
+ glm::vec3 normal;
+ glm::vec2 uv0;
+ glm::u16vec4 joints;
+ glm::vec4 weights;
+ };
+
protected:
+ LL::GLTF::Asset mGLTFAsset;
tinygltf::Model mGltfModel;
bool mGltfLoaded;
bool mMeshesLoaded;
@@ -155,9 +167,15 @@ private:
void uploadMeshes();
bool parseMaterials();
void uploadMaterials();
- bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh);
+ void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const;
+ bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count);
+ void populateJointFromSkin(const LL::GLTF::Skin& skin);
+ S32 findValidRootJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const;
+ S32 findGLTFRootJoint(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint
LLUUID imageBufferToTextureUUID(const gltf_texture& tex);
+ void notifyUnsupportedExtension(bool unsupported);
+
// bool mPreprocessGLTF;
/* Below inherited from dae loader - unknown if/how useful here
diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp
index 9a381f9ba6..5dec4dd62f 100644
--- a/indra/newview/gltfscenemanager.cpp
+++ b/indra/newview/gltfscenemanager.cpp
@@ -317,7 +317,7 @@ void GLTFSceneManager::load(const std::string& filename)
{
std::shared_ptr<Asset> asset = std::make_shared<Asset>();
- if (asset->load(filename))
+ if (asset->load(filename, true))
{
gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions
asset->updateTransforms();
diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp
index 2516bece8d..66e19d551e 100644
--- a/indra/newview/llfilepicker.cpp
+++ b/indra/newview/llfilepicker.cpp
@@ -64,7 +64,7 @@ LLFilePicker LLFilePicker::sInstance;
#define XML_FILTER L"XML files (*.xml)\0*.xml\0"
#define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0"
#define RAW_FILTER L"RAW files (*.raw)\0*.raw\0"
-#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0"
+#define MODEL_FILTER L"Model files (*.dae, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0"
#define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0"
#define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0"
#define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0"
@@ -676,6 +676,8 @@ std::unique_ptr<std::vector<std::string>> LLFilePicker::navOpenFilterProc(ELoadF
case FFLOAD_HDRI:
allowedv->push_back("exr");
case FFLOAD_MODEL:
+ allowedv->push_back("gltf");
+ allowedv->push_back("glb");
case FFLOAD_COLLADA:
allowedv->push_back("dae");
break;
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index 8332a430e6..96f9ddbf8a 100644
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -64,6 +64,7 @@
#include "llcallbacklist.h"
#include "llviewertexteditor.h"
#include "llviewernetwork.h"
+#include "llmaterialeditor.h"
//static
@@ -113,6 +114,11 @@ void LLMeshFilePicker::notify(const std::vector<std::string>& filenames)
if (filenames.size() > 0)
{
mMP->loadModel(filenames[0], mLOD);
+
+ if (filenames[0].substr(filenames[0].length() - 4) == ".glb" || filenames[0].substr(filenames[0].length() - 5) == ".gltf")
+ {
+ LLMaterialEditor::loadMaterialFromFile(filenames[0], -1);
+ }
}
else
{
diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp
index 28160177f6..378f5fdf91 100644
--- a/indra/newview/llmaterialeditor.cpp
+++ b/indra/newview/llmaterialeditor.cpp
@@ -137,7 +137,8 @@ LLFloaterComboOptions* LLFloaterComboOptions::showUI(
{
combo_picker->mComboOptions->addSimpleElement(*iter);
}
- combo_picker->mComboOptions->selectFirstItem();
+ // select 'Bulk Upload All' option
+ combo_picker->mComboOptions->selectNthItem((S32)options.size() - 1);
combo_picker->openFloater(LLSD(title));
combo_picker->setFocus(true);
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index c73282dad3..c7cb2b68e0 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -30,7 +30,7 @@
#include "llmodelloader.h"
#include "lldaeloader.h"
-#include "llgltfloader.h"
+#include "gltf/llgltfloader.h"
#include "llfloatermodelpreview.h"
#include "llagent.h"
@@ -3090,25 +3090,48 @@ void LLModelPreview::lookupLODModelFiles(S32 lod)
S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS;
std::string lod_filename = mLODFile[LLModel::LOD_HIGH];
- std::string ext = ".dae";
std::string lod_filename_lower(lod_filename);
LLStringUtil::toLower(lod_filename_lower);
- std::string::size_type i = lod_filename_lower.rfind(ext);
- if (i != std::string::npos)
+
+ // Check for each supported file extension
+ std::vector<std::string> supported_exts = { ".dae", ".gltf", ".glb" };
+ std::string found_ext;
+ std::string::size_type ext_pos = std::string::npos;
+
+ for (const auto& ext : supported_exts)
{
- lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext);
+ std::string::size_type i = lod_filename_lower.rfind(ext);
+ if (i != std::string::npos)
+ {
+ ext_pos = i;
+ found_ext = ext;
+ break;
+ }
}
- if (gDirUtilp->fileExists(lod_filename))
+
+ if (ext_pos != std::string::npos)
{
- LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
- if (fmp)
+ // Replace extension with LOD suffix + original extension
+ std::string lod_file_to_check = lod_filename;
+ lod_file_to_check.replace(ext_pos, found_ext.size(), getLodSuffix(next_lod) + found_ext);
+
+ if (gDirUtilp->fileExists(lod_file_to_check))
+ {
+ LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
+ if (fmp)
+ {
+ fmp->setCtrlLoadFromFile(next_lod);
+ }
+ loadModel(lod_file_to_check, next_lod);
+ }
+ else
{
- fmp->setCtrlLoadFromFile(next_lod);
+ lookupLODModelFiles(next_lod);
}
- loadModel(lod_filename, next_lod);
}
else
{
+ // No recognized extension found, continue with next LOD
lookupLODModelFiles(next_lod);
}
}
diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp
index cee43f3cff..43836a420b 100644
--- a/indra/newview/llskinningutil.cpp
+++ b/indra/newview/llskinningutil.cpp
@@ -135,6 +135,12 @@ void LLSkinningUtil::initSkinningMatrixPalette(
initJointNums(const_cast<LLMeshSkinInfo*>(skin), avatar);
+ if (skin->mInvBindMatrix.size() < count )
+ {
+ // faulty model? mInvBindMatrix.size() should have matched mJointNames.size()
+ return;
+ }
+
LLMatrix4a world[LL_CHARACTER_MAX_ANIMATED_JOINTS];
for (S32 j = 0; j < count; ++j)
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 6b3dccf89c..d0bdcf132f 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -3783,7 +3783,12 @@ bool LLVOVolume::canBeAnimatedObject() const
bool LLVOVolume::isAnimatedObject() const
{
- LLVOVolume *root_vol = (LLVOVolume*)getRootEdit();
+ LLViewerObject *root_obj = getRootEdit();
+ if (root_obj->getPCode() != LL_PCODE_VOLUME)
+ {
+ return false; // at the moment only volumes can be animated
+ }
+ LLVOVolume* root_vol = (LLVOVolume*)root_obj;
mIsAnimatedObject = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG;
return mIsAnimatedObject;
}
diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml
index 90223fcda8..6231abf9a5 100644
--- a/indra/newview/skins/default/xui/en/floater_model_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml
@@ -45,6 +45,7 @@
<string name="UnrecognizedJoint">Rigged to unrecognized joint name [NAME]</string>
<string name="UnknownJoints">Skinning disabled due to [COUNT] unknown joints</string>
<string name="ModelLoaded">Model [MODEL_NAME] loaded</string>
+ <string name="InvBindCountMismatch">Bind matrices count mismatch joints count</string>
<string name="IncompleteTC">Texture coordinates data is not complete.</string>
<string name="PositionNaN">Found NaN while loading position data from DAE-Model, invalid model.</string>
@@ -60,6 +61,9 @@
<string name="ParsingErrorNoRoot">Document has no root</string>
<string name="ParsingErrorNoScene">Document has no visual_scene</string>
<string name="ParsingErrorPositionInvalidModel">Unable to process mesh without position data. Invalid model.</string>
+ <string name="InvalidGeometryNonTriangulated">Invalid geometry: GLTF files must contain triangulated meshes only.</string>
+ <string name="IgnoredExtension">Model uses unsupported extension: [EXT], related material properties are ignored.</string>
+ <string name="UnsupportedExtension">Unable to load a model, unsupported extension: [EXT]</string>
<panel
follows="top|left"
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 8a41405e4c..6bb9f7b1dc 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -9453,8 +9453,11 @@ Unable to upload texture: &apos;[NAME]&apos;
icon="alertmodal.tga"
name="CannotUploadMaterial"
type="alertmodal">
-There was a problem uploading the file
+Unable to upload material file. The file may be corrupted, in an unsupported format, or contain invalid data. Please check that you're using a valid GLTF/GLB file with proper material definitions.
<tag>fail</tag>
+ <usetemplate
+ name="okbutton"
+ yestext="OK"/>
</notification>
<notification