feature/blend_tree_editor #1

Open
martin wants to merge 29 commits from feature/blend_tree_editor into main
4 changed files with 98 additions and 10 deletions
Showing only changes of commit 3dd1ce42df - Show all commits

View File

@ -385,6 +385,10 @@ StringName BLTAnimationNodeSampler::get_animation() const {
return animation_name; return animation_name;
} }
AnimationPlayer *BLTAnimationNodeSampler::get_animation_player() const {
return animation_player;
}
TypedArray<StringName> BLTAnimationNodeSampler::get_animations_as_typed_array() const { TypedArray<StringName> BLTAnimationNodeSampler::get_animations_as_typed_array() const {
TypedArray<StringName> typed_arr; TypedArray<StringName> typed_arr;
@ -417,6 +421,7 @@ TypedArray<StringName> BLTAnimationNodeSampler::get_animations_as_typed_array()
void BLTAnimationNodeSampler::_bind_methods() { void BLTAnimationNodeSampler::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_animation", "name"), &BLTAnimationNodeSampler::set_animation); ClassDB::bind_method(D_METHOD("set_animation", "name"), &BLTAnimationNodeSampler::set_animation);
ClassDB::bind_method(D_METHOD("get_animation"), &BLTAnimationNodeSampler::get_animation); ClassDB::bind_method(D_METHOD("get_animation"), &BLTAnimationNodeSampler::get_animation);
ClassDB::bind_method(D_METHOD("get_animation_player"), &BLTAnimationNodeSampler::get_animation_player);
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation");

22
blendalot_math_helper.h Normal file
View File

@ -0,0 +1,22 @@
//
// Created by martin on 20.02.26.
//
#ifndef MASTER_BLENDALOT_MATH_HELPER_H
#define MASTER_BLENDALOT_MATH_HELPER_H
inline int greatest_common_divisor(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
inline int least_common_multiple(int a, int b) {
return (a / greatest_common_divisor(a, b)) * b;
}
#endif //MASTER_BLENDALOT_MATH_HELPER_H

View File

@ -2,6 +2,7 @@
#include "core/templates/local_vector.h" #include "core/templates/local_vector.h"
#include "blendalot_math_helper.h"
#include <cassert> #include <cassert>
#include <cmath> #include <cmath>
@ -21,7 +22,7 @@
* duration. Blended SyncTracks always have their first interval start at t = 0.0s. * duration. Blended SyncTracks always have their first interval start at t = 0.0s.
*/ */
struct SyncTrack { struct SyncTrack {
static constexpr int cSyncTrackMaxIntervals = 8; static constexpr int cSyncTrackMaxIntervals = 32;
SyncTrack() : SyncTrack() :
duration(0.f), num_intervals(1) { duration(0.f), num_intervals(1) {
@ -59,6 +60,12 @@ struct SyncTrack {
} }
double calc_ratio_from_sync_time(double sync_time) const { double calc_ratio_from_sync_time(double sync_time) const {
// When blending SyncTracks with differing numbers of intervals the resulting SyncTrack may have
// additional repeats of the animation (=> "virtual sync periods", https://youtu.be/Jkv0pbp0ckQ?t=8178).
//
// Therefore, we first have to transform it back to the numbers of intervals we actually have.
sync_time = fmod(sync_time, num_intervals);
float interval_ratio = fmod(sync_time, 1.0f); float interval_ratio = fmod(sync_time, 1.0f);
int interval = int(sync_time - interval_ratio); int interval = int(sync_time - interval_ratio);
@ -126,19 +133,32 @@ struct SyncTrack {
*/ */
static SyncTrack static SyncTrack
blend(float weight, const SyncTrack &track_A, const SyncTrack &track_B) { blend(float weight, const SyncTrack &track_A, const SyncTrack &track_B) {
assert(track_A.num_intervals == track_B.num_intervals); if (Math::is_zero_approx(weight)) {
return track_A;
}
if (Math::is_zero_approx(1.0 - weight)) {
return track_B;
}
SyncTrack result; SyncTrack result;
if (track_A.num_intervals != track_B.num_intervals) {
result.num_intervals = least_common_multiple(track_A.num_intervals, track_B.num_intervals);
} else {
result.num_intervals = track_A.num_intervals; result.num_intervals = track_A.num_intervals;
}
assert(result.num_intervals < cSyncTrackMaxIntervals);
result.duration = float track_A_repeats = static_cast<float>(result.num_intervals / track_A.num_intervals);
(1.0f - weight) * track_A.duration + weight * track_B.duration; float track_B_repeats = static_cast<float>(result.num_intervals / track_B.num_intervals);
result.duration = (1.0f - weight) * (track_A.duration * track_A_repeats) + weight * (track_B.duration * track_B_repeats);
result.interval_start_ratio[0] = 0.f; result.interval_start_ratio[0] = 0.f;
for (int i = 0; i < result.num_intervals; i++) { for (int i = 0; i < result.num_intervals; i++) {
float interval_duration_A = track_A.interval_duration_ratio[i]; float interval_duration_A = track_A.interval_duration_ratio[i % track_A.num_intervals] / track_A_repeats;
float interval_duration_B = track_B.interval_duration_ratio[i]; float interval_duration_B = track_B.interval_duration_ratio[i % track_B.num_intervals] / track_B_repeats;
result.interval_duration_ratio[i] = result.interval_duration_ratio[i] =
(1.0f - weight) * interval_duration_A + weight * interval_duration_B; (1.0f - weight) * interval_duration_A + weight * interval_duration_B;
@ -152,8 +172,6 @@ struct SyncTrack {
} }
} }
assert(result.num_intervals < cSyncTrackMaxIntervals);
return result; return result;
} }
}; };

View File

@ -203,4 +203,47 @@ TEST_CASE("[Blendalot][SyncTrack] Sync Track blending") {
} }
} }
} //namespace TestSyncedAnimationGraph TEST_CASE("[Blendalot][SyncTrack] Sync Track blending non-matching interval count") {
SyncTrack track_a = SyncTrack::create_from_markers(2.0, { 0., 0.6, 1.8 });
SyncTrack track_b = SyncTrack::create_from_markers(1.5f, { 1.05 });
WHEN("Blending two synctracks with weight 0.") {
SyncTrack blended = SyncTrack::blend(0.f, track_a, track_b);
blended.duration = track_a.duration;
blended.interval_start_ratio[0] = 0.0;
for (int i = 0; i < track_a.num_intervals; i++) {
CHECK(blended.interval_duration_ratio[i] == track_a.interval_duration_ratio[i]);
}
}
WHEN("Blending two synctracks with weight 1.") {
SyncTrack blended = SyncTrack::blend(1.f, track_a, track_b);
blended.duration = track_b.duration;
blended.interval_start_ratio[0] = 0.0;
for (int i = 0; i < track_b.num_intervals; i++) {
CHECK(blended.interval_duration_ratio[i] == track_b.interval_duration_ratio[i]);
}
}
WHEN("Blending with weight 0.2") {
float weight = 0.2f;
SyncTrack blended = SyncTrack::blend(weight, track_a, track_b);
float track_a_repeats = static_cast<float>(blended.num_intervals / track_a.num_intervals);
float track_b_repeats = static_cast<float>(blended.num_intervals / track_b.num_intervals);
CHECK(
blended.duration == doctest::Approx(2.5));
CHECK(
blended.interval_start_ratio[0] == 0.0);
CHECK(
blended.interval_duration_ratio[0] == doctest::Approx((1.0 - weight) * track_a.interval_duration_ratio[0] / track_a_repeats + weight * track_b.interval_duration_ratio[0] / track_b_repeats));
CHECK(
blended.interval_duration_ratio[1] == doctest::Approx((1.0 - weight) * track_a.interval_duration_ratio[1] / track_a_repeats + weight * track_b.interval_duration_ratio[0] / track_b_repeats));
CHECK(
blended.interval_duration_ratio[2] == doctest::Approx((1.0 - weight) * track_a.interval_duration_ratio[2] / track_a_repeats + weight * track_b.interval_duration_ratio[0] / track_b_repeats));
}
}
} //namespace TestBlendalotAnimationGraph