mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-24 16:37:18 +08:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user