psemek/libs/gfx/include/psemek/gfx/gltf_animation.hpp

199 lines
5.1 KiB
C++

#pragma once
#include <psemek/gfx/gltf_parser.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/util/exception.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);
}
static output_type normalize(output_type const & v)
{
return v;
}
};
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);
}
static output_type normalize(output_type const & v)
{
return geom::normalized(v);
}
};
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);
}
static output_type normalize(output_type const & v)
{
return v;
}
};
}
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())
{
if (interpolation_ == geom::easing_type::cubic)
return output_[1];
else
return output_.front();
}
if (it == input_.end())
{
if (interpolation_ == geom::easing_type::cubic)
return output_[output_.size() - 2];
else
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::normalize(output_type::zero()
+ 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 util::exception("Unknown 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();
}
gltf_scale_animation const & scale() const { return scale_; }
gltf_rotation_animation const & rotation() const { return rotation_; }
gltf_translation_animation const & translation() const { return translation_; }
private:
gltf_scale_animation scale_;
gltf_rotation_animation rotation_;
gltf_translation_animation translation_;
};
}