diff --git a/src/libs/gltf/fastgltf/lv_fastgltf.hpp b/src/libs/gltf/fastgltf/lv_fastgltf.hpp index c108331944..649db9d020 100644 --- a/src/libs/gltf/fastgltf/lv_fastgltf.hpp +++ b/src/libs/gltf/fastgltf/lv_fastgltf.hpp @@ -22,6 +22,77 @@ namespace fastgltf { +/** + * Computes the transform matrix for a given node + */ +FASTGLTF_EXPORT inline auto getLocalTransformMatrix(const Node& node) { + return visit_exhaustive(visitor { + [&](const math::fmat4x4& matrix) { + return matrix; + }, + [&](const TRS& trs) { + /* This may appear backwards, like it is applying the scale last, but it should + * be first. However, these operations alter the matrix *in place*, they are *not* + * matrix multiplications and they should *not* be applied in the standard + * post-multiplicative order, so this is correct as shown. This method is used for + * performance reasons, since it uses less multiplications and gets the same results. + * This function is being added to fastgltf and will be removed from this file soon. + */ + return scale(rotate(translate(math::fmat4x4(), trs.translation), trs.rotation), trs.scale); + } + }, node.transform); +} + +/** + * Computes the transform matrix for a given node a different way with less total operations + */ +FASTGLTF_EXPORT inline auto getFastLocalTransformMatrix(const Node& node) { + return visit_exhaustive(visitor { + [&](const math::fmat4x4& matrix) { + return matrix; + }, + [&](const TRS& trs) { + math::fmat4x4 matrix = math::fmat4x4(); + float sx = trs.scale[0], sy = trs.scale[1], sz = trs.scale[2]; + float qx = trs.rotation[0], qy = trs.rotation[1], qz = trs.rotation[2], qw = trs.rotation[3]; + float x2 = qx + qx, y2 = qy + qy, z2 = qz + qz; + float xx = qx * x2, xy = qx * y2, xz = qx * z2; + float yy = qy * y2, yz = qy * z2, zz = qz * z2; + float wx = qw * x2, wy = qw * y2, wz = qw * z2; + matrix[0][0] = (1 - (yy + zz)) * sx; + matrix[0][1] = (xy + wz) * sx; + matrix[0][2] = (xz - wy) * sx; + matrix[1][0] = (xy - wz) * sy; + matrix[1][1] = (1 - (xx + zz)) * sy; + matrix[1][2] = (yz + wx) * sy; + matrix[2][0] = (xz + wy) * sz; + matrix[2][1] = (yz - wx) * sz; + matrix[2][2] = (1 - (xx + yy)) * sz; + matrix[3][0] = trs.translation[0]; + matrix[3][1] = trs.translation[1]; + matrix[3][2] = trs.translation[2]; + matrix[0][3] = 0.f; + matrix[1][3] = 0.f; + matrix[2][3] = 0.f; + matrix[3][3] = 1.f; + return matrix; + } + }, node.transform); +} + +/** + * Attempts to remove the scale component of a 4x4 matrix transform. Will silently fail if + * any of the component scales is 0 or near zero (which they should never be). + */ +FASTGLTF_EXPORT inline void removeScale(fastgltf::math::fmat4x4& matrix) { + auto scale = math::fvec3( length(matrix.col(0)), length(matrix.col(1)), length(matrix.col(2)) ); + if ( ( fabs(scale.x()) > 0.00001f) && (fabs(scale.y()) > 0.00001f) && (fabs(scale.z()) > 0.00001f) ) { + matrix.col(0) /= scale.x(); + matrix.col(1) /= scale.y(); + matrix.col(2) /= scale.z(); + } +} + FASTGLTF_EXPORT template #if FASTGLTF_HAS_CONCEPTS requires std::same_as, Asset> && @@ -78,7 +149,7 @@ findlight_iterate_scene_nodes(AssetType &&asset, std::size_t sceneIndex, auto & self) -> void { assert(asset.nodes.size() > nodeIndex); auto & node = asset.nodes[nodeIndex]; - auto _localMat = getTransformMatrix(node, math::fmat4x4()); + auto _localMat = getFastLocalTransformMatrix(node); std::invoke(callback, node, parentWorldMatrix, _localMat); for(auto & child : node.children) { math::fmat4x4 _parentWorldTemp = @@ -103,7 +174,7 @@ inline void custom_iterate_scene_nodes(lv_gltf_model_t * model, std::size_t scen fastgltf::Node & fastgltf_node = *node->fastgltf_node; LV_ASSERT(node->fastgltf_node == &fastgltf_node); - auto local_matrix = getTransformMatrix(fastgltf_node, math::fmat4x4()); + auto local_matrix = getFastLocalTransformMatrix(fastgltf_node); std::invoke(callback, node, parent_world_matrix, local_matrix); uint32_t num_children = fastgltf_node.children.size(); @@ -127,6 +198,7 @@ inline void custom_iterate_scene_nodes(lv_gltf_model_t * model, std::size_t scen invoke_cb(scene.nodeIndices[i], *initial, invoke_cb); } } + } diff --git a/src/libs/gltf/gltf_data/lv_gltf_data_animations.cpp b/src/libs/gltf/gltf_data/lv_gltf_data_animations.cpp index 64d2872443..c0d45acb7e 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_data_animations.cpp +++ b/src/libs/gltf/gltf_data/lv_gltf_data_animations.cpp @@ -82,6 +82,7 @@ std::vector * lv_gltf_data_animation_get_channel_set(std::size_t anim_ return &data->channel_set_cache[node]; } + void lv_gltf_data_animation_matrix_apply(float timestamp, std::size_t anim_num, lv_gltf_model_t * gltf_data, fastgltf::Node * node, fastgltf::math::fmat4x4 & matrix) @@ -95,43 +96,76 @@ void lv_gltf_data_animation_matrix_apply(float timestamp, std::size_t anim_num, } if(animation_count > anim_num) { auto & anim = asset->animations[anim_num]; - fastgltf::math::fvec3 newPos, newScale; - fastgltf::math::fmat3x3 rotmat; + bool need_rot_recalc = false; + int32_t translation_comp_index = -1; + int32_t rotation_comp_index = -1; + int32_t scale_comp_index = -1; + for(const auto & c : (*_channel_set)) { switch(anim.channels[c].path) { case fastgltf::AnimationPath::Translation: - newPos = animation_get_vec3_at_timestamp(gltf_data, &anim.samplers[c], timestamp); - matrix[3][0] = newPos[0]; - matrix[3][1] = newPos[1]; - matrix[3][2] = newPos[2]; + translation_comp_index = c; break; case fastgltf::AnimationPath::Rotation: - rotmat = fastgltf::math::asMatrix(animation_get_quat_at_timestamp(gltf_data, &anim.samplers[c], timestamp)); - matrix[0][0] = rotmat[0][0]; - matrix[0][1] = rotmat[0][1]; - matrix[0][2] = rotmat[0][2]; - - matrix[1][0] = rotmat[1][0]; - matrix[1][1] = rotmat[1][1]; - matrix[1][2] = rotmat[1][2]; - - matrix[2][0] = rotmat[2][0]; - matrix[2][1] = rotmat[2][1]; - matrix[2][2] = rotmat[2][2]; + rotation_comp_index = c; + need_rot_recalc = true; break; case fastgltf::AnimationPath::Scale: - newScale = animation_get_vec3_at_timestamp(gltf_data, &anim.samplers[c], timestamp); - for(int32_t rs = 0; rs < 3; ++rs) { - matrix[0][rs] *= newScale[0]; - matrix[1][rs] *= newScale[1]; - matrix[2][rs] *= newScale[2]; - } + scale_comp_index = c; + need_rot_recalc = true; break; case fastgltf::AnimationPath::Weights: LV_LOG_WARN("Unhandled weights animation"); break; } } + + if(need_rot_recalc) { + fastgltf::math::fvec3 new_scale; + fastgltf::math::fquat new_quat; + if(!((scale_comp_index > -1) && (rotation_comp_index > -1))) { + fastgltf::math::fvec3 unused; + fastgltf::math::decomposeTransformMatrix(matrix, new_scale, new_quat, unused); + } + if(scale_comp_index > -1) new_scale = animation_get_vec3_at_timestamp(gltf_data, &anim.samplers[scale_comp_index], + timestamp); + if(rotation_comp_index > -1) new_quat = animation_get_quat_at_timestamp(gltf_data, &anim.samplers[rotation_comp_index], + timestamp); + + float sx = new_scale[0], sy = new_scale[1], sz = new_scale[2]; + float qx = new_quat[0], qy = new_quat[1], qz = new_quat[2], qw = new_quat[3]; + + float x2 = qx + qx, y2 = qy + qy, z2 = qz + qz; + float xx = qx * x2, xy = qx * y2, xz = qx * z2; + float yy = qy * y2, yz = qy * z2, zz = qz * z2; + float wx = qw * x2, wy = qw * y2, wz = qw * z2; + + matrix[0][0] = (1 - (yy + zz)) * sx; + matrix[0][1] = (xy + wz) * sx; + matrix[0][2] = (xz - wy) * sx; + + matrix[1][0] = (xy - wz) * sy; + matrix[1][1] = (1 - (xx + zz)) * sy; + matrix[1][2] = (yz + wx) * sy; + + matrix[2][0] = (xz + wy) * sz; + matrix[2][1] = (yz - wx) * sz; + matrix[2][2] = (1 - (xx + yy)) * sz; + + /* These entries should not be necessary */ + //matrix[0][3] = 0.f; + //matrix[1][3] = 0.f; + //matrix[2][3] = 0.f; + } + + if(translation_comp_index > -1) { + fastgltf::math::fvec3 new_translation = animation_get_vec3_at_timestamp(gltf_data, + &anim.samplers[translation_comp_index], timestamp); + matrix[3][0] = new_translation[0]; + matrix[3][1] = new_translation[1]; + matrix[3][2] = new_translation[2]; + } + matrix[3][3] = 1.f; } } diff --git a/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp b/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp index 062818460c..ba3e6e226a 100644 --- a/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp +++ b/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp @@ -275,7 +275,7 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo } std::sort(distance_sort_nodes.begin(), distance_sort_nodes.end(), [](const NodeIndexDistancePair & a, const NodeIndexDistancePair & b) { - return a.first < b.first; + return a.first > b.first; }); /* Reset the last material index to an unused value once per frame at the start*/ @@ -1240,6 +1240,8 @@ static void lv_gltf_view_recache_all_transforms(lv_gltf_model_t * model) localmatrix); made_changes = true; } + bool worldmatrix_was_inlined = false; + fastgltf::math::fmat4x4 inlined_worldmatrix; const uint32_t write_ops_count = lv_array_size(&node->write_ops); if(node->read_attrs || write_ops_count > 0) { fastgltf::math::fvec3 local_pos; @@ -1266,13 +1268,17 @@ static void lv_gltf_view_recache_all_transforms(lv_gltf_model_t * model) } /* Rebuild the local matrix after applying all write operations*/ - localmatrix = fastgltf::math::scale( - fastgltf::math::rotate(fastgltf::math::translate(fastgltf::math::fmat4x4(), local_pos), - made_rotation_changes ? - lv_gltf_math_euler_to_quaternion( - local_rot[0], local_rot[1], local_rot[2]) : - local_quat), - local_scale); + if(made_rotation_changes) local_quat = lv_gltf_math_euler_to_quaternion(local_rot[0], local_rot[1], local_rot[2]); + + if(node->fastgltf_node->children.size() == 0) { + worldmatrix_was_inlined = true; + inlined_worldmatrix = fastgltf::math::scale(fastgltf::math::rotate(fastgltf::math::translate(fastgltf::math::fmat4x4( + parentworldmatrix), local_pos), local_quat), local_scale); + } + else { + localmatrix = fastgltf::math::scale(fastgltf::math::rotate(fastgltf::math::translate(fastgltf::math::fmat4x4(), + local_pos), local_quat), local_scale); + } if(node->read_attrs) { bool value_changed = false; @@ -1309,17 +1315,18 @@ static void lv_gltf_view_recache_all_transforms(lv_gltf_model_t * model) } if(made_changes || !lv_gltf_data_has_cached_transform(model, node->fastgltf_node)) { - lv_gltf_data_set_cached_transform(model, node->fastgltf_node, parentworldmatrix * localmatrix); + auto world_matrix = worldmatrix_was_inlined ? inlined_worldmatrix : (parentworldmatrix * localmatrix); + lv_gltf_data_set_cached_transform(model, node->fastgltf_node, world_matrix); } if(node->fastgltf_node->cameraIndex.has_value()) { current_camera_count++; if(current_camera_count == model->camera) { - fastgltf::math::fmat4x4 cammat = (parentworldmatrix * localmatrix); - model->view_pos[0] = cammat[3][0]; - model->view_pos[1] = cammat[3][1]; - model->view_pos[2] = cammat[3][2]; + fastgltf::math::fmat4x4 cammat = worldmatrix_was_inlined ? inlined_worldmatrix : (parentworldmatrix * localmatrix); + fastgltf::removeScale(cammat); + model->view_pos = cammat.col(3); /* Implicit conversion from 4 element column to 3 element vector */ model->view_mat = fastgltf::math::invert(cammat); + } } });