From 063e8e43ba8253fede32b8745d1406957b3eb501 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sun, 26 Mar 2023 23:51:51 +0300 Subject: [PATCH] glTF animations --- .../gfx/include/psemek/gfx/gltf_animation.hpp | 169 ++++++++++++++++++ libs/gfx/include/psemek/gfx/gltf_parser.hpp | 14 +- libs/gfx/source/gltf_parser.cpp | 12 ++ 3 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 libs/gfx/include/psemek/gfx/gltf_animation.hpp diff --git a/libs/gfx/include/psemek/gfx/gltf_animation.hpp b/libs/gfx/include/psemek/gfx/gltf_animation.hpp new file mode 100644 index 00000000..22514385 --- /dev/null +++ b/libs/gfx/include/psemek/gfx/gltf_animation.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace psemek::gfx +{ + + namespace detail + { + + template + struct gltf_animation_traits; + + template <> + struct gltf_animation_traits + { + using output_type = geom::vector; + + static output_type default_value() + { + return {1.f, 1.f, 1.f}; + } + + static output_type lerp(output_type const & v1, output_type const & v2, float t) + { + return geom::lerp(v1, v2, t); + } + }; + + template <> + struct gltf_animation_traits + { + using output_type = geom::quaternion; + + static output_type default_value() + { + return output_type::identity(); + } + + static output_type lerp(output_type const & v1, output_type const & v2, float t) + { + return geom::slerp(v1, v2, t); + } + }; + + template <> + struct gltf_animation_traits + { + using output_type = geom::vector; + + static output_type default_value() + { + return {0.f, 0.f, 0.f}; + } + + static output_type lerp(output_type const & v1, output_type const & v2, float t) + { + return geom::lerp(v1, v2, t); + } + }; + + } + + template + struct gltf_animation_channel + { + using traits = detail::gltf_animation_traits; + using output_type = typename traits::output_type; + + gltf_animation_channel() + : input_{0.f} + , output_{traits::default_value()} + , interpolation_{geom::easing_type::constant_left} + {} + + gltf_animation_channel(std::vector input, std::vector output, geom::easing_type interpolation) + : input_(std::move(input)) + , output_(std::move(output)) + , interpolation_(interpolation) + {} + + gltf_animation_channel(gltf_animation_channel &&) = default; + + gltf_animation_channel & operator = (gltf_animation_channel &&) = default; + + geom::interval range() const + { + return {input_.front(), input_.back()}; + } + + output_type operator()(float time) const + { + auto it = std::lower_bound(input_.begin(), input_.end(), time); + if (it == input_.begin()) + return output_.back(); + if (it == input_.end()) + return output_.back(); + + auto i = it - input_.begin(); + + float t = (time - input_[i - 1]) / (input_[i] - input_[i - 1]); + + switch (interpolation_) + { + case geom::easing_type::constant_left: + return output_[i - 1]; + case geom::easing_type::linear: + return traits::lerp(output_[i - 1], output_[i], t); + case geom::easing_type::cubic: + { + // see https://github.khronos.org/glTF-Tutorials/gltfTutorial/gltfTutorial_007_Animations.html#cubic-spline-interpolation + float t2 = t * t; + float t3 = t * t2; + return traits::default_value() + + output_[3 * (i - 1) + 1] * ( 2.f * t3 - 3.f * t2 + 1.f) + + output_[3 * (i - 1) + 2] * ( t3 - 2.f * t2 + t ) + + output_[3 * (i + 0) + 1] * (- 2.f * t3 + 3.f * t2 ) + + output_[3 * (i + 0) + 0] * ( t3 - t2 ) + ; + } + default: + throw std::runtime_error("unsupported glTF animation interpolation type"); + } + } + + private: + std::vector input_; + std::vector output_; + geom::easing_type interpolation_; + }; + + using gltf_scale_animation = gltf_animation_channel; + using gltf_rotation_animation = gltf_animation_channel; + using gltf_translation_animation = gltf_animation_channel; + + struct gltf_animation + { + gltf_animation() = default; + + gltf_animation(gltf_scale_animation scale, gltf_rotation_animation rotation, gltf_translation_animation translation) + : scale_(std::move(scale)) + , rotation_(std::move(rotation)) + , translation_(std::move(translation)) + {} + + geom::interval range() const + { + return scale_.range() | rotation_.range() | translation_.range(); + } + + geom::affine_transform operator()(float time) const + { + return geom::translation(translation_(time)).transform() + * geom::quaternion_rotation(rotation_(time)).transform() + * geom::scale(scale_(time)).transform(); + } + + private: + gltf_scale_animation scale_; + gltf_rotation_animation rotation_; + gltf_translation_animation translation_; + }; + +} diff --git a/libs/gfx/include/psemek/gfx/gltf_parser.hpp b/libs/gfx/include/psemek/gfx/gltf_parser.hpp index 216a42fd..64d13c22 100644 --- a/libs/gfx/include/psemek/gfx/gltf_parser.hpp +++ b/libs/gfx/include/psemek/gfx/gltf_parser.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,7 @@ namespace psemek::gfx geom::vector translation; geom::quaternion rotation; geom::vector scale; + geom::affine_transform transform; }; struct mesh @@ -156,8 +158,16 @@ namespace psemek::gfx std::vector buffers; std::vector lights; // KHR_lights_punctual - std::unordered_map node_index; - std::unordered_map material_index; + std::unordered_map node_index; // node by name + std::unordered_map material_index; // material by name + + struct animation_and_channel + { + std::size_t animation; + std::size_t channel; + }; + + std::vector> node_animations; // node to list of animation channels that affect it }; gltf_asset parse_gltf(io::istream && stream); diff --git a/libs/gfx/source/gltf_parser.cpp b/libs/gfx/source/gltf_parser.cpp index 09c845a3..553155d1 100644 --- a/libs/gfx/source/gltf_parser.cpp +++ b/libs/gfx/source/gltf_parser.cpp @@ -1,4 +1,7 @@ #include +#include +#include +#include #include #include @@ -122,6 +125,10 @@ namespace psemek::gfx target.scale[i] = scale[i].GetFloat(); } + target.transform = geom::translation(target.translation).transform() + * geom::quaternion_rotation(target.rotation).transform() + * geom::scale(target.scale).transform(); + if (node.HasMember("children")) { for (auto const & child : node["children"].GetArray()) @@ -357,6 +364,11 @@ namespace psemek::gfx for (std::size_t i = 0; i < result.materials.size(); ++i) result.material_index[result.materials[i].name] = i; + result.node_animations.resize(result.nodes.size()); + for (std::size_t i = 0; i < result.animations.size(); ++i) + for (std::size_t j = 0; j < result.animations[i].channels.size(); ++j) + result.node_animations[result.animations[i].channels[j].target].push_back({i, j}); + return result; }