Commit bf977a2e authored by Florian Oetke's avatar Florian Oetke
Browse files

refactored animation system in preparation for QBS

parent 09f1ad62
......@@ -39,9 +39,9 @@ build_clang:
- cmake -G Ninja -DCMAKE_INSTALL_PREFIX:PATH=../bin -DCMAKE_BUILD_TYPE=Release -DMIRRAGE_EXPORT_EXECUTABLE=ON ..
- cmake --build . --target src/install
- cmake --build . --target test
- wget -N https://github.com/lowkey42/mirrage/releases/download/v0.2/model_data.tar.xz || true
- wget -N https://github.com/lowkey42/mirrage/releases/download/v0.2/model_data_lbs.tar.xz || true
- cd bin/bin
- tar xf ../../model_data.tar.xz
- tar xf ../../model_data_lbs.tar.xz
artifacts:
paths:
- build/bin
......
......@@ -6,6 +6,7 @@
#include <mirrage/utils/log.hpp>
#include <assimp/scene.h>
#include <glm/gtx/matrix_decompose.hpp>
#include <glm/gtx/string_cast.hpp>
#include <gsl/gsl>
......@@ -16,10 +17,10 @@
namespace mirrage {
Bone_data::Bone_data(const aiNode& node,
renderer::Bone_transform local_node_transform,
int idx,
int parent_idx)
Bone_data::Bone_data(const aiNode& node,
const renderer::Local_bone_transform& local_node_transform,
int idx,
int parent_idx)
: assimp_node(&node)
, assimp_bone(nullptr)
, name(node.mName.C_Str())
......@@ -42,8 +43,15 @@ namespace mirrage {
auto idx = skeleton.bones.size();
skeleton.bones_by_name.emplace(node->mName.C_Str(), idx);
auto scale = glm::vec3();
auto orientation = glm::quat();
auto translation = glm::vec3();
auto skew = glm::vec3();
auto perspective = glm::vec4();
glm::decompose(to_glm(node->mTransformation), scale, orientation, translation, skew, perspective);
skeleton.bones.emplace_back(
*node, renderer::to_bone_transform(to_glm(node->mTransformation)), idx, parent_idx);
*node, renderer::Local_bone_transform{orientation, translation, scale}, idx, parent_idx);
for(auto& c : gsl::span(node->mChildren, node->mNumChildren)) {
recurse(c, idx, recurse);
......@@ -133,9 +141,7 @@ namespace mirrage {
write(out_file, std::uint16_t(0));
write(out_file, std::uint32_t(skeleton.bones.size()));
write(out_file,
renderer::to_bone_transform(
glm::inverse(renderer::from_bone_transform(skeleton.bones[0].local_node_transform))));
write(out_file, renderer::to_bone_transform(glm::inverse(to_glm(scene.mRootNode->mTransformation))));
for(auto& bone : skeleton.bones)
write(out_file, bone.offset);
......
......@@ -19,15 +19,18 @@ namespace mirrage {
const aiNode* assimp_node;
const aiBone* assimp_bone;
std::string name;
int idx = -1;
std::string parent_name;
int parent_idx = -1;
renderer::Bone_transform offset{1};
renderer::Bone_transform local_node_transform{1};
std::string name;
int idx = -1;
std::string parent_name;
int parent_idx = -1;
renderer::Bone_transform offset{1};
renderer::Local_bone_transform local_node_transform{};
Bone_data() = default;
Bone_data(const aiNode& node, renderer::Bone_transform loca_node_transform, int idx, int parent_idx);
Bone_data(const aiNode& node,
const renderer::Local_bone_transform& loca_node_transform,
int idx,
int parent_idx);
};
struct Skeleton_data {
std::vector<Bone_data> bones;
......
......@@ -159,6 +159,7 @@ namespace mirrage::asset {
: stream(aid, manager, reinterpret_cast<File_handle*>(PHYSFS_openRead(path.c_str())), path)
, std::istream(_fbuf.get())
{
exceptions(std::istream::badbit);
}
istream::istream(istream&& o) : stream(std::move(o)), std::istream(_fbuf.get()) {}
auto istream::operator=(istream&& s) -> istream&
......@@ -208,6 +209,7 @@ namespace mirrage::asset {
: stream(aid, manager, reinterpret_cast<File_handle*>(PHYSFS_openWrite(path.c_str())), path)
, std::ostream(_fbuf.get())
{
exceptions(std::istream::badbit);
}
ostream::ostream(ostream&& o) : stream(std::move(o)), std::ostream(_fbuf.get()) {}
ostream::~ostream() { _manager._post_write(); }
......
......@@ -14,11 +14,20 @@ namespace mirrage::renderer {
using Bone_id = std::int_fast32_t;
struct Local_bone_transform {
glm::quat orientation;
glm::vec3 translation;
glm::vec3 scale;
};
static_assert(sizeof(Local_bone_transform) == 4 * (4 + 3 + 3), "Local_bone_transform contains padding");
using Bone_transform = glm::mat3x4;
extern auto default_bone_transform() -> Bone_transform;
extern auto to_bone_transform(const glm::vec3& translation,
const glm::quat& orientation,
const glm::vec3& scale) -> Bone_transform;
extern auto to_bone_transform(const Local_bone_transform&) -> Bone_transform;
extern auto to_bone_transform(const glm::mat4&) -> Bone_transform;
extern auto from_bone_transform(const Bone_transform&) -> glm::mat4;
......@@ -48,8 +57,8 @@ namespace mirrage::renderer {
* | INVERSE BIND POSE | mesh space -> bone space
* * 12 * BONE_COUNT
*
* | NODE TRANSFORM | bone space -> mesh space
* * 12 * BONE_COUNT
* | NODE TRANSFORM | bone space -> mesh space (Local_bone_transform)
* * 10 * BONE_COUNT
*
* | BONE PARENT INDEX | signed; <0 => none
* * BONE_COUNT
......@@ -70,9 +79,9 @@ namespace mirrage::renderer {
return _inv_bind_poses[std::size_t(bone)];
}
auto node_transform(Bone_id bone) const -> const Bone_transform&
auto node_transform(Bone_id bone) const -> const Local_bone_transform&
{
return _node_offset[std::size_t(bone)];
return _node_transforms[std::size_t(bone)];
}
auto inverse_root_transform() const -> const Bone_transform& { return _inv_root_transform; }
......@@ -85,12 +94,15 @@ namespace mirrage::renderer {
return util::nothing;
}
void to_final_transforms(gsl::span<const Local_bone_transform> in,
gsl::span<Bone_transform> out) const;
private:
std::vector<Bone_transform> _inv_bind_poses;
std::vector<Bone_transform> _node_offset;
std::vector<std::int32_t> _parent_ids;
std::vector<util::Str_id> _names;
Bone_transform _inv_root_transform;
std::vector<Bone_transform> _inv_bind_poses;
std::vector<Local_bone_transform> _node_transforms;
std::vector<std::int32_t> _parent_ids;
std::vector<util::Str_id> _names;
Bone_transform _inv_root_transform;
std::unordered_map<util::Str_id, std::size_t> _bones_by_name;
};
......@@ -154,7 +166,8 @@ namespace mirrage::renderer {
public:
Animation(asset::istream&);
auto bone_transform(Bone_id, float time, Animation_key& key) const -> util::maybe<Bone_transform>;
auto bone_transform(Bone_id, float time, Animation_key& key) const
-> util::maybe<Local_bone_transform>;
auto duration() const { return _duration; }
......
......@@ -12,11 +12,23 @@ namespace mirrage::renderer {
class Pose_comp : public ecs::Component<Pose_comp> {
public:
static constexpr const char* name() { return "Pose"; }
friend void load_component(ecs::Deserializer& state, Pose_comp&);
friend void save_component(ecs::Serializer& state, const Pose_comp&);
Pose_comp() = default;
Pose_comp(ecs::Entity_handle owner, ecs::Entity_manager& em) : Component(owner, em) {}
std::vector<Bone_transform> bone_transforms;
auto bone_transforms() const -> gsl::span<const Local_bone_transform> { return _bone_transforms; }
auto bone_transforms() -> gsl::span<Local_bone_transform> { return _bone_transforms; }
auto skeleton() const -> const Skeleton& { return *_skeleton; }
private:
friend class Animation_pass;
asset::AID _skeleton_id;
asset::Ptr<Skeleton> _skeleton;
std::vector<Local_bone_transform> _bone_transforms;
};
class Shared_pose_comp : public ecs::Component<Shared_pose_comp> {
......
......@@ -11,6 +11,7 @@ namespace mirrage::graphic {
} // namespace mirrage::graphic
namespace mirrage::renderer {
class Pose_comp;
class Deferred_geometry_subpass {
public:
......@@ -27,6 +28,17 @@ namespace mirrage::renderer {
void draw(Frame_data&, graphic::Render_pass&);
private:
struct Animation_upload_queue_entry {
const Model* model;
const Pose_comp* pose;
std::int32_t uniform_offset;
Animation_upload_queue_entry(const Model* model, Pose_comp& pose, std::int32_t uniform_offset)
: model(model), pose(&pose), uniform_offset(uniform_offset)
{
}
};
ecs::Entity_manager& _ecs;
Deferred_renderer& _renderer;
......@@ -38,6 +50,6 @@ namespace mirrage::renderer {
util::iter_range<std::vector<Geometry>::iterator> _rigged_geometry_range;
std::unordered_map<ecs::Entity_handle, std::uint32_t> _animation_uniform_offsets;
std::unordered_set<ecs::Entity_handle> _dead_shared_poses;
std::vector<Animation_upload_queue_entry> _animation_uniform_queue;
};
} // namespace mirrage::renderer
......@@ -25,12 +25,17 @@ namespace mirrage::renderer {
} // namespace
auto default_bone_transform() -> Bone_transform { return Bone_transform(1); }
auto to_bone_transform(const glm::vec3& translation, const glm::quat& orientation, const glm::vec3& scale)
-> Bone_transform
{
return to_bone_transform(glm::translate(glm::mat4(1), translation) * glm::mat4_cast(orientation)
* glm::scale(glm::mat4(1.f), scale));
}
auto to_bone_transform(const Local_bone_transform& t) -> Bone_transform
{
return to_bone_transform(t.translation, t.orientation, t.scale);
}
auto to_bone_transform(const glm::mat4& m) -> Bone_transform
{
MIRRAGE_INVARIANT(
......@@ -94,12 +99,12 @@ namespace mirrage::renderer {
_inv_root_transform = read<Bone_transform>(file);
_inv_bind_poses.resize(bone_count);
_node_offset.resize(bone_count);
_node_transforms.resize(bone_count);
_parent_ids.resize(bone_count);
_names.resize(bone_count);
read(file, bone_count, _inv_bind_poses);
read(file, bone_count, _node_offset);
read(file, bone_count, _node_transforms);
read(file, bone_count, _parent_ids);
read(file, bone_count, _names);
......@@ -114,6 +119,30 @@ namespace mirrage::renderer {
}
void Skeleton::to_final_transforms(gsl::span<const Local_bone_transform> in,
gsl::span<Bone_transform> out) const
{
if(in.size() != out.size()) {
std::fill(out.begin(), out.end(), default_bone_transform());
return;
}
for(auto i : util::range(in.size())) {
auto parent = _parent_ids[std::size_t(i)];
if(parent >= 0)
out[i] = mul(out[parent], to_bone_transform(in[i]));
else
out[i] = to_bone_transform(in[i]);
}
auto i = gsl::index(0);
for(auto& transform : out) {
transform = mul(inverse_root_transform(), transform, inv_bind_pose(i));
i++;
}
}
Animation::Animation(asset::istream& file)
{
auto header = std::array<char, 4>();
......@@ -261,7 +290,7 @@ namespace mirrage::renderer {
} // namespace
auto Animation::bone_transform(Bone_id bone_idx, float time, Animation_key& key) const
-> util::maybe<Bone_transform>
-> util::maybe<Local_bone_transform>
{
const auto& bone_data = _bones.at(std::size_t(bone_idx));
......@@ -288,7 +317,7 @@ namespace mirrage::renderer {
auto scale = interpolate(bone_data.scales, scale_t, key.scale_key);
auto rotation = interpolate(bone_data.orientations, orientation_t, key.orientation_key);
return to_bone_transform(position, rotation, scale);
return util::maybe<Local_bone_transform>{rotation, position, scale};
}
......
......@@ -2,9 +2,26 @@
namespace mirrage::renderer {
void load_component(ecs::Deserializer& state, Pose_comp& a)
{
auto skeleton = a._skeleton_id.str();
state.read_virtual(sf2::vmember("skeleton", skeleton));
if(!skeleton.empty()) {
a._skeleton_id = skeleton;
a._skeleton = state.assets.load<Skeleton>(skeleton);
}
}
void save_component(ecs::Serializer& state, const Pose_comp& a)
{
state.write_virtual(sf2::vmember("skeleton", a._skeleton_id.str()));
}
void load_component(ecs::Deserializer& state, Animation_comp& a)
{
auto skeleton = std::string();
auto skeleton = a._skeleton_id.str();
auto preload = std::unordered_map<std::string, std::string>();
auto default_animation = a._current_animation_id.get_or(""_strid).str();
......@@ -17,8 +34,10 @@ namespace mirrage::renderer {
sf2::vmember("paused", a._paused),
sf2::vmember("default_looped", a._looped));
a._skeleton_id = skeleton;
a._skeleton = state.assets.load<Skeleton>(skeleton);
if(!skeleton.empty()) {
a._skeleton_id = skeleton;
a._skeleton = state.assets.load<Skeleton>(skeleton);
}
a._preloaded_animations.clear();
for(auto&& [key, aid] : preload) {
......
......@@ -29,7 +29,6 @@ namespace mirrage::renderer {
void Animation_pass::draw(Frame_data& frame)
{
// FIXME: currently uploads a new skeleton for each sub-mesh!
for(auto& geo : frame.geometry_queue) {
_ecs.get(geo.entity).process([&](ecs::Entity_facet& entity) {
auto anim_mb = entity.get<Animation_comp>();
......@@ -37,7 +36,7 @@ namespace mirrage::renderer {
return; // not animated
auto& anim = anim_mb.get_or_throw();
if(!anim._skeleton || !anim._current_animation || !anim._dirty)
if(!anim._current_animation || !anim._dirty)
return; // no animation playing
entity.get<Pose_comp>().process([&](auto& skeleton) { _update_animation(anim, skeleton); });
......@@ -51,29 +50,19 @@ namespace mirrage::renderer {
const auto bone_count = skeleton_data.bone_count();
const auto& animation = *anim_comp._current_animation;
result.bone_transforms.reserve(std::size_t(bone_count));
result.bone_transforms.clear();
result._bone_transforms.clear();
result._bone_transforms.reserve(std::size_t(bone_count));
anim_comp._dirty = false;
anim_comp._animation_keys.resize(std::size_t(bone_count), Animation_key{});
for(auto i : util::range(bone_count)) {
auto parent = skeleton_data.parent_bone(i).process(
Bone_transform(1.f), [&](auto idx) { return result.bone_transforms[std::size_t(idx)]; });
auto& key = anim_comp._animation_keys[std::size_t(i)];
auto local = animation.bone_transform(i, anim_comp._time, key).get_or([&] {
return skeleton_data.node_transform(i);
});
result.bone_transforms.emplace_back(mul(parent, local));
}
for(auto i : util::range(bone_count)) {
result.bone_transforms[std::size_t(i)] = mul(skeleton_data.inverse_root_transform(),
result.bone_transforms[std::size_t(i)],
skeleton_data.inv_bind_pose(i));
result._bone_transforms.emplace_back(
animation.bone_transform(i, anim_comp._time, key).get_or([&] {
return skeleton_data.node_transform(i);
}));
}
}
......
......@@ -135,7 +135,7 @@ namespace mirrage::renderer {
// upload skeleton pose
_animation_uniform_offsets.clear();
_dead_shared_poses.clear();
_animation_uniform_queue.clear();
auto required_size = std::int32_t(0);
auto alignment = std::int32_t(
_renderer.device().physical_device_properties().limits.minUniformBufferOffsetAlignment);
......@@ -146,32 +146,31 @@ namespace mirrage::renderer {
};
for(auto& geo : _rigged_geometry_range) {
auto entity = _ecs.get(geo.entity).get_or_throw("Invalid entity in render queue");
if(!entity.has<Pose_comp>())
continue;
auto [_, success] = _animation_uniform_offsets.try_emplace(
geo.entity, gsl::narrow<std::uint32_t>(required_size));
(void) _;
if(success) {
required_size += aligned_byte_size(geo.model->bone_count());
}
}
const auto offset = gsl::narrow<std::uint32_t>(required_size);
for(auto& geo : _rigged_geometry_range) {
auto entity = _ecs.get(geo.entity).get_or_throw("Invalid entity in render queue");
entity.get<Shared_pose_comp>().process([&](auto& sp) {
auto upload_required = entity.get<Shared_pose_comp>().process(true, [&](auto& sp) {
auto pose_offset = util::find_maybe(_animation_uniform_offsets, sp.pose_owner);
if(pose_offset.is_some()) {
_animation_uniform_offsets.emplace(geo.entity, pose_offset.get_or_throw());
} else {
// TODO: upload default matrices instead
_animation_uniform_offsets.emplace(geo.entity, gsl::narrow<std::uint32_t>(required_size));
required_size += aligned_byte_size(geo.model->bone_count());
_dead_shared_poses.emplace(geo.entity);
}
_animation_uniform_offsets.emplace(geo.entity, pose_offset.get_or(offset));
if(pose_offset.is_some())
return false;
entity = _ecs.get(sp.pose_owner).get_or_throw("Invalid entity in render queue");
return true;
});
if(upload_required) {
entity.get<Pose_comp>().process([&](auto& pose) {
auto [_, success] = _animation_uniform_offsets.try_emplace(entity.handle(), offset);
(void) _;
if(success) {
_animation_uniform_queue.emplace_back(geo.model, pose, required_size);
required_size += aligned_byte_size(geo.model->bone_count());
}
});
}
}
if(_animation_uniforms.resize(required_size)) {
......@@ -190,22 +189,14 @@ namespace mirrage::renderer {
_renderer.device().vk_device()->updateDescriptorSets(1, &anim_desc_writes, 0, nullptr);
}
for(auto& geo : _rigged_geometry_range) {
auto entity = _ecs.get(geo.entity).get_or_throw("Invalid entity in render queue");
auto pose = entity.get<Pose_comp>();
auto shared_pose = entity.get<Shared_pose_comp>();
auto offset = std::int32_t(_animation_uniform_offsets.at(geo.entity));
for(auto& upload : _animation_uniform_queue) {
_animation_uniforms.update_objects<glm::mat3x4>(upload.uniform_offset, [&](auto uniform_matrices) {
auto geo_matrices = uniform_matrices.subspan(0, upload.model->bone_count());
_animation_uniforms.update_objects<glm::mat3x4>(offset, [&](auto uniform_matrices) {
auto geo_matrices = uniform_matrices.subspan(0, geo.model->bone_count());
if(upload.pose->bone_transforms().size() == upload.model->bone_count()) {
upload.pose->skeleton().to_final_transforms(upload.pose->bone_transforms(), geo_matrices);
if(pose.is_some()
&& pose.get_or_throw().bone_transforms.size() == std::size_t(geo.model->bone_count())) {
auto& sm = pose.get_or_throw().bone_transforms;
std::memcpy(geo_matrices.data(), sm.data(), sm.size() * sizeof(glm::mat3x4));
} else if(shared_pose.is_nothing() || _dead_shared_poses.count(geo.entity)) {
} else {
std::fill(geo_matrices.begin(), geo_matrices.end(), glm::mat3x4(1));
}
});
......
......@@ -18,6 +18,11 @@
namespace mirrage::util {
template <typename T>
class maybe;
struct nothing_t;
namespace details {
struct maybe_else_callable {
bool is_nothing;
......@@ -29,12 +34,28 @@ namespace mirrage::util {
f();
}
};
template <typename T, typename... Args>
constexpr bool is_first_maybe()
{
if constexpr(sizeof...(Args) == 0)
return true;
else {
using First_arg = std::remove_cv_t<
std::remove_reference_t<typename std::tuple_element<0, std::tuple<Args...>>::type>>;
return std::is_same_v<maybe<T>, First_arg> || std::is_same_v<nothing_t, First_arg>;
}
}
} // namespace details
template <typename T>
class maybe {
public:
maybe() noexcept : _valid(false) {}
template <class... Args, typename = std::enable_if_t<!details::is_first_maybe<T, Args...>()>>
explicit maybe(Args&&... args) noexcept : _valid(true), _data{std::forward<Args>(args)...}
{
}
/*implicit*/ maybe(T&& data) noexcept : _valid(true), _data(std::move(data)) {}
/*implicit*/ maybe(const T& data) noexcept : _valid(true), _data(data) {}
maybe(const maybe& o) noexcept : _valid(o._valid), _data(o._data) {}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment