This commit is contained in:
Mikulas Florek 2023-08-17 22:39:29 +02:00
parent f7d308a8dc
commit 2831432da4
5 changed files with 100 additions and 91 deletions

View file

@ -20,6 +20,7 @@ Animation::Animation(const Path& path, ResourceManager& resource_manager, IAlloc
, m_allocator(allocator, m_path.c_str())
, m_mem(m_allocator)
, m_translations(m_allocator)
, m_const_translations(m_allocator)
, m_rotations(m_allocator)
, m_const_rotations(m_allocator)
, m_root_motion(m_allocator)
@ -67,16 +68,34 @@ struct AnimationSampler {
const u32 sample_idx = u32(sample);
const float t = sample - sample_idx;
for (u32 i = 0, c = anim.m_translations.size(); i < c; ++i) {
const Animation::TranslationTrack& curve = anim.m_translations[i];
Model::BoneMap::ConstIterator iter = model.getBoneIndex(curve.name);
for (u32 i = 0, c = anim.m_const_translations.size(); i < c; ++i) {
const Animation::ConstTranslationTrack& track = anim.m_const_translations[i];
Model::BoneMap::ConstIterator iter = model.getBoneIndex(track.name);
if (!iter.isValid()) continue;
if constexpr(use_mask) {
if (mask->bones.find(curve.name) == mask->bones.end()) continue;
if (mask->bones.find(track.name) == mask->bones.end()) continue;
}
Vec3 anim_pos = lerp(anim.getTranslation(sample_idx, i), anim.getTranslation(sample_idx + 1, i), t);
const int model_bone_index = iter.value();
if constexpr (use_weight) {
pos[model_bone_index] = lerp(pos[model_bone_index], track.value, weight);
}
else {
pos[model_bone_index] = track.value;
}
}
for (u32 i = 0, c = anim.m_translations.size(); i < c; ++i) {
const Animation::TranslationTrack& track = anim.m_translations[i];
Model::BoneMap::ConstIterator iter = model.getBoneIndex(track.name);
if (!iter.isValid()) continue;
if constexpr(use_mask) {
if (mask->bones.find(track.name) == mask->bones.end()) continue;
}
Vec3 anim_pos = lerp(anim.getTranslation(sample_idx, track), anim.getTranslation(sample_idx + 1, track), t);
const int model_bone_index = iter.value();
if constexpr (use_weight) {
@ -168,7 +187,7 @@ void Animation::setRootMotionBone(BoneNameHash bone_name) {
for (u32 f = 0; f < m_frame_count + 1; ++f) {
LocalRigidTransform tmp = {Vec3(0), Quat::IDENTITY};
if (translation_idx >= 0) tmp.pos = getTranslation(f, translation_idx);
if (translation_idx >= 0) tmp.pos = getTranslation(f, m_translations[translation_idx]);
if (rotation_idx >= 0) tmp.rot = getRotation(f, m_rotations[rotation_idx]);
LocalRigidTransform rm = AnimationSampler::maskRootMotion(m_flags, tmp);
if (!m_root_motion.translations.empty()) m_root_motion.translations[f] = rm.pos;
@ -185,7 +204,7 @@ void Animation::setRootMotionBone(BoneNameHash bone_name) {
}
m_root_motion.rotation_track_idx = rotation_idx;
m_translations[translation_idx].type = Animation::TrackType::ROOT_MOTION_ROOT;
m_root_motion.translation_track_idx = translation_idx;
}
LocalRigidTransform Animation::getRootMotion(Time time) const {
@ -229,47 +248,28 @@ void Animation::getRelativePose(const SampleContext& ctx) {
}
}
Vec3 Animation::getTranslation(Time time, u32 curve_idx) const {
float frame = time.toFrame(m_fps);
const u32 frame_idx = u32(frame);
if (frame_idx < m_frame_count) {
const float frame_t = frame - frame_idx;
return lerp(getTranslation(frame_idx, curve_idx), getTranslation(frame_idx + 1, curve_idx), frame_t);
}
return getTranslation(m_frame_count, curve_idx);
}
static float unpackChannel(u64 val, float min, float to_float_range, u32 bitsize) {
if (bitsize == 0) return min;
const u64 mask = (u64(1) << bitsize) - 1;
return float(min + to_float_range * double(val & mask));
}
Vec3 Animation::getTranslation(u32 frame, u32 curve_idx) const {
const TranslationTrack& track = m_translations[curve_idx];
switch (track.type) {
case Animation::TrackType::CONSTANT: return track.min;
case Animation::TrackType::ROOT_MOTION_ROOT: return m_root_motion.pose_translations[frame];
case Animation::TrackType::SAMPLED: {
const u32 offset = m_translations_frame_size_bits * frame + track.offset_bits;
Vec3 Animation::getTranslation(u32 frame, const TranslationTrack& track) const {
ASSERT(&track >= m_translations.begin() && &track < m_translations.end());
if (u32(&track - m_translations.begin()) == m_root_motion.translation_track_idx) return m_root_motion.pose_translations[frame];
const u32 offset = m_translations_frame_size_bits * frame + track.offset_bits;
u64 tmp;
memcpy(&tmp, &m_translation_stream[offset / 8], sizeof(tmp));
tmp >>= offset & 7;
u64 tmp;
memcpy(&tmp, &m_translation_stream[offset / 8], sizeof(tmp));
tmp >>= offset & 7;
Vec3 res;
res.x = unpackChannel(tmp, track.min.x, track.to_range.x, track.bitsizes[0]);
tmp >>= track.bitsizes[0];
res.y = unpackChannel(tmp, track.min.y, track.to_range.y, track.bitsizes[1]);
tmp >>= track.bitsizes[1];
res.z = unpackChannel(tmp, track.min.z, track.to_range.z, track.bitsizes[2]);
return res;
}
}
ASSERT(false);
return {};
Vec3 res;
res.x = unpackChannel(tmp, track.min.x, track.to_range.x, track.bitsizes[0]);
tmp >>= track.bitsizes[0];
res.y = unpackChannel(tmp, track.min.y, track.to_range.y, track.bitsizes[1]);
tmp >>= track.bitsizes[1];
res.z = unpackChannel(tmp, track.min.z, track.to_range.z, track.bitsizes[2]);
return res;
}
Quat Animation::getRotation(u32 frame, const RotationTrack& track) const {
@ -321,7 +321,7 @@ bool Animation::load(Span<const u8> mem) {
}
if (header.version <= Version::COMPRESSION) {
logError(getPath(), ": version not supported. Please delete '.lumix' directory and try again");
logError(getPath(), ": version too old. Please delete '.lumix' directory and try again");
return false;
}
@ -335,21 +335,21 @@ bool Animation::load(Span<const u8> mem) {
m_mem.resize(size + 8/*padding for unpacker*/);
file.read(&m_mem[0], size);
m_translations.resize(translations_count);
m_translations_frame_size_bits = 0;
InputMemoryStream blob(&m_mem[0], size);
for (int i = 0; i < m_translations.size(); ++i) {
TranslationTrack& track = m_translations[i];
track.name = blob.read<BoneNameHash>();
blob.read(track.type);
for (u32 i = 0; i < translations_count; ++i) {
auto name = blob.read<BoneNameHash>();
auto type = blob.read<Animation::TrackType>();
if (track.type == Animation::TrackType::CONSTANT) {
blob.read(track.min);
if (type == Animation::TrackType::CONSTANT) {
ConstTranslationTrack& track = m_const_translations.emplace();
track.name = name;
blob.read(track.value);
}
else {
TranslationTrack& track = m_translations.emplace();
track.name = name;
blob.read(track.min);
Vec3 range;
blob.read(track.to_range);
blob.read(track.bitsizes);
blob.read(track.offset_bits);

View file

@ -57,14 +57,11 @@ struct Animation final : Resource {
enum class TrackType : u8 {
CONSTANT,
SAMPLED,
ROOT_MOTION_ROOT
ANIMATED
};
enum class Version : u32 {
FIRST = 3,
FLAGS,
COMPRESSION,
COMPRESSION = 6,
LAST
};
@ -84,13 +81,17 @@ struct Animation final : Resource {
Version version;
};
struct ConstTranslationTrack {
BoneNameHash name;
Vec3 value;
};
struct TranslationTrack {
BoneNameHash name;
Vec3 min;
Vec3 to_range; // * to_normalized * (max - min)
u16 offset_bits = 0;
u8 bitsizes[3] = {};
TrackType type;
};
struct ConstRotationTrack {
@ -117,12 +118,14 @@ struct Animation final : Resource {
Animation(const Path& path, ResourceManager& resource_manager, IAllocator& allocator);
ResourceType getType() const override { return TYPE; }
Vec3 getTranslation(u32 frame, u32 curve_idx) const;
Vec3 getTranslation(Time time, u32 curve_idx) const;
void getRelativePose(const SampleContext& ctx);
Time getLength() const { return Time::fromSeconds(m_frame_count / m_fps); }
Vec3 getTranslation(u32 frame, const TranslationTrack& track) const;
Quat getRotation(u32 sample, const RotationTrack& track) const;
const Array<TranslationTrack>& getTranslations() const { return m_translations; }
Quat getRotation(u32 sample, const RotationTrack& curve_idx) const;
const Array<ConstTranslationTrack>& getConstTranslations() const { return m_const_translations; }
const Array<RotationTrack>& getRotations() const { return m_rotations; }
const Array<ConstRotationTrack>& getConstRotations() const { return m_const_rotations; }
struct LocalRigidTransform getRootMotion(Time t) const;
@ -138,6 +141,7 @@ private:
TagAllocator m_allocator;
Array<TranslationTrack> m_translations;
Array<ConstTranslationTrack> m_const_translations;
Array<RotationTrack> m_rotations;
Array<ConstRotationTrack> m_const_rotations;
@ -149,6 +153,7 @@ private:
Array<Quat> rotations;
BoneNameHash bone;
i32 rotation_track_idx = -1;
i32 translation_track_idx = -1;
} m_root_motion;
Array<u8> m_mem;

View file

@ -95,6 +95,7 @@ struct AnimationAssetBrowserPlugin : AssetBrowser::IPlugin {
OutputMemoryStream blob(m_app.getAllocator());
m_parent_meta.serialize(blob);
m_app.getAssetCompiler().updateMeta(Path(Path::getResource(m_resource->getPath())), blob);
m_dirty = false;
}
void windowGUI() override {
@ -134,6 +135,7 @@ struct AnimationAssetBrowserPlugin : AssetBrowser::IPlugin {
const Array<Animation::RotationTrack>& rotations = m_resource->getRotations();
const Array<Animation::ConstRotationTrack>& const_rotations = m_resource->getConstRotations();
const Array<Animation::TranslationTrack>& translations = m_resource->getTranslations();
const Array<Animation::ConstTranslationTrack>& const_translations = m_resource->getConstTranslations();
ImGuiEx::Label("Root rotation");
saveUndo(ImGui::CheckboxFlags("##rmr", (i32*)&m_parent_meta.root_motion_flags, (i32)Animation::Flags::ROOT_ROTATION));
@ -153,54 +155,56 @@ struct AnimationAssetBrowserPlugin : AssetBrowser::IPlugin {
ImGuiEx::Label("Rotation frame size");
ImGui::Text("%d", m_resource->getRotationFrameSizeBits());
u32 const_translation_tracks_count = 0;
for (const Animation::TranslationTrack& curve : translations) {
if (curve.type == Animation::TrackType::CONSTANT) ++const_translation_tracks_count;
}
ImGuiEx::Label("Translation tracks (constant / animated)");
ImGui::Text("%d / %d", const_translation_tracks_count, translations.size() - const_translation_tracks_count);
ImGui::Text("%d / %d", const_translations.size(), translations.size());
ImGuiEx::Label("Rotation tracks (constant / animated)");
ImGui::Text("%d / %d", const_rotations.size(), rotations.size());
if (!translations.empty() && ImGui::TreeNode("Translations")) {
for (const Animation::TranslationTrack& curve : translations) {
u32 curve_idx = u32(&curve - translations.begin());
auto iter = m_model->getBoneIndex(curve.name);
for (const Animation::TranslationTrack& track : translations) {
auto iter = m_model->getBoneIndex(track.name);
if (!iter.isValid()) continue;
const Model::Bone& bone = m_model->getBone(iter.value());
ImGuiTreeNodeFlags flags = m_selected_bone == curve.name ? ImGuiTreeNodeFlags_Selected : 0;
ImGuiTreeNodeFlags flags = m_selected_bone == track.name ? ImGuiTreeNodeFlags_Selected : 0;
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
u32 bits = curve.bitsizes[0] + curve.bitsizes[1] + curve.bitsizes[2];
u32 bits = track.bitsizes[0] + track.bitsizes[1] + track.bitsizes[2];
bool open = ImGui::TreeNodeEx(&bone, flags, "%s (%d bits)", bone.name.c_str(), bits);
if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
m_selected_bone = curve.name;
m_selected_bone = track.name;
}
if (open) {
if (curve.type == Animation::TrackType::CONSTANT) {
Vec3 p = curve.min;
ImGui::Text("%f, %f, %f", p.x, p.y, p.z);
}
else {
ImGui::Columns(4);
for (u32 i = 0; i < m_resource->getFramesCount(); ++i) {
const Vec3 p = m_resource->getTranslation(i, curve_idx);
ImGui::Text("%d:", i);
ImGui::NextColumn();
ImGui::Text("%f", p.x);
ImGui::NextColumn();
ImGui::Text("%f", p.y);
ImGui::NextColumn();
ImGui::Text("%f", p.z);
ImGui::NextColumn();
ImGui::Columns(4);
for (u32 i = 0; i < m_resource->getFramesCount(); ++i) {
const Vec3 p = m_resource->getTranslation(i, track);
ImGui::Text("%d:", i);
ImGui::NextColumn();
ImGui::Text("%f", p.x);
ImGui::NextColumn();
ImGui::Text("%f", p.y);
ImGui::NextColumn();
ImGui::Text("%f", p.z);
ImGui::NextColumn();
}
ImGui::Columns();
}
ImGui::Columns();
ImGui::TreePop();
}
}
for (const Animation::ConstTranslationTrack& track : const_translations) {
auto iter = m_model->getBoneIndex(track.name);
if (!iter.isValid()) continue;
const Model::Bone& bone = m_model->getBone(iter.value());
ImGuiTreeNodeFlags flags = m_selected_bone == track.name ? ImGuiTreeNodeFlags_Selected : 0;
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
bool open = ImGui::TreeNodeEx(&bone, flags, "%s (constant)", bone.name.c_str());
if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
m_selected_bone = track.name;
}
if (open) ImGui::Text("%f; %f; %f", track.value.x, track.value.y, track.value.z);
}
ImGui::TreePop();
}

View file

@ -507,7 +507,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
saveUndo(true);
}
{
if (!node.m_triangles.empty()) {
float w = maximum(ImGui::GetContentRegionAvail().x, 100.f);
ImGui::InvisibleButton("tmp", ImVec2(w, w));
ImDrawList* dl = ImGui::GetWindowDrawList();

View file

@ -1610,7 +1610,7 @@ void FBXImporter::writeAnimations(const Path& src, const ImportConfig& cfg)
}
else {
translation_tracks[bone_idx].is_const = false;
write(Animation::TrackType::SAMPLED);
write(Animation::TrackType::ANIMATED);
write(min);
write((max.x - min.x) / ((1 << bitsizes[0]) - 1));
@ -1711,7 +1711,7 @@ void FBXImporter::writeAnimations(const Path& src, const ImportConfig& cfg)
}
else {
rotation_tracks[bone_idx].is_const = false;
write(Animation::TrackType::SAMPLED);
write(Animation::TrackType::ANIMATED);
u8 skipped_channel = 0;
for (u32 i = 1; i < 4; ++i) {