WIP: Blend2 node and blending of AnimationData.

This commit is contained in:
Martin Felis 2025-12-22 00:37:27 +01:00
parent f4eea6d2d4
commit 56fde580c3
3 changed files with 171 additions and 17 deletions

View File

@ -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<AnimationData*>& inputs, AnimationData &output) {
assert(inputs.size() == 0);
void AnimationData::sample_from_animation(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d, double p_time) {
const Vector<Animation::Track *> 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<AnimationData *> &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<AnimationData *> &inputs, AnimationData &output) {
}

View File

@ -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<const PositionTrackValue &>(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<const PositionTrackValue &>(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<const RotationTrackValue &>(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<const RotationTrackValue &>(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<Animation::TypeHash, TrackValue *> &K : other.track_values) {
track_values.insert(K.key, K.value->clone());
}
}
AnimationData(AnimationData &&other) noexcept :
track_values(std::exchange(other.track_values, AHashMap<Animation::TypeHash, TrackValue *, HashHasher>())) {
}
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<Animation::TypeHash> valid_track_hashes;
for (const KeyValue<Animation::TypeHash, TrackValue *> &K : track_values) {
valid_track_hashes.insert(K.key);
}
for (const KeyValue<Animation::TypeHash, TrackValue *> &K : other.track_values) {
if (HashSet<Animation::TypeHash>::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<Animation::TypeHash, TrackValue *> &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> &animation, const Skeleton3D *skeleton_3d, double p_time);
AHashMap<Animation::TypeHash, TrackValue *, HashHasher> 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<StringName> &inputs) const override {
inputs.push_back("Input0");
inputs.push_back("Input1");
}
void evaluate(GraphEvaluationContext &context, const Vector<AnimationData *> &inputs, AnimationData &output) override;
};
struct BlendTreeConnection {

View File

@ -54,7 +54,7 @@ struct SyncedAnimationGraphFixture {
namespace TestSyncedAnimationGraph {
TEST_CASE("[SyncedAnimationGraph] TestBlendTreeConstruction") {
TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
BlendTreeBuilder tree_constructor;
Ref<AnimationSamplerNode> 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<Animation::TypeHash, AnimationData::TrackValue *> &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<Animation::TypeHash, AnimationData::TrackValue *> &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<AnimationSamplerNode> animation_sampler_node;
animation_sampler_node.instantiate();