From 56fde580c30b9326ff5816058aadb3c0e5ed7a8e Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Mon, 22 Dec 2025 00:37:27 +0100 Subject: [PATCH] WIP: Blend2 node and blending of AnimationData. --- synced_animation_node.cpp | 38 +++++---- synced_animation_node.h | 120 +++++++++++++++++++++++++++- tests/test_synced_animation_graph.h | 30 ++++++- 3 files changed, 171 insertions(+), 17 deletions(-) diff --git a/synced_animation_node.cpp b/synced_animation_node.cpp index 44271e0..4cd6bbd 100644 --- a/synced_animation_node.cpp +++ b/synced_animation_node.cpp @@ -4,15 +4,7 @@ #include "synced_animation_node.h" -void AnimationSamplerNode::initialize(GraphEvaluationContext &context) { - animation = context.animation_player->get_animation(animation_name); - node_time_info.length = animation->get_length(); - node_time_info.loop_mode = Animation::LOOP_LINEAR; -} - -void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const Vector& inputs, AnimationData &output) { - assert(inputs.size() == 0); - +void AnimationData::sample_from_animation(const Ref &animation, const Skeleton3D *skeleton_3d, double p_time) { const Vector tracks = animation->get_tracks(); Animation::Track *const *tracks_ptr = tracks.ptr(); @@ -20,7 +12,7 @@ void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const Vecto for (int i = 0; i < count; i++) { AnimationData::TrackValue *track_value = nullptr; const Animation::Track *animation_track = tracks_ptr[i]; - const NodePath& track_node_path = animation_track->path; + const NodePath &track_node_path = animation_track->path; if (!animation_track->enabled) { continue; } @@ -31,11 +23,11 @@ void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const Vecto AnimationData::PositionTrackValue *position_track_value = memnew(AnimationData::PositionTrackValue); if (track_node_path.get_subname_count() == 1) { - int bone_idx = context.skeleton_3d->find_bone(track_node_path.get_subname(0)); + int bone_idx = skeleton_3d->find_bone(track_node_path.get_subname(0)); if (bone_idx != -1) { position_track_value->bone_idx = bone_idx; } - animation->try_position_track_interpolate(i, node_time_info.position, &position_track_value->position); + animation->try_position_track_interpolate(i, p_time, &position_track_value->position); } else { // TODO assert(false && !"Not yet implemented"); @@ -48,11 +40,11 @@ void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const Vecto AnimationData::RotationTrackValue *rotation_track_value = memnew(AnimationData::RotationTrackValue); if (track_node_path.get_subname_count() == 1) { - int bone_idx = context.skeleton_3d->find_bone(track_node_path.get_subname(0)); + int bone_idx = skeleton_3d->find_bone(track_node_path.get_subname(0)); if (bone_idx != -1) { rotation_track_value->bone_idx = bone_idx; } - animation->try_rotation_track_interpolate(i, node_time_info.position, &rotation_track_value->rotation); + animation->try_rotation_track_interpolate(i, p_time, &rotation_track_value->rotation); } else { // TODO assert(false && !"Not yet implemented"); @@ -69,6 +61,22 @@ void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const Vecto } track_value->track = tracks_ptr[i]; - output.set_value(animation_track->thash, track_value); + set_value(animation_track->thash, track_value); } +} + +void AnimationSamplerNode::initialize(GraphEvaluationContext &context) { + animation = context.animation_player->get_animation(animation_name); + node_time_info.length = animation->get_length(); + node_time_info.loop_mode = Animation::LOOP_LINEAR; +} + +void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const Vector &inputs, AnimationData &output) { + assert(inputs.size() == 0); + + output.clear(); + output.sample_from_animation(animation, context.skeleton_3d, node_time_info.position); +} + +void AnimationBlend2Node::evaluate(GraphEvaluationContext &context, const Vector &inputs, AnimationData &output) { } \ No newline at end of file diff --git a/synced_animation_node.h b/synced_animation_node.h index 5979e12..ceaf820 100644 --- a/synced_animation_node.h +++ b/synced_animation_node.h @@ -29,18 +29,81 @@ struct AnimationData { struct TrackValue { Animation::Track *track = nullptr; TrackType type = TYPE_ANIMATION; + + virtual ~TrackValue() = default; + + virtual void blend(const TrackValue &to_value, const float lambda) { + print_error(vformat("Blending of TrackValue of type %d with TrackValue of type %d not yet implemented.", type, to_value.type)); + } + + virtual bool operator==(const TrackValue &other_value) const { + print_error(vformat("Comparing TrackValue of type %d with TrackValue of type %d not yet implemented.", type, other_value.type)); + return false; + } + bool operator!=(const TrackValue &other_value) const { + return !(*this == other_value); + } + + virtual TrackValue *clone() const { + print_error(vformat("Cannot clone TrackValue of type %d: not yet implemented.", type)); + return nullptr; + } }; struct PositionTrackValue : public TrackValue { int bone_idx = -1; Vector3 position = Vector3(0, 0, 0); PositionTrackValue() { type = TYPE_POSITION_3D; } + + void blend(const TrackValue &to_value, const float lambda) override { + const PositionTrackValue *to_value_casted = &static_cast(to_value); + assert(bone_idx == to_value_casted->bone_idx); + position = (1. - lambda) * position + lambda * to_value_casted->position; + } + + bool operator==(const TrackValue &other_value) const override { + if (type != other_value.type) { + return false; + } + + const PositionTrackValue *other_value_casted = &static_cast(other_value); + return bone_idx == other_value_casted->bone_idx && position == other_value_casted->position; + } + + TrackValue *clone() const override { + PositionTrackValue *result = memnew(PositionTrackValue); + result->bone_idx = bone_idx; + result->position = position; + return result; + } }; struct RotationTrackValue : public TrackValue { int bone_idx = -1; Quaternion rotation = Quaternion(0, 0, 0, 1); RotationTrackValue() { type = TYPE_ROTATION_3D; } + + void blend(const TrackValue &to_value, const float lambda) override { + const RotationTrackValue *to_value_casted = &static_cast(to_value); + assert(bone_idx == to_value_casted->bone_idx); + rotation = rotation.slerp(to_value_casted->rotation, lambda); + } + + bool operator==(const TrackValue &other_value) const override { + if (type != other_value.type) { + return false; + } + + const RotationTrackValue *other_value_casted = &static_cast(other_value); + return bone_idx == other_value_casted->bone_idx && rotation == other_value_casted->rotation; + } + + TrackValue *clone() const override { + RotationTrackValue *result = memnew(RotationTrackValue); + result->bone_idx = bone_idx; + result->rotation = rotation; + return result; + } }; struct ScaleTrackValue : public TrackValue { @@ -53,8 +116,26 @@ struct AnimationData { ~AnimationData() { _clear_values(); } + AnimationData(const AnimationData &other) { + for (const KeyValue &K : other.track_values) { + track_values.insert(K.key, K.value->clone()); + } + } + AnimationData(AnimationData &&other) noexcept : + track_values(std::exchange(other.track_values, AHashMap())) { + } + AnimationData &operator=(const AnimationData &other) { + AnimationData temp(other); + std::swap(track_values, temp.track_values); + return *this; + } + AnimationData &operator=(AnimationData &&other) noexcept { + std::swap(track_values, other.track_values); + return *this; + } - void set_value(Animation::TypeHash thash, TrackValue *value) { + void + set_value(const Animation::TypeHash& thash, TrackValue *value) { if (!track_values.has(thash)) { track_values.insert(thash, value); } else { @@ -66,6 +147,39 @@ struct AnimationData { _clear_values(); } + bool has_same_tracks(const AnimationData &other) const { + HashSet valid_track_hashes; + for (const KeyValue &K : track_values) { + valid_track_hashes.insert(K.key); + } + + for (const KeyValue &K : other.track_values) { + if (HashSet::Iterator entry = valid_track_hashes.find(K.key)) { + valid_track_hashes.remove(entry); + } else { + return false; + } + } + + return valid_track_hashes.size() == 0; + } + + void blend(const AnimationData &to_data, const float lambda) { + if (!has_same_tracks(to_data)) { + print_error("Cannot blend AnimationData: tracks do not match."); + return; + } + + for (const KeyValue &K : track_values) { + TrackValue *track_value = K.value; + TrackValue *other_track_value = to_data.track_values[K.key]; + + track_value->blend(*other_track_value, lambda); + } + } + + void sample_from_animation(const Ref &animation, const Skeleton3D *skeleton_3d, double p_time); + AHashMap track_values; // Animation::Track to TrackValue protected: @@ -192,10 +306,14 @@ public: class AnimationBlend2Node : public SyncedAnimationNode { public: + float blend_weight = 0.0f; + void get_input_names(Vector &inputs) const override { inputs.push_back("Input0"); inputs.push_back("Input1"); } + + void evaluate(GraphEvaluationContext &context, const Vector &inputs, AnimationData &output) override; }; struct BlendTreeConnection { diff --git a/tests/test_synced_animation_graph.h b/tests/test_synced_animation_graph.h index 6278626..91a07ed 100644 --- a/tests/test_synced_animation_graph.h +++ b/tests/test_synced_animation_graph.h @@ -54,7 +54,7 @@ struct SyncedAnimationGraphFixture { namespace TestSyncedAnimationGraph { -TEST_CASE("[SyncedAnimationGraph] TestBlendTreeConstruction") { +TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") { BlendTreeBuilder tree_constructor; Ref animation_sampler_node0; @@ -133,6 +133,34 @@ TEST_CASE("[SyncedAnimationGraph] TestBlendTreeConstruction") { } } +TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] Test AnimationData blending") { + AnimationData data_t0; + data_t0.sample_from_animation(test_animation, skeleton_node, 0.0); + + AnimationData data_t1; + data_t1.sample_from_animation(test_animation, skeleton_node, 1.0); + + AnimationData data_t0_5; + data_t0_5.sample_from_animation(test_animation, skeleton_node, 0.5); + + AnimationData data_blended = data_t0; + data_blended.blend(data_t1, 0.5); + + REQUIRE(data_blended.has_same_tracks(data_t0_5)); + for (const KeyValue &K : data_blended.track_values) { + CHECK(K.value->operator==(*data_t0_5.track_values.find(K.key)->value)); + } + + // And also check that values do not match + data_blended = data_t0; + data_blended.blend(data_t1, 0.3); + + REQUIRE(data_blended.has_same_tracks(data_t0_5)); + for (const KeyValue &K : data_blended.track_values) { + CHECK(K.value->operator!=(*data_t0_5.track_values.find(K.key)->value)); + } +} + TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] SyncedAnimationGraph with an AnimationSampler as root node") { Ref animation_sampler_node; animation_sampler_node.instantiate();