diff --git a/blendalot_animation_node.cpp b/blendalot_animation_node.cpp index f3751c0..2a3c60b 100644 --- a/blendalot_animation_node.cpp +++ b/blendalot_animation_node.cpp @@ -385,6 +385,10 @@ StringName BLTAnimationNodeSampler::get_animation() const { return animation_name; } +AnimationPlayer *BLTAnimationNodeSampler::get_animation_player() const { + return animation_player; +} + TypedArray BLTAnimationNodeSampler::get_animations_as_typed_array() const { TypedArray typed_arr; @@ -417,6 +421,7 @@ TypedArray BLTAnimationNodeSampler::get_animations_as_typed_array() void BLTAnimationNodeSampler::_bind_methods() { 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_player"), &BLTAnimationNodeSampler::get_animation_player); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation"); diff --git a/blendalot_math_helper.h b/blendalot_math_helper.h new file mode 100644 index 0000000..68c1de0 --- /dev/null +++ b/blendalot_math_helper.h @@ -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 diff --git a/sync_track.h b/sync_track.h index 7a1f3f2..2cdb2e0 100644 --- a/sync_track.h +++ b/sync_track.h @@ -2,6 +2,7 @@ #include "core/templates/local_vector.h" +#include "blendalot_math_helper.h" #include #include @@ -21,7 +22,7 @@ * duration. Blended SyncTracks always have their first interval start at t = 0.0s. */ struct SyncTrack { - static constexpr int cSyncTrackMaxIntervals = 8; + static constexpr int cSyncTrackMaxIntervals = 32; SyncTrack() : duration(0.f), num_intervals(1) { @@ -59,6 +60,12 @@ struct SyncTrack { } 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); int interval = int(sync_time - interval_ratio); @@ -126,19 +133,32 @@ struct SyncTrack { */ static SyncTrack 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; - result.num_intervals = track_A.num_intervals; - result.duration = - (1.0f - weight) * track_A.duration + weight * track_B.duration; + 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; + } + assert(result.num_intervals < cSyncTrackMaxIntervals); + float track_A_repeats = static_cast(result.num_intervals / track_A.num_intervals); + float track_B_repeats = static_cast(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; for (int i = 0; i < result.num_intervals; i++) { - float interval_duration_A = track_A.interval_duration_ratio[i]; - float interval_duration_B = track_B.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 % track_B.num_intervals] / track_B_repeats; result.interval_duration_ratio[i] = (1.0f - weight) * interval_duration_A + weight * interval_duration_B; @@ -152,8 +172,6 @@ struct SyncTrack { } } - assert(result.num_intervals < cSyncTrackMaxIntervals); - return result; } }; \ No newline at end of file diff --git a/tests/test_sync_track.h b/tests/test_sync_track.h index 63616b2..4dea640 100644 --- a/tests/test_sync_track.h +++ b/tests/test_sync_track.h @@ -203,4 +203,47 @@ TEST_CASE("[Blendalot][SyncTrack] Sync Track blending") { } } -} //namespace TestSyncedAnimationGraph \ No newline at end of file +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(blended.num_intervals / track_a.num_intervals); + float track_b_repeats = static_cast(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 \ No newline at end of file