diff options
Diffstat (limited to 'indra/newview/gltf/llgltfloader.cpp')
| -rw-r--r-- | indra/newview/gltf/llgltfloader.cpp | 238 |
1 files changed, 199 insertions, 39 deletions
diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 3a1d8079a9..1bdc915bd5 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 @@ -489,7 +507,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32> } else { - setLoadState(ERROR_MODEL + pModel->getStatus()); + setLoadState(static_cast<U32>(ERROR_MODEL) + static_cast<U32>(pModel->getStatus())); delete pModel; return; } @@ -684,7 +702,12 @@ std::string LLGLTFLoader::processTexture(std::string& full_path_out, S32 texture // 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 ""; @@ -734,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. @@ -803,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) { @@ -1332,6 +1398,24 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); } + // Precompute the source bind pose in viewer coordinate space so each + // joint can later be remapped against its own GLTF rest matrix. This + // keeps bind-pose deltas local to the joint hierarchy. Without this, + // a source skeleton whose parent joints carry bind-pose rotations that + // the viewer skeleton does not have can leak those rotations into + // child joints with different local bases. + joint_node_mat4_map_t rotated_bind_matrices; + joint_node_mat4_map_t converted_bind_matrices; + if (inverse_count > 0) + { + for (S32 i = 0; i < joint_count && i < inverse_count; i++) + { + S32 joint = skin.mJoints[i]; + glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); + rotated_bind_matrices[joint] = rotateGltfMatrixToViewerSpace(original_bind_matrix); + } + } + for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; @@ -1364,12 +1448,19 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } else if (inverse_count > i) { - // Transalte existing bind matrix to viewer's overriden skeleton - glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); - glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); - glm::mat4 tranlated_original = skeleton_transform * rotated_original; - glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); + // Translate existing bind matrices to the viewer skeleton by + // preserving each joint's bind-pose delta from its own GLTF rest + // matrix. The previous world-space remap applied one skeleton + // transform directly to each bind matrix; when a source parent has + // bind-pose rotation that the viewer parent does not, that rotation + // could get baked into child inverse binds. Keeping the delta local + // fixes the resulting child joint twists. + glm::mat4 translated_original = computeViewerBindMatrix( + joints_data, + rotated_bind_matrices, + joint, + converted_bind_matrices); + glm::mat4 final_inverse_bind_matrix = glm::inverse(translated_original); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; @@ -1485,6 +1576,12 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map glm::quat rotation; glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + glm::mat4 viewer_rotation_scale(1.0f); + viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[0]), glm::vec3(1, 0, 0)); + viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[1]), glm::vec3(0, 1, 0)); + viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[2]), glm::vec3(0, 0, 1)); + viewer_rotation_scale = glm::scale(viewer_rotation_scale, viewer_data.mScale); + // Viewer allows overrides, which are base joint with applied translation override. // fortunately normal bones use only translation, without rotation or scale node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity<glm::quat>(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); @@ -1500,13 +1597,14 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map } else { - // This is likely incomplete or even wrong. - // Viewer Collision bones specify rotation and scale. - // Importer should apply rotation and scale to this matrix and save as needed - // then subsctruct them from bind matrix - // Todo: get models that use collision bones, made by different programs - - overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); + // Collision volumes need the imported translation override, but + // their local rotation-scale basis must come from the raw viewer + // skeleton XML values. For non-uniform torso volumes, matching DAE + // requires viewer rotation followed by viewer scale. + overriden_joint = viewer_rotation_scale; + overriden_joint[3][0] = translation_override.x; + overriden_joint[3][1] = translation_override.y; + overriden_joint[3][2] = translation_override.z; node.mOverrideRestMatrix = parent_support_rest * overriden_joint; } } @@ -1585,6 +1683,69 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_d return data.mGltfMatrix; } +// Convert a GLTF-space transform into the viewer coordinate space used by +// skeleton override and bind-pose calculations. Keeping this conversion in +// one helper ensures bind and rest matrices use the same rotation path, +// including the optional XY rotation applied for compatible uploads. +glm::mat4 LLGLTFLoader::rotateGltfMatrixToViewerSpace(const glm::mat4& gltf_matrix) const +{ + glm::mat4 rotated = coord_system_rotation * gltf_matrix; + if (mApplyXYRotation) + { + rotated = coord_system_rotationxy * rotated; + } + return rotated; +} + +// Convert one GLTF bind matrix to the matching viewer bind matrix. The +// function derives the joint-local bind delta from the GLTF bind and rest +// matrices, then applies that same delta to the viewer override rest matrix. +// Results are cached because child calculations can request the same joint +// more than once while preserving local hierarchy behavior. +glm::mat4 LLGLTFLoader::computeViewerBindMatrix( + const joints_data_map_t& joints_data_map, + const joint_node_mat4_map_t& rotated_bind_matrices, + S32 gltf_node_index, + joint_node_mat4_map_t& converted_bind_matrices) const +{ + auto cached = converted_bind_matrices.find(gltf_node_index); + if (cached != converted_bind_matrices.end()) + { + return cached->second; + } + + auto node_iter = joints_data_map.find(gltf_node_index); + if (node_iter == joints_data_map.end()) + { + return glm::mat4(1.f); + } + + const JointNodeData& node_data = node_iter->second; + if (!node_data.mIsOverrideValid) + { + // No valid override, falling back to rest matrix. + glm::mat4 fallback_bind = rotateGltfMatrixToViewerSpace(node_data.mGltfRestMatrix); + converted_bind_matrices[gltf_node_index] = fallback_bind; + return fallback_bind; + } + + auto bind_iter = rotated_bind_matrices.find(gltf_node_index); + if (bind_iter == rotated_bind_matrices.end()) + { + converted_bind_matrices[gltf_node_index] = node_data.mOverrideRestMatrix; + return node_data.mOverrideRestMatrix; + } + + glm::mat4 gltf_joint_node = rotateGltfMatrixToViewerSpace(node_data.mGltfRestMatrix); + glm::mat4 gltf_joint_bind = bind_iter->second; + glm::mat4 viewer_joint_node = node_data.mOverrideRestMatrix; + glm::mat4 bind_delta = gltf_joint_bind * glm::inverse(gltf_joint_node); + glm::mat4 viewer_joint_bind = bind_delta * viewer_joint_node; + + converted_bind_matrices[gltf_node_index] = viewer_joint_bind; + return viewer_joint_bind; +} + // This function computes the transformation matrix needed to convert from GLTF skeleton space // to viewer skeleton space for a specific joint @@ -1769,7 +1930,7 @@ std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std:: "gltf_embedded_" + texture_type + "_" + std::to_string(sourceIndex) + extension; // Write the image data to the temporary file - std::ofstream temp_file(temp_filename, std::ios::binary); + llofstream temp_file(temp_filename, std::ios::binary); if (temp_file.is_open()) { temp_file.write(reinterpret_cast<const char*>(data_ptr), data_size); @@ -1837,4 +1998,3 @@ std::string LLGLTFLoader::getLodlessLabel(const LL::GLTF::Node& node) } return node.mName; } - |
