Started working on AnimationLibrary.

This commit is contained in:
Martin Felis 2025-04-11 19:03:13 +02:00
parent 89fedce539
commit 5d01dfcca2
8 changed files with 343 additions and 3 deletions

View File

@ -59,7 +59,9 @@ add_library(AnimTestbedCode OBJECT
src/AnimGraph/AnimGraphResource.cc src/AnimGraph/AnimGraphResource.cc
src/AnimGraph/AnimGraphResource.h src/AnimGraph/AnimGraphResource.h
src/AnimGraph/SyncTrack.cc src/AnimGraph/SyncTrack.cc
src/AnimGraph/SyncTrack.h) src/AnimGraph/SyncTrack.h
src/AnimGraph/AnimLibrary.h
)
target_include_directories( target_include_directories(
AnimTestbedCode AnimTestbedCode
@ -150,6 +152,9 @@ target_sources(runtests PRIVATE
tests/AnimGraphEvalTests.cc tests/AnimGraphEvalTests.cc
tests/NodeDescriptorTests.cc tests/NodeDescriptorTests.cc
tests/SyncTrackTests.cc tests/SyncTrackTests.cc
tests/AnimDataTests.cc
tests/TestAnimData.cc
tests/TestAnimData.h
tests/main.cc tests/main.cc
${ozz_offline_test_objs} ${ozz_offline_test_objs}
) )

View File

@ -34,6 +34,8 @@ struct AnimDataRef {
struct AnimationResource { struct AnimationResource {
std::string m_name; std::string m_name;
std::string m_filename;
ozz::animation::Animation* m_animation; ozz::animation::Animation* m_animation;
SyncTrack m_sync_track; SyncTrack m_sync_track;
}; };
@ -42,8 +44,9 @@ inline void to_json(
nlohmann::json& j, nlohmann::json& j,
const AnimationResource& animation_resource) { const AnimationResource& animation_resource) {
j["type"] = "AnimationResource"; j["type"] = "AnimationResource";
j["synctrack"] = animation_resource.m_sync_track;
j["name"] = animation_resource.m_name; j["name"] = animation_resource.m_name;
j["filename"] = animation_resource.m_filename;
j["synctrack"] = animation_resource.m_sync_track;
} }
inline void from_json( inline void from_json(
@ -51,8 +54,9 @@ inline void from_json(
AnimationResource& animation_resource) { AnimationResource& animation_resource) {
assert(j["type"] == "AnimationResource"); assert(j["type"] == "AnimationResource");
animation_resource.m_sync_track = j["synctrack"];
animation_resource.m_name = j["name"]; animation_resource.m_name = j["name"];
animation_resource.m_filename = j["filename"];
animation_resource.m_sync_track = j["synctrack"];
} }
struct AnimDataAllocator { struct AnimDataAllocator {

View File

@ -152,6 +152,7 @@ bool AnimSamplerNode::Init(AnimGraphContext& context) {
archive >> *m_animation; archive >> *m_animation;
context.m_animation_map[m_filename] = { context.m_animation_map[m_filename] = {
m_filename,
m_filename, m_filename,
m_animation, m_animation,
SyncTrack()}; SyncTrack()};

133
src/AnimGraph/AnimLibrary.h Normal file
View File

@ -0,0 +1,133 @@
//
// Created by martin on 11.04.25.
//
#ifndef ANIMATIONLIBRARY_H
#define ANIMATIONLIBRARY_H
#include <iostream>
#include "AnimGraph/AnimGraphData.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
struct AnimLibrary {
typedef std::map<std::string, AnimationResource> AnimationResourceMap;
AnimationResourceMap mAnimations = {};
std::vector<ozz::animation::Animation*> mManagedAnimations = {};
AnimLibrary() = default;
~AnimLibrary() { Reset(); }
// Move operators. Needed to avoid duplicate frees on the managed animations.
AnimLibrary(AnimLibrary&& other)
: mAnimations(std::move(other.mAnimations)),
mManagedAnimations(std::move(other.mManagedAnimations)) {};
AnimLibrary& operator=(AnimLibrary&& other) {
mAnimations = other.mAnimations;
mManagedAnimations = std::move(other.mManagedAnimations);
return *this;
}
bool AddAnimation(
const std::string& name,
ozz::animation::Animation* animation) {
AnimationResource animation_resource;
animation_resource.m_name = name;
animation_resource.m_animation = animation;
mAnimations[name] = animation_resource;
return true;
}
bool AddAnimationFile(const std::string& name, const std::string& filename) {
if (mAnimations.find(name) != mAnimations.end()) {
std::cerr << "Cannot add animation '" << name
<< "' to library. Animation already exists." << std::endl;
return false;
}
ozz::animation::Animation* animation = new ozz::animation::Animation;
assert(!filename.empty());
ozz::io::File file(filename.c_str(), "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open animation file " << filename << "."
<< std::endl;
return false;
}
ozz::io::IArchive archive(&file);
if (!archive.TestTag<ozz::animation::Animation>()) {
ozz::log::Err() << "Failed to load animation instance from file "
<< filename << "." << std::endl;
return false;
}
// Once the tag is validated, reading cannot fail.
archive >> *animation;
mManagedAnimations.push_back(animation);
AnimationResource animation_resource;
animation_resource.m_name = name;
animation_resource.m_animation = animation;
animation_resource.m_filename = filename;
mAnimations[name] = animation_resource;
return true;
}
void Reset() {
for (ozz::animation::Animation* animation : mManagedAnimations) {
assert(animation->num_tracks() < 5);
delete animation;
}
mManagedAnimations.clear();
mAnimations.clear();
}
};
inline void to_json(nlohmann::json& j, const AnimLibrary& animation_library) {
j["type"] = "AnimationLibrary";
for (AnimLibrary::AnimationResourceMap::const_iterator iter =
animation_library.mAnimations.cbegin();
iter != animation_library.mAnimations.cend();
++iter) {
j["animations"][iter->first] = iter->second;
}
}
inline void from_json(const nlohmann::json& j, AnimLibrary& animation_library) {
animation_library.Reset();
if (!j.contains("type") || j["type"] != "AnimationLibrary") {
std::cerr << "Invalid type. Expected 'AnimationLibrary'." << std::endl;
}
if (!j.contains("animations")) {
std::cerr << "Invalid AnimationLibrary. Expected 'animations' key."
<< std::endl;
}
for (nlohmann::json::const_iterator iter = j["animations"].begin();
iter != j["animations"].cend();
++iter) {
AnimationResource animation_resource = *iter;
if (!animation_resource.m_filename.empty()) {
animation_library.AddAnimationFile(
iter.key(),
j["animations"][iter.key()]["filename"]);
animation_library.mAnimations[iter.key()].m_sync_track =
animation_resource.m_sync_track;
} else {
animation_library.mAnimations[iter.key()] = *iter;
}
}
}
#endif //ANIMATIONLIBRARY_H

41
tests/AnimDataTests.cc Normal file
View File

@ -0,0 +1,41 @@
//
// Created by martin on 11.04.25.
//
#include "3rdparty/json/json.hpp"
#include "AnimGraph/AnimLibrary.h"
#include "TestAnimData.h"
#include "catch.hpp"
using namespace nlohmann;
TEST_CASE("Serialize AnimLibrary", "[AnimLibrary]") {
AnimLibrary library;
TestAnimData::SingleBoneSkeleton single_bone_testdata;
REQUIRE(library.AddAnimationFile(
"translation_x",
single_bone_testdata.animation_translate_x_resource.m_filename));
REQUIRE(library.AddAnimationFile(
"translation_y",
single_bone_testdata.animation_translate_y_resource.m_filename));
// serialize
json library_data;
library_data = library;
// deserialize
AnimLibrary library_deserialized;
library_deserialized = library_data;
CHECK(library_deserialized.mAnimations.size() == library.mAnimations.size());
for (AnimLibrary::AnimationResourceMap::const_iterator iter =
library.mAnimations.cbegin();
iter != library.mAnimations.cend();
++iter) {
CHECK(
library_deserialized.mAnimations.find(iter->first)
!= library_deserialized.mAnimations.end());
}
}

View File

@ -200,10 +200,12 @@ TEST_CASE_METHOD(
graph_context.m_skeleton = skeleton.get(); graph_context.m_skeleton = skeleton.get();
graph_context.m_animation_map["trans_x"] = { graph_context.m_animation_map["trans_x"] = {
"trans_x", "trans_x",
"",
animation_translate_x.get(), animation_translate_x.get(),
animation_translate_x_sync_track}; animation_translate_x_sync_track};
graph_context.m_animation_map["trans_y"] = { graph_context.m_animation_map["trans_y"] = {
"trans_y", "trans_y",
"",
animation_translate_y.get(), animation_translate_y.get(),
animation_translate_y_sync_track}; animation_translate_y_sync_track};

117
tests/TestAnimData.cc Normal file
View File

@ -0,0 +1,117 @@
//
// Created by martin on 11.04.25.
//
#include "TestAnimData.h"
#include <iostream>
#include "ozz/animation/offline/animation_builder.h"
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/log.h"
namespace TestAnimData {
SingleBoneSkeleton::SingleBoneSkeleton() {
using namespace ozz::animation::offline;
RawSkeleton raw_skeleton;
RawSkeleton::Joint raw_joint;
raw_joint.name = "Bone0";
raw_joint.transform.translation.x = 1.f;
raw_joint.transform.translation.y = 2.f;
raw_joint.transform.translation.z = 3.f;
raw_skeleton.roots.push_back(raw_joint);
SkeletonBuilder skeleton_builder;
skeleton = skeleton_builder(raw_skeleton);
// SingleBoneSkeleton Animations
ozz::animation::offline::RawAnimation raw_animation_translation_x;
raw_animation_translation_x.name = "TranslationX";
RawAnimation::JointTrack bone0_track;
RawAnimation::JointTrack::Translations bone0_translations;
// animation_translate_x
RawAnimation::TranslationKey translation_key;
translation_key.time = 0.f;
translation_key.value = ozz::math::Float3(0.f, 0.f, 0.f);
bone0_translations.push_back(translation_key);
translation_key.time = 1.f;
translation_key.value = ozz::math::Float3(1.f, 0.f, 0.f);
bone0_translations.push_back(translation_key);
bone0_track.translations = bone0_translations;
raw_animation_translation_x.tracks.push_back(bone0_track);
raw_animation_translation_x.duration = 1.f;
if (!raw_animation_translation_x.Validate()) {
std::cerr << "Error: could animation raw data invalid!" << std::endl;
}
AnimationBuilder animation_builder;
animation_translate_x = animation_builder(raw_animation_translation_x);
animation_translate_x_resource.m_animation = animation_translate_x.get();
SaveAnimation("single_bone_translation_z.ozz", animation_translate_x.get());
animation_translate_x_resource.m_name = "single_bone_translation_z";
animation_translate_x_resource.m_filename = "single_bone_translation_z.ozz";
// animation_translate_y
ozz::animation::offline::RawAnimation raw_animation_translation_y;
raw_animation_translation_y.name = "TranslationY";
bone0_translations.clear();
translation_key.time = 0.f;
translation_key.value = ozz::math::Float3(0.f, 0.f, 0.f);
bone0_translations.push_back(translation_key);
translation_key.time = 1.f;
translation_key.value = ozz::math::Float3(0.f, 1.f, 0.f);
bone0_translations.push_back(translation_key);
bone0_track.translations = bone0_translations;
raw_animation_translation_y.tracks.push_back(bone0_track);
raw_animation_translation_y.duration = 1.f;
if (!raw_animation_translation_y.Validate()) {
std::cerr << "Error: could animation raw data invalid!" << std::endl;
}
animation_translate_y = animation_builder(raw_animation_translation_y);
animation_translate_y_resource.m_animation = animation_translate_y.get();
SaveAnimation("single_bone_translation_y.ozz", animation_translate_y.get());
animation_translate_y_resource.m_name = "single_bone_translation_y";
animation_translate_y_resource.m_filename = "single_bone_translation_y.ozz";
}
bool SingleBoneSkeleton::SaveSkeleton(
const char* filename,
ozz::animation::Skeleton* skeleton) {
assert(false);
return false;
}
bool SingleBoneSkeleton::SaveAnimation(
const char* filename,
ozz::animation::Animation* animation) {
ozz::io::File file(filename, "wb");
if (!file.opened()) {
ozz::log::Err() << "Failed to create animation file " << filename << "."
<< std::endl;
delete animation;
return false;
}
ozz::io::OArchive archive(&file);
archive << *animation;
return true;
}
} // namespace TestAnimData

37
tests/TestAnimData.h Normal file
View File

@ -0,0 +1,37 @@
//
// Created by martin on 11.04.25.
//
#ifndef TESTANIMDATA_H
#define TESTANIMDATA_H
#include "AnimGraph/AnimGraphData.h"
#include "AnimGraph/SyncTrack.h"
#include "ozz/animation/offline/skeleton_builder.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/skeleton.h"
namespace TestAnimData {
struct SingleBoneSkeleton {
SingleBoneSkeleton();
ozz::unique_ptr<ozz::animation::Skeleton> skeleton = nullptr;
ozz::unique_ptr<ozz::animation::Animation> animation_translate_x = nullptr;
AnimationResource animation_translate_x_resource;
SyncTrack animation_translate_x_sync_track = {};
ozz::unique_ptr<ozz::animation::Animation> animation_translate_y = nullptr;
AnimationResource animation_translate_y_resource;
SyncTrack animation_translate_y_sync_track = {};
bool SaveSkeleton(const char* filename, ozz::animation::Skeleton* skeleton);
bool SaveAnimation(
const char* filename,
ozz::animation::Animation* animation);
};
} // namespace TestAnimData
#endif //TESTANIMDATA_H