summaryrefslogtreecommitdiff
path: root/indra/newview/gltf
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/gltf')
-rw-r--r--indra/newview/gltf/asset.cpp41
-rw-r--r--indra/newview/gltf/asset.h2
-rw-r--r--indra/newview/gltf/buffer_util.h44
-rw-r--r--indra/newview/gltf/llgltfloader.cpp125
-rw-r--r--indra/newview/gltf/llgltfloader.h2
5 files changed, 185 insertions, 29 deletions
diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp
index f7a5a20872..d44aa9d72c 100644
--- a/indra/newview/gltf/asset.cpp
+++ b/indra/newview/gltf/asset.cpp
@@ -33,10 +33,11 @@
#include "../llviewertexturelist.h"
#include "../pipeline.h"
#include "buffer_util.h"
-#include <boost/url.hpp>
#include "llimagejpeg.h"
#include "../llskinningutil.h"
+#include <future>
+
using namespace LL::GLTF;
using namespace boost::json;
@@ -961,9 +962,41 @@ LLViewerFetchedTexture* fetch_texture(const LLUUID& id);
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
+ LL_DEBUGS("GLTF") << "Loading image from an id" << id<< LL_ENDL;
+ }
+ else if (mUri.find("data:") == 0)
+ { // embedded in a data URI, load the texture from the URI
+ LL_WARNS("GLTF") << "Data URIs not yet supported" << LL_ENDL;
+ return false;
+ }
+
+ // Image::prepImpl containes code that must run on the main thread
+ std::promise<bool> prep_promise;
+ std::future<bool> prep_future = prep_promise.get_future();
+
+ LLAppViewer::instance()->postToMainCoro([this, &asset, id, &prep_promise]() mutable {
+ try {
+ bool result = prepImpl(asset, id);
+ prep_promise.set_value(result);
+ }
+ catch (...) {
+ // Propagate exception to the waiting thread
+ prep_promise.set_exception(std::current_exception());
+ }
+ });
+
+ // Block until prep is done on the main thread
+ return prep_future.get();
+}
+
+bool Image::prepImpl(Asset& asset, const LLUUID& id)
+{
+ if (id.notNull())
+ { // loaded from an asset, fetch the texture from the asset system
mTexture = fetch_texture(id);
}
else if (mUri.find("data:") == 0)
@@ -994,6 +1027,12 @@ bool Image::prep(Asset& asset, bool loadIntoVRAM)
std::string dir = gDirUtilp->getDirName(asset.mFilename);
std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri;
+ if (!gDirUtilp->fileExists(img_file))
+ {
+ // URI might be escaped, unescape.
+ img_file = dir + gDirUtilp->getDirDelimiter() + LLURI::unescape(mUri);
+ }
+
LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file);
if (tracking_id.notNull() && mLoadIntoTexturePipe)
{
diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h
index b9554d753c..2802664ed3 100644
--- a/indra/newview/gltf/asset.h
+++ b/indra/newview/gltf/asset.h
@@ -320,6 +320,8 @@ namespace LL
void clearData(Asset& asset);
bool prep(Asset& asset, bool loadIntoVRAM);
+ private:
+ bool prepImpl(Asset& asset, const LLUUID& id);
};
// Render Batch -- vertex buffer and list of primitives to render using
diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h
index c231443a9e..53dee98cd1 100644
--- a/indra/newview/gltf/buffer_util.h
+++ b/indra/newview/gltf/buffer_util.h
@@ -141,6 +141,42 @@ namespace LL
}
template<>
+ inline void copyScalar<U32, LLVector4a>(U32* src, LLVector4a& dst)
+ {
+ dst.set((F32)*src, 0.f, 0.f, 0.f);
+ }
+
+ template<>
+ inline void copyScalar<U16, LLVector4a>(U16* src, LLVector4a& dst)
+ {
+ dst.set((F32)*src, 0.f, 0.f, 0.f);
+ }
+
+ template<>
+ inline void copyScalar<U8, LLVector4a>(U8* src, LLVector4a& dst)
+ {
+ dst.set((F32)*src, 0.f, 0.f, 0.f);
+ }
+
+ template<>
+ inline void copyScalar<U32, LLVector2>(U32* src, LLVector2& dst)
+ {
+ dst.set((F32)*src, 0.f);
+ }
+
+ template<>
+ inline void copyScalar<U16, LLVector2>(U16* src, LLVector2& dst)
+ {
+ dst.set((F32)*src, 0.f);
+ }
+
+ template<>
+ inline void copyScalar<U8, LLVector2>(U8* src, LLVector2& dst)
+ {
+ dst.set((F32)*src, 0.f);
+ }
+
+ template<>
inline void copyVec2<F32, LLVector2>(F32* src, LLVector2& dst)
{
dst.set(src[0], src[1]);
@@ -221,6 +257,12 @@ namespace LL
}
template<>
+ inline void copyVec4<U32, LLVector4a>(U32* src, LLVector4a& dst)
+ {
+ dst.set((F32)src[0], (F32)src[1], (F32)src[2], (F32)src[3]);
+ }
+
+ template<>
inline void copyVec4<U16, LLVector4a>(U16* src, LLVector4a& dst)
{
dst.set(src[0], src[1], src[2], src[3]);
@@ -373,7 +415,7 @@ namespace LL
}
else
{
- LL_ERRS("GLTF") << "Unsupported accessor type" << LL_ENDL;
+ LL_ERRS("GLTF") << "Unsupported accessor type " << (S32)accessor.mType << LL_ENDL;
}
}
diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp
index 4f8f80129d..5a94a2c6c6 100644
--- a/indra/newview/gltf/llgltfloader.cpp
+++ b/indra/newview/gltf/llgltfloader.cpp
@@ -440,7 +440,25 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32>
(LLModel::NO_ERRORS == pModel->getStatus()) &&
validate_model(pModel))
{
- mTransform.setIdentity();
+ // Build the scene transform.
+ // Non-skinned meshes: scene transform carries coord rotation + hierarchy,
+ // preserving the object's rotation/position/scale for upload.
+ // Skinned meshes: transform is already baked into vertices, so scene is identity.
+ if (node.mSkin >= 0)
+ {
+ mTransform.setIdentity();
+ }
+ else
+ {
+ glm::mat4 hierarchy_transform;
+ computeCombinedNodeTransform(mGLTFAsset, node_idx, hierarchy_transform);
+ glm::mat4 combined = coord_system_rotation * hierarchy_transform;
+ if (mApplyXYRotation)
+ {
+ combined = coord_system_rotationxy * combined;
+ }
+ mTransform = LLMatrix4(glm::value_ptr(combined));
+ }
transformation = mTransform;
// adjust the transformation to compensate for mesh normalization
@@ -602,11 +620,12 @@ LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_in
if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0)
{
S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex;
- std::string filename = processTexture(texIndex, "base_color", material->mName);
+ std::string full_path;
+ std::string filename = processTexture(full_path, texIndex, "base_color", material->mName);
if (!filename.empty())
{
- impMat.mDiffuseMapFilename = filename;
+ impMat.mDiffuseMapFilename = full_path;
impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName;
// Check if the texture is already loaded
@@ -637,7 +656,7 @@ LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_in
return cachedMat;
}
-std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name)
+std::string LLGLTFLoader::processTexture(std::string& full_path_out, S32 texture_index, const std::string& texture_type, const std::string& material_name)
{
S32 sourceIndex;
if (!validateTextureIndex(texture_index, sourceIndex))
@@ -661,6 +680,12 @@ std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& t
{
// Uri might be escaped
filename = LLURI::unescape(filename);
+ full_path = dir + gDirUtilp->getDirDelimiter() + filename;
+ }
+
+ if (gDirUtilp->fileExists(full_path))
+ {
+ full_path_out = full_path;
}
LL_INFOS("GLTF_IMPORT") << "Found texture: " << filename << " for material: " << material_name << LL_ENDL;
@@ -677,7 +702,12 @@ std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& t
// Process embedded textures
if (image.mBufferView >= 0)
{
- return extractTextureToTempFile(texture_index, texture_type);
+ std::string temp_path = extractTextureToTempFile(texture_index, texture_type);
+ if (!temp_path.empty())
+ {
+ full_path_out = temp_path;
+ }
+ return temp_path;
}
return "";
@@ -727,23 +757,47 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas
S32 skinIdx = nodeno.mSkin;
- // Compute final combined transform matrix (hierarchy + coordinate rotation)
+ // Compute the vertex transform for this mesh.
+ // Non-skinned meshes: vertices are left untransformed; the node's hierarchy transform
+ // (rotation, translation, scale) is stored in the scene transform instead, matching
+ // the DAE loader. This ensures the uploaded object's bounding box and transform
+ // properties are correct. See: https://github.com/secondlife/viewer/issues/5431
+ // Skinned meshes: coord rotation + hierarchy are baked into vertex positions because
+ // inverse bind matrices and skin weights are already computed in that space.
+ // TODO: consider aligning skinned meshes with the DAE loader (scene transform instead
+ // of vertex baking), which would require adjusting inverse bind matrices, bind shape
+ // matrix, and weight keying to match.
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
- glm::mat4 final_transform = coord_system_rotation * hierarchy_transform;
- if (mApplyXYRotation)
+ glm::mat4 vertex_transform;
+ if (skinIdx >= 0)
+ {
+ // Skinned mesh: bake coord rotation + hierarchy into vertices.
+ // Inverse bind matrices and skin weights depend on this transform being applied.
+ vertex_transform = coord_system_rotation * hierarchy_transform;
+ if (mApplyXYRotation)
+ {
+ vertex_transform = coord_system_rotationxy * vertex_transform;
+ }
+ }
+ else
{
- final_transform = coord_system_rotationxy * final_transform;
+ // Non-skinned mesh: don't apply any transform to vertices.
+ // The hierarchy transform will be stored in the scene transform matrix.
+ vertex_transform = glm::mat4(1.0f); // identity
}
// Check if we have a negative scale (flipped coordinate system)
- bool hasNegativeScale = glm::determinant(final_transform) < 0.0f;
+ // coord_system_rotation and coord_system_rotationxy are pure rotations (det=1),
+ // so negative scale depends only on the hierarchy transform.
+ bool hasNegativeScale = glm::determinant(hierarchy_transform) < 0.0f;
+
+ bool hasVertexTransform = (vertex_transform != glm::mat4(1.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)));
+ const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(vertex_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.
@@ -796,30 +850,49 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& bas
return false; // Skip this primitive
}
- // Apply the global scale and center offset to all vertices
+ // Apply vertex transform (if any) to all vertices.
+ // Skinned meshes: this bakes coord rotation + hierarchy into vertices.
+ // Non-skinned meshes: vertex_transform is identity (no baking).
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())
+ if (hasVertexTransform)
{
- // 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);
+ glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f);
+ glm::vec4 transformed_pos = vertex_transform * pos;
+ vert.position = glm::vec3(transformed_pos);
+
+ if (!prim.mNormals.empty())
+ {
+ 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
+ {
+ 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;
+ }
}
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;
+ // No transform: store raw GLTF positions and normals.
+ // The scene transform will carry coord rotation + hierarchy.
+ vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]);
+
+ if (!prim.mNormals.empty())
+ {
+ vert.normal = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]);
+ }
+ else
+ {
+ vert.normal = 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]);
+ // Flip texture V coordinate
+ vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], 1.f - prim.mTexCoords0[i][1]);
if (skinIdx >= 0)
{
diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h
index 7aa1a94c20..a847e567a6 100644
--- a/indra/newview/gltf/llgltfloader.h
+++ b/indra/newview/gltf/llgltfloader.h
@@ -149,7 +149,7 @@ private:
void processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params);
bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx);
LLGLTFImportMaterial processMaterial(S32 material_index, S32 fallback_index);
- std::string processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name);
+ std::string processTexture(std::string& full_path_out, S32 texture_index, const std::string& texture_type, const std::string& material_name);
bool validateTextureIndex(S32 texture_index, S32& source_index);
std::string generateMaterialName(S32 material_index, S32 fallback_index = -1);
bool populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats);