glTF animations

This commit is contained in:
Nikita Lisitsa 2023-03-26 23:51:51 +03:00
parent c26f626baf
commit 063e8e43ba
3 changed files with 193 additions and 2 deletions

View file

@ -0,0 +1,169 @@
#pragma once
#include <psemek/gfx/gltf_parser.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/translation.hpp>
#include <algorithm>
namespace psemek::gfx
{
namespace detail
{
template <gltf_asset::animation::channel::path_t path>
struct gltf_animation_traits;
template <>
struct gltf_animation_traits<gltf_asset::animation::channel::scale>
{
using output_type = geom::vector<float, 3>;
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<gltf_asset::animation::channel::rotation>
{
using output_type = geom::quaternion<float>;
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<gltf_asset::animation::channel::translation>
{
using output_type = geom::vector<float, 3>;
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 <gltf_asset::animation::channel::path_t path>
struct gltf_animation_channel
{
using traits = detail::gltf_animation_traits<path>;
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<float> input, std::vector<output_type> 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<float> 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<float> input_;
std::vector<output_type> output_;
geom::easing_type interpolation_;
};
using gltf_scale_animation = gltf_animation_channel<gltf_asset::animation::channel::scale>;
using gltf_rotation_animation = gltf_animation_channel<gltf_asset::animation::channel::rotation>;
using gltf_translation_animation = gltf_animation_channel<gltf_asset::animation::channel::translation>;
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<float> range() const
{
return scale_.range() | rotation_.range() | translation_.range();
}
geom::affine_transform<float, 3, 3> 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_;
};
}

View file

@ -4,6 +4,7 @@
#include <psemek/gfx/color.hpp>
#include <psemek/geom/vector.hpp>
#include <psemek/geom/quaternion.hpp>
#include <psemek/geom/affine_transform.hpp>
#include <psemek/geom/easing.hpp>
#include <vector>
@ -29,6 +30,7 @@ namespace psemek::gfx
geom::vector<float, 3> translation;
geom::quaternion<float> rotation;
geom::vector<float, 3> scale;
geom::affine_transform<float, 3, 3> transform;
};
struct mesh
@ -156,8 +158,16 @@ namespace psemek::gfx
std::vector<buffer> buffers;
std::vector<light> lights; // KHR_lights_punctual
std::unordered_map<std::string, std::size_t> node_index;
std::unordered_map<std::string, std::size_t> material_index;
std::unordered_map<std::string, std::size_t> node_index; // node by name
std::unordered_map<std::string, std::size_t> material_index; // material by name
struct animation_and_channel
{
std::size_t animation;
std::size_t channel;
};
std::vector<std::vector<animation_and_channel>> node_animations; // node to list of animation channels that affect it
};
gltf_asset parse_gltf(io::istream && stream);

View file

@ -1,4 +1,7 @@
#include <psemek/gfx/gltf_parser.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/util/to_string.hpp>
#include <boost/preprocessor/stringize.hpp>
@ -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;
}