Commit 7777cc54 authored by Florian Oetke's avatar Florian Oetke
Browse files

better animation interpolation

parent 13adfd11
......@@ -17,16 +17,17 @@
namespace mirrage {
using renderer::detail::Behaviour;
namespace {
auto invalid_char(char c) { return !std::isalnum(c); }
auto to_our_behaviour(aiAnimBehaviour b)
{
using renderer::detail::Behaviour;
switch(b) {
case aiAnimBehaviour_DEFAULT: return Behaviour::node_transform;
case aiAnimBehaviour_CONSTANT: return Behaviour::nearest;
case aiAnimBehaviour_LINEAR: return Behaviour::extrapolate;
case aiAnimBehaviour_DEFAULT: [[fallthrough]];
case aiAnimBehaviour_CONSTANT: return Behaviour::clamp;
case aiAnimBehaviour_LINEAR: return Behaviour::linear;
case aiAnimBehaviour_REPEAT: return Behaviour::repeat;
case _aiAnimBehaviour_Force32Bit: break;
......@@ -34,6 +35,46 @@ namespace mirrage {
MIRRAGE_FAIL("Invalid/Unexpected aiAnimBehaviour: " << static_cast<int>(b));
}
void reserve_result_buffers(aiAnimation* anim,
std::vector<float>& times,
std::vector<glm::vec3>& positions,
std::vector<glm::vec3>& scales,
std::vector<glm::quat>& orientations)
{
auto time_count = std::size_t(0);
auto position_count = std::size_t(0);
auto scale_count = std::size_t(0);
auto orientation_count = std::size_t(0);
for(auto channel : gsl::span(anim->mChannels, anim->mNumChannels)) {
time_count = channel->mNumPositionKeys + channel->mNumScalingKeys + channel->mNumRotationKeys;
position_count = channel->mNumPositionKeys;
scale_count = channel->mNumScalingKeys;
orientation_count = channel->mNumRotationKeys;
}
times.reserve(time_count);
positions.reserve(position_count);
scales.reserve(scale_count);
orientations.reserve(orientation_count);
}
struct Bone_anim {
Behaviour pre_behaviour = Behaviour::clamp;
Behaviour post_behaviour = Behaviour::clamp;
std::size_t position_count = 0;
std::size_t position_times_idx = 0;
std::size_t positions_idx = 0;
std::size_t scale_count = 0;
std::size_t scale_times_idx = 0;
std::size_t scales_idx = 0;
std::size_t orientation_count = 0;
std::size_t orientation_times_idx = 0;
std::size_t orientations_idx = 0;
};
} // namespace
void parse_animations(const std::string& model_name,
......@@ -57,17 +98,22 @@ namespace mirrage {
}
for(auto anim : animations) {
using namespace renderer::detail;
auto name = std::string(anim->mName.C_Str());
name.erase(std::remove_if(name.begin(), name.end(), invalid_char), name.end());
auto animation_data = Animation_data();
animation_data.duration = static_cast<float>(anim->mDuration / anim->mTicksPerSecond);
animation_data.bones.resize(skeleton.bones.size(), Bone_animation{});
auto times = std::vector<float>();
auto positions = std::vector<glm::vec3>();
auto scales = std::vector<glm::vec3>();
auto orientations = std::vector<glm::quat>();
reserve_result_buffers(anim, times, positions, scales, orientations);
auto bones = std::vector<Bone_anim>();
bones.resize(skeleton.bones.size());
float duration = static_cast<float>(anim->mDuration / anim->mTicksPerSecond);
auto channels = gsl::span(anim->mChannels, anim->mNumChannels);
for(auto channel : channels) {
for(auto channel : gsl::span(anim->mChannels, anim->mNumChannels)) {
auto bone_idx = skeleton.bones_by_name.find(channel->mNodeName.C_Str());
if(bone_idx == skeleton.bones_by_name.end()) {
LOG(plog::warning) << "Couldn't find bone '" << channel->mNodeName.C_Str()
......@@ -75,36 +121,35 @@ namespace mirrage {
continue;
}
auto& per_bone_data = animation_data.bones.at(std::size_t(bone_idx->second));
auto& per_bone_data = bones.at(std::size_t(bone_idx->second));
per_bone_data.pre_behaviour = to_our_behaviour(channel->mPreState);
per_bone_data.post_behaviour = to_our_behaviour(channel->mPostState);
per_bone_data.positions.reserve(channel->mNumPositionKeys);
for(auto i = 0; i < int(channel->mNumPositionKeys); i++) {
auto& p = channel->mPositionKeys[i];
per_bone_data.positions.emplace_back(static_cast<float>(p.mTime / anim->mTicksPerSecond),
p.mValue.x,
p.mValue.y,
p.mValue.z);
// positions
per_bone_data.position_count = channel->mNumPositionKeys;
per_bone_data.position_times_idx = times.size();
per_bone_data.positions_idx = positions.size();
for(auto& p : gsl::span(channel->mPositionKeys, channel->mNumPositionKeys)) {
times.emplace_back(static_cast<float>(p.mTime / anim->mTicksPerSecond));
positions.emplace_back(p.mValue.x, p.mValue.y, p.mValue.z);
}
per_bone_data.scales.reserve(channel->mNumScalingKeys);
for(auto i = 0; i < int(channel->mNumScalingKeys); i++) {
auto& p = channel->mScalingKeys[i];
per_bone_data.scales.emplace_back(static_cast<float>(p.mTime / anim->mTicksPerSecond),
p.mValue.x,
p.mValue.y,
p.mValue.z);
// scales
per_bone_data.scale_count = channel->mNumScalingKeys;
per_bone_data.scale_times_idx = times.size();
per_bone_data.scales_idx = scales.size();
for(auto& p : gsl::span(channel->mScalingKeys, channel->mNumScalingKeys)) {
times.emplace_back(static_cast<float>(p.mTime / anim->mTicksPerSecond));
scales.emplace_back(p.mValue.x, p.mValue.y, p.mValue.z);
}
per_bone_data.rotations.reserve(channel->mNumRotationKeys);
for(auto i = 0; i < int(channel->mNumRotationKeys); i++) {
auto& p = channel->mRotationKeys[i];
per_bone_data.rotations.emplace_back(static_cast<float>(p.mTime / anim->mTicksPerSecond),
p.mValue.w,
p.mValue.x,
p.mValue.y,
p.mValue.z);
// orientations
per_bone_data.orientation_count = channel->mNumRotationKeys;
per_bone_data.orientation_times_idx = times.size();
per_bone_data.orientations_idx = orientations.size();
for(auto& p : gsl::span(channel->mRotationKeys, channel->mNumRotationKeys)) {
times.emplace_back(static_cast<float>(p.mTime / anim->mTicksPerSecond));
orientations.emplace_back(p.mValue.w, p.mValue.x, p.mValue.y, p.mValue.z);
}
}
......@@ -113,7 +158,43 @@ namespace mirrage {
util::to_lower_inplace(filename);
auto file = std::ofstream(filename);
MIRRAGE_INVARIANT(file, "Couldn't open output animation file for: " << name);
sf2::serialize_json(file, animation_data);
// write animation data
file.write("MAFF", 4);
constexpr auto version = std::uint16_t(1);
write(file, version);
write(file, std::uint16_t(0));
write(file, float(duration));
write(file, std::uint32_t(bones.size()));
write(file, std::uint32_t(times.size()));
write(file, std::uint32_t(positions.size()));
write(file, std::uint32_t(scales.size()));
write(file, std::uint32_t(orientations.size()));
write(file, times);
write(file, positions);
write(file, scales);
write(file, orientations);
for(auto& bone : bones) {
write(file, std::uint16_t(bone.pre_behaviour));
write(file, std::uint16_t(bone.post_behaviour));
write(file, std::uint32_t(bone.position_count));
write(file, std::uint32_t(bone.position_times_idx));
write(file, std::uint32_t(bone.positions_idx));
write(file, std::uint32_t(bone.scale_count));
write(file, std::uint32_t(bone.scale_times_idx));
write(file, std::uint32_t(bone.scales_idx));
write(file, std::uint32_t(bone.orientation_count));
write(file, std::uint32_t(bone.orientation_times_idx));
write(file, std::uint32_t(bone.orientations_idx));
}
file.write("MAFF", 4);
}
}
......
......@@ -147,6 +147,8 @@ namespace mirrage {
write(out_file, util::Str_id(bone.name, true));
}
out_file.write("MBFF", 4);
LOG(plog::debug) << "Bone count: " << skeleton.bones.size();
return skeleton;
......
......@@ -32,6 +32,8 @@ namespace mirrage::renderer {
*
* | BONE REF NAME | util::Str_id (empty if invalid)
* * BONE_COUNT
*
* | M | B | F | F |
*/
class Skeleton {
public:
......@@ -67,91 +69,90 @@ namespace mirrage::renderer {
std::unordered_map<util::Str_id, std::size_t> _bones_by_name;
};
namespace detail {
template <class T>
struct Timed {
T value;
float time;
Timed() = default;
template <class... Args>
Timed(float time, Args&&... args) : value(std::forward<Args>(args)...), time(time)
{
}
};
enum class Behaviour { node_transform, nearest, extrapolate, repeat };
struct Bone_animation {
std::vector<Timed<glm::vec3>> positions;
std::vector<Timed<glm::vec3>> scales;
std::vector<Timed<glm::quat>> rotations;
Behaviour pre_behaviour = Behaviour::node_transform;
Behaviour post_behaviour = Behaviour::node_transform;
};
struct Animation_data {
float duration;
std::vector<detail::Bone_animation> bones;
};
template <class Reader>
auto load(sf2::Deserializer<Reader>& reader, Timed<glm::vec3>& v)
{
reader.read_virtual(sf2::vmember("time", v.time),
sf2::vmember("x", v.value.x),
sf2::vmember("y", v.value.y),
sf2::vmember("z", v.value.z));
}
template <class Writer>
auto save(sf2::Serializer<Writer>& writer, const Timed<glm::vec3>& v)
{
writer.write_virtual(sf2::vmember("time", v.time),
sf2::vmember("x", v.value.x),
sf2::vmember("y", v.value.y),
sf2::vmember("z", v.value.z));
}
template <class Reader>
auto load(sf2::Deserializer<Reader>& reader, Timed<glm::quat>& v)
{
reader.read_virtual(sf2::vmember("time", v.time),
sf2::vmember("x", v.value.x),
sf2::vmember("y", v.value.y),
sf2::vmember("z", v.value.z),
sf2::vmember("w", v.value.w));
}
template <class Writer>
auto save(sf2::Serializer<Writer>& writer, const Timed<glm::quat>& v)
{
writer.write_virtual(sf2::vmember("time", v.time),
sf2::vmember("x", v.value.x),
sf2::vmember("y", v.value.y),
sf2::vmember("z", v.value.z),
sf2::vmember("w", v.value.w));
}
enum class Behaviour { clamp, linear, repeat };
}
sf2_enumDef(Behaviour, node_transform, nearest, extrapolate, repeat);
sf2_structDef(Bone_animation, pre_behaviour, post_behaviour, positions, scales, rotations);
sf2_structDef(Animation_data, duration, bones);
} // namespace detail
struct Animation_key {
std::int32_t position_key = 0;
std::int32_t orientation_key = 0;
std::int32_t scale_key = 0;
std::int32_t orientation_key = 0;
};
/*
* File format:
* | 0 | 1 | 2 | 3 |
* | M | A | F | F |
* | VERSION | RESERVED |
* | DURATION |
* | BONE COUNT |
* | TIME COUNT |
* | POSITION COUNT |
* | SCALE COUNT |
* | ORIENTATION COUNT |
* | TIMES |
* * TIME COUNT
*
* | POSITION X |
* | POSITION Y |
* | POSITION Z |
* * POSITION COUNT
*
* | SCALE X |
* | SCALE Y |
* | SCALE Z |
* * SCALE COUNT
*
* | ORIENTATION X |
* | ORIENTATION Y |
* | ORIENTATION Z |
* | ORIENTATION W |
* * ORIENTATION COUNT
*
* |PRE BEHAVIOUR | POST BEHAVIOUR|
* | POSITION COUNT |
* | POSITION TIME START INDEX |
* | POSITION START INDEX |
* | SCALE COUNT |
* | SCALE TIME START INDEX |
* | SCALE START INDEX |
* | ORIENTATION COUNT |
* | ORIENTATION TIME START INDEX |
* | ORIENTATION START INDEX |
* * BONE COUNT
*
* | M | A | F | F |
*/
class Animation {
public:
Animation(asset::istream&);
auto bone_transform(Bone_id, float time, Animation_key& key) const -> util::maybe<glm::mat4>;
auto duration() const { return _duration; }
private:
std::vector<detail::Bone_animation> _bones;
float _duration;
struct Bone_animation {
detail::Behaviour pre_behaviour = detail::Behaviour::clamp;
detail::Behaviour post_behaviour = detail::Behaviour::clamp;
gsl::span<float> position_times;
gsl::span<glm::vec3> positions;
gsl::span<float> scale_times;
gsl::span<glm::vec3> scales;
gsl::span<float> orientation_times;
gsl::span<glm::quat> orientations;
};
std::vector<float> _times;
std::vector<glm::vec3> _positions;
std::vector<glm::vec3> _scales;
std::vector<glm::quat> _orientations;
std::vector<Bone_animation> _bones;
float _duration;
};
} // namespace mirrage::renderer
......
......@@ -33,6 +33,8 @@ namespace mirrage::renderer {
void animation(util::Str_id); ///< play preloaded; for small or frequently used animations
void animation(asset::AID); ///< load + play; for large one-time animations
auto animation() { return _current_animation; }
// TODO: pause, stop, speed, ...
private:
......
......@@ -7,78 +7,200 @@
namespace mirrage::renderer {
namespace {
template <class T>
auto read(asset::istream& in)
{
auto v = T{};
in.read(reinterpret_cast<char*>(&v), sizeof(T));
return v;
}
template <class T>
void read(asset::istream& in, std::size_t size, std::vector<T>& out)
{
out.resize(size);
in.read(reinterpret_cast<char*>(out.data()), std::streamsize(sizeof(T) * size));
}
} // namespace
Skeleton::Skeleton(asset::istream& file)
{
auto header = std::array<char, 4>();
file.read(header.data(), header.size());
MIRRAGE_INVARIANT(header[0] == 'M' && header[1] == 'B' && header[2] == 'F' && header[3] == 'F',
"Mirrage bone file '" << file.aid().str() << "' corrupted.");
"Mirrage bone file '" << file.aid().str() << "' corrupted (header).");
auto version = std::uint16_t(0);
file.read(reinterpret_cast<char*>(&version), sizeof(version));
auto version = read<std::uint16_t>(file);
MIRRAGE_INVARIANT(version == 1, "Unsupported bone file version " << version << ". Expected 1");
// skip reserved
file.read(reinterpret_cast<char*>(&version), sizeof(version));
read<std::uint16_t>(file);
auto bone_count = std::uint32_t(0);
file.read(reinterpret_cast<char*>(&bone_count), sizeof(bone_count));
auto bone_count = read<std::uint32_t>(file);
_inv_bind_poses.resize(bone_count);
_node_offset.resize(bone_count);
_parent_ids.resize(bone_count);
_names.resize(bone_count);
file.read(reinterpret_cast<char*>(_inv_bind_poses.data()), sizeof(glm::mat4) * bone_count);
file.read(reinterpret_cast<char*>(_node_offset.data()), sizeof(glm::mat4) * bone_count);
file.read(reinterpret_cast<char*>(_parent_ids.data()), sizeof(std::int32_t) * bone_count);
file.read(reinterpret_cast<char*>(_names.data()), sizeof(util::Str_id) * bone_count);
read(file, bone_count, _inv_bind_poses);
read(file, bone_count, _node_offset);
read(file, bone_count, _parent_ids);
read(file, bone_count, _names);
for(auto i = std::size_t(0); i < _names.size(); ++i) {
if(_names[i])
_bones_by_name.emplace(_names[i], i);
}
file.read(header.data(), header.size());
MIRRAGE_INVARIANT(header[0] == 'M' && header[1] == 'B' && header[2] == 'F' && header[3] == 'F',
"Mirrage bone file '" << file.aid().str() << "' corrupted (footer).");
}
Animation::Animation(asset::istream& file)
{
auto data = sf2::deserialize_json<detail::Animation_data>(file);
_bones = std::move(data.bones);
_duration = std::move(data.duration);
auto header = std::array<char, 4>();
file.read(header.data(), header.size());
MIRRAGE_INVARIANT(header[0] == 'M' && header[1] == 'A' && header[2] == 'F' && header[3] == 'F',
"Mirrage bone file '" << file.aid().str() << "' corrupted (header).");
auto version = read<std::uint16_t>(file);
MIRRAGE_INVARIANT(version == 1, "Unsupported bone file version " << version << ". Expected 1");
// skip reserved
read<std::uint16_t>(file);
_duration = read<float>(file);
auto bone_count = read<std::uint32_t>(file);
auto time_count = read<std::uint32_t>(file);
auto position_count = read<std::uint32_t>(file);
auto scale_count = read<std::uint32_t>(file);
auto orientation_count = read<std::uint32_t>(file);
read(file, time_count, _times);
read(file, position_count, _positions);
read(file, scale_count, _scales);
read(file, orientation_count, _orientations);
_bones.reserve(bone_count);
for(auto i : util::range(bone_count)) {
(void) i;
auto& bone = _bones.emplace_back();
bone.pre_behaviour = static_cast<detail::Behaviour>(read<std::uint16_t>(file));
bone.post_behaviour = static_cast<detail::Behaviour>(read<std::uint16_t>(file));
auto pos_count = read<std::uint32_t>(file);
auto pos_time_start = read<std::uint32_t>(file);
auto pos_start = read<std::uint32_t>(file);
bone.position_times = gsl::span(&_times[pos_time_start], pos_count);
bone.positions = gsl::span(&_positions[pos_start], pos_count);
auto scale_count = read<std::uint32_t>(file);
auto scale_time_start = read<std::uint32_t>(file);
auto scale_start = read<std::uint32_t>(file);
bone.scale_times = gsl::span(&_times[scale_time_start], scale_count);
bone.scales = gsl::span(&_scales[scale_start], scale_count);
auto orientation_count = read<std::uint32_t>(file);
auto orientation_time_start = read<std::uint32_t>(file);
auto orientation_start = read<std::uint32_t>(file);
bone.orientation_times = gsl::span(&_times[orientation_time_start], orientation_count);
bone.orientations = gsl::span(&_orientations[orientation_start], orientation_count);
}
file.read(header.data(), header.size());
MIRRAGE_INVARIANT(header[0] == 'M' && header[1] == 'A' && header[2] == 'F' && header[3] == 'F',
"Mirrage bone file '" << file.aid().str() << "' corrupted (footer).");
}
namespace {
using detail::Behaviour;
template <class T>
auto interpolate(const std::vector<detail::Timed<T>>& keyframes, float time, std::int32_t& key) -> T
auto interpolate(gsl::span<T> keyframes, float t, std::int32_t& key) -> T
{
// TODO: optimize, like alot; Also: contains errors calculating t
//auto start_time = keyframes.front().time;
//auto end_time = keyframes.back().time;
auto& a = keyframes[key];
auto& b = keyframes[(key + 1) % keyframes.size()];
//time = start_time + std::fmod(time - start_time, std::abs(end_time - start_time));
// TODO: more interesting interpolation curves
if(keyframes.size() == 1)
return keyframes[0].value;
key = 0;
for(; key < int(keyframes.size()) - 1; key++) {
if(time < keyframes[std::size_t(key) + 1].time)
break;
if constexpr(std::is_same_v<glm::vec3, T>) {
return glm::mix(a, b, t);
} else {
return glm::normalize(glm::slerp(a, b, t));
}
}
auto& a = keyframes[std::size_t(key)];
auto& b = keyframes[std::size_t(key + 1) % keyframes.size()];
/// returns the index of the first element not >= value or the top/bottom if out of range
/// performs an interpolation search starting at a given index
/// O(1) if the given index is already near the solution
/// O(log log N) if the data is nearly uniformly distributed
/// O(N) else (worst case)
auto binary_search(gsl::span<const float> container, float value, std::int32_t i)
{
auto high = std::int32_t(container.size() - 2);
auto low = std::int32_t(0);
auto t_delta = b.time - a.time;
auto t = t_delta > 0 ? (time - a.time) / t_delta : 0.f;
do {
if(container[i] > value) {
high = i - 1;
} else if(container[i + 1] <= value) {
low = i + 1;