fix(gltf): correctly handle matrices for animation and cameras (#9273)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Matt
2025-12-17 15:23:45 -05:00
committed by GitHub
parent 2159fcb708
commit 057fe71d7a
3 changed files with 152 additions and 39 deletions
+74 -2
View File
@@ -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 <typename AssetType, typename Callback>
#if FASTGLTF_HAS_CONCEPTS
requires std::same_as<std::remove_cvref_t<AssetType>, 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);
}
}
}
@@ -82,6 +82,7 @@ std::vector<uint32_t> * 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;
}
}
+20 -13
View File
@@ -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);
}
}
});