diff options
Diffstat (limited to 'indra/newview/gltf')
| -rw-r--r-- | indra/newview/gltf/asset.cpp | 41 | ||||
| -rw-r--r-- | indra/newview/gltf/asset.h | 2 | ||||
| -rw-r--r-- | indra/newview/gltf/buffer_util.h | 44 | ||||
| -rw-r--r-- | indra/newview/gltf/llgltfloader.cpp | 125 | ||||
| -rw-r--r-- | indra/newview/gltf/llgltfloader.h | 2 |
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); |
