From e2959aaed39bb8b1e2b1847be37289cb6aee5282 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Thu, 18 Nov 2021 22:37:41 +0100 Subject: [PATCH] Implemented sync track blending for tracks with more than 2 markers. --- src/SkinnedMesh.h | 86 ++++++++++++++-------- tests/SyncTrackTests.cc | 153 ++++++++++++++++++++++++++-------------- 2 files changed, 158 insertions(+), 81 deletions(-) diff --git a/src/SkinnedMesh.h b/src/SkinnedMesh.h index ddf8442..b117279 100644 --- a/src/SkinnedMesh.h +++ b/src/SkinnedMesh.h @@ -27,8 +27,7 @@ struct SyncTrack { int m_num_intervals; float m_sync_markers[cSyncTrackMaxIntervals]; float m_interval_start[cSyncTrackMaxIntervals]; - float m_interval_end[cSyncTrackMaxIntervals]; - float m_interval_durations[cSyncTrackMaxIntervals]; + float m_interval_ratio[cSyncTrackMaxIntervals]; void CalcIntervals() { int i; @@ -36,20 +35,21 @@ struct SyncTrack { if (m_num_intervals == 0) { m_num_intervals = 1; m_interval_start[0] = 0.f; - m_interval_end[0] = 1.f; + m_interval_ratio[0] = 1.f; } else { for (i = 0; i < m_num_intervals; i++) { + assert (m_sync_markers[i] >= 0.f && m_sync_markers[i] <= 1.0f); + int end_index = i < m_num_intervals - 1 ? i + 1 : 0; m_interval_start[i] = m_sync_markers[i]; - m_interval_end[i] = m_sync_markers[end_index]; + float interval_end = m_sync_markers[end_index]; - if (m_interval_end[i] > m_interval_start[i]) { - m_interval_durations[i] = m_interval_end[i] - m_interval_start[i]; - } else { - m_interval_durations[i] = - m_interval_end[i] + (1. - m_interval_start[i]); + if (interval_end < m_interval_start[i]) { + interval_end += 1.0f; } + + m_interval_ratio[i] = interval_end - m_interval_start[i]; } } } @@ -58,13 +58,12 @@ struct SyncTrack { float sync_time = fmodf(abs_time, m_duration) / m_duration; int interval_index = 0; - while (sync_time >= m_interval_durations[interval_index]) { - sync_time -= m_interval_durations[interval_index]; + while (sync_time >= m_interval_ratio[interval_index]) { + sync_time -= m_interval_ratio[interval_index]; interval_index++; } - return float(interval_index) - + sync_time / m_interval_durations[interval_index]; + return float(interval_index) + sync_time / m_interval_ratio[interval_index]; } float CalcRatioFromSyncTime(float sync_time) { @@ -73,10 +72,29 @@ struct SyncTrack { return fmodf( m_interval_start[interval] - + m_interval_durations[interval] * interval_ratio, + + m_interval_ratio[interval] * interval_ratio, 1.0f); } + bool operator==(const SyncTrack& other) const { + bool result = m_duration == other.m_duration + && m_num_intervals == other.m_num_intervals; + + if (!result) { + return false; + } + + for (int i = 0; i < m_num_intervals; i++) { + if ((fabsf(m_interval_start[i] - other.m_interval_start[i]) > 1.0e-5) + || (fabsf(m_interval_ratio[i] - other.m_interval_ratio[i]) + > 1.0e-5)) { + return false; + } + } + + return true; + } + static SyncTrack Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) { assert(track_A.m_num_intervals == track_B.m_num_intervals); @@ -87,22 +105,34 @@ struct SyncTrack { result.m_duration = (1.0f - weight) * track_A.m_duration + weight * track_B.m_duration; + float interval_0_offset = + track_B.m_interval_start[0] - track_A.m_interval_start[0]; + if (interval_0_offset > 0.5f) { + interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f); + } else if (interval_0_offset < -0.5) { + interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f); + } + + result.m_interval_start[0] = fmodf( + 1.0 + (1.0f - weight) * track_A.m_interval_start[0] + + weight * (track_A.m_interval_start[0] + interval_0_offset), + 1.0f); + result.m_sync_markers[0] = result.m_interval_start[0]; + for (int i = 0; i < result.m_num_intervals; i++) { - result.m_interval_durations[i] = - (1.0f - weight) * track_A.m_interval_durations[i] - + weight * track_B.m_interval_durations[i]; + float interval_duration_A = track_A.m_interval_ratio[i]; + float interval_duration_B = track_B.m_interval_ratio[i]; + result.m_interval_ratio[i] = + (1.0f - weight) * interval_duration_A + weight * interval_duration_B; - result.m_interval_start[i] = - (1.0f - weight) * track_A.m_interval_start[i] - + weight * track_B.m_interval_start[i]; - - result.m_interval_end[i] = - (1.0f - weight) * track_A.m_interval_end[i] - + weight * track_B.m_interval_end[i]; - - result.m_sync_markers[i] = - (1.0f - weight) * track_A.m_sync_markers[i] - + weight * track_B.m_sync_markers[i]; + if (i < cSyncTrackMaxIntervals) { + result.m_interval_start[i + 1] = + result.m_interval_start[i] + result.m_interval_ratio[i]; + if (result.m_interval_start[i + 1] > 1.0f) { + result.m_interval_start[i + 1] = + fmodf(result.m_interval_start[i + 1], 1.0f); + } + } } return result; diff --git a/tests/SyncTrackTests.cc b/tests/SyncTrackTests.cc index e7887e0..ccd3cca 100644 --- a/tests/SyncTrackTests.cc +++ b/tests/SyncTrackTests.cc @@ -10,21 +10,17 @@ TEST_CASE("Basic", "[SyncTrack]") { track_A.m_num_intervals = 2; track_A.m_duration = 2.0; track_A.m_interval_start[0] = 0.f; - track_A.m_interval_end[0] = 0.7f; - track_A.m_interval_durations[0] = 0.7; + track_A.m_interval_ratio[0] = 0.7; track_A.m_interval_start[1] = 0.7f; - track_A.m_interval_end[1] = 1.0f; - track_A.m_interval_durations[1] = 0.3; + track_A.m_interval_ratio[1] = 0.3; SyncTrack track_B; track_B.m_num_intervals = 2; track_B.m_duration = 1.5; track_B.m_interval_start[0] = 0.0f; - track_B.m_interval_end[0] = 0.6f; - track_B.m_interval_durations[0] = 0.6; + track_B.m_interval_ratio[0] = 0.6; track_B.m_interval_start[1] = 0.6f; - track_B.m_interval_end[1] = 1.0f; - track_B.m_interval_durations[1] = 0.4; + track_B.m_interval_ratio[1] = 0.4; WHEN("Calculating sync time of track_B at 0.5 duration") { float sync_time_at_0_75 = @@ -69,48 +65,16 @@ TEST_CASE("Basic", "[SyncTrack]") { WHEN("Blending two synctracks with weight 0.") { SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B); - THEN("Result must equal track_A") { - REQUIRE(blended.m_duration == track_A.m_duration); - REQUIRE( - blended.m_interval_durations[0] == track_A.m_interval_durations[0]); - REQUIRE( - blended.m_interval_durations[1] == track_A.m_interval_durations[1]); - - REQUIRE(blended.m_sync_markers[0] == track_A.m_sync_markers[0]); - REQUIRE(blended.m_sync_markers[1] == track_A.m_sync_markers[1]); - - REQUIRE(blended.m_interval_start[0] == track_A.m_interval_start[0]); - REQUIRE(blended.m_interval_start[1] == track_A.m_interval_start[1]); - - REQUIRE(blended.m_interval_end[0] == track_A.m_interval_end[0]); - REQUIRE(blended.m_interval_end[1] == track_A.m_interval_end[1]); - } + THEN("Result must equal track_A") { REQUIRE(track_A == blended); } } WHEN("Blending two synctracks with weight 1.") { SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B); - THEN("Result must equal track_B") { - REQUIRE(blended.m_duration == track_B.m_duration); - REQUIRE( - blended.m_interval_durations[0] == track_B.m_interval_durations[0]); - REQUIRE( - blended.m_interval_durations[1] == track_B.m_interval_durations[1]); - - REQUIRE(blended.m_sync_markers[0] == track_B.m_sync_markers[0]); - REQUIRE(blended.m_sync_markers[1] == track_B.m_sync_markers[1]); - - REQUIRE(blended.m_interval_start[0] == track_B.m_interval_start[0]); - REQUIRE(blended.m_interval_start[1] == track_B.m_interval_start[1]); - - REQUIRE(blended.m_interval_end[0] == track_B.m_interval_end[0]); - REQUIRE(blended.m_interval_end[1] == track_B.m_interval_end[1]); - } + THEN("Result must equal track_B") { REQUIRE(track_B == blended); } } } - - TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") { SyncTrack track_A; track_A.m_num_intervals = 2; @@ -121,13 +85,11 @@ TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") { WHEN("Calculating intervals") { track_A.CalcIntervals(); - CHECK(track_A.m_interval_start[0] == 0.9f); - CHECK(track_A.m_interval_end[0] == 0.2f); - CHECK(track_A.m_interval_durations[0] == 0.3f); + CHECK(track_A.m_interval_start[0] == Catch::Detail::Approx(0.9f)); + CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3f)); - CHECK(track_A.m_interval_start[1] == 0.2f); - CHECK(track_A.m_interval_end[1] == 0.9f); - CHECK(track_A.m_interval_durations[1] == 0.7f); + CHECK(track_A.m_interval_start[1] == Catch::Detail::Approx(0.2f)); + CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.7f)); WHEN("Querying ratio at sync time at 1.001") { float ratio = track_A.CalcRatioFromSyncTime(1.0001f); @@ -145,11 +107,96 @@ TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") { } } - WHEN ("Blending with another sync track") { + WHEN("Blending sync track with 3 events") { + track_A.m_num_intervals = 3; + track_A.m_duration = 2.0; + track_A.m_sync_markers[0] = 0.; + track_A.m_sync_markers[1] = 0.3; + track_A.m_sync_markers[2] = 0.9; + track_A.CalcIntervals(); + SyncTrack track_B; - track_B.m_num_intervals = 2; - track_B.m_duration = 1.0; - track_B.m_sync_markers[0] = 0.9; - track_B.m_sync_markers[1] = 0.2; + track_B.m_num_intervals = 3; + track_B.m_duration = 1.5; + track_B.m_sync_markers[0] = 0.7; + track_B.m_sync_markers[1] = 0.9; + track_B.m_sync_markers[2] = 0.2; + track_B.CalcIntervals(); + + WHEN("Calculating A's durations") { + CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3)); + CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.6)); + CHECK(track_A.m_interval_ratio[2] == Catch::Detail::Approx(0.1)); + } + + WHEN("Calculating B's durations") { + CHECK(track_B.m_interval_ratio[0] == Catch::Detail::Approx(0.2)); + CHECK(track_B.m_interval_ratio[1] == Catch::Detail::Approx(0.3)); + CHECK(track_B.m_interval_ratio[2] == Catch::Detail::Approx(0.5)); + } + + WHEN("Blending two synctracks with weight 0.") { + SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B); + + THEN("Result must equal track_A") { REQUIRE(track_A == blended); } + } + + WHEN("Blending two synctracks with weight 1.") { + SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B); + + THEN("Result must equal track_B") { REQUIRE(track_B == blended); } + } + + WHEN("Blending with weight 0.2") { + float weight = 0.2f; + SyncTrack blended = SyncTrack::Blend(weight, track_A, track_B); + + REQUIRE( + blended.m_duration + == (1.0f - weight) * track_A.m_duration + + weight * track_B.m_duration); + REQUIRE( + blended.m_interval_start[0] + == fmodf( + (1.0f - weight) * (track_A.m_interval_start[0] + 1.0f) + + weight * (track_B.m_interval_start[0]), + 1.0f)); + REQUIRE( + blended.m_interval_ratio[1] + == (1.0f - weight) * (track_A.m_interval_ratio[1]) + + weight * (track_B.m_interval_ratio[1]) + ); + REQUIRE( + blended.m_interval_ratio[2] + == (1.0f - weight) * (track_A.m_interval_ratio[2]) + + weight * (track_B.m_interval_ratio[2]) + ); + } + + WHEN("Inverted blending with weight 0.2") { + float weight = 0.2f; + SyncTrack blended = SyncTrack::Blend(weight, track_B, track_A); + + REQUIRE( + blended.m_duration + == (1.0f - weight) * track_B.m_duration + + weight * track_A.m_duration); + REQUIRE( + blended.m_interval_start[0] + == fmodf( + (1.0f - weight) * (track_B.m_interval_start[0]) + + weight * (track_A.m_interval_start[0] + 1.0f), + 1.0f)); + REQUIRE( + blended.m_interval_ratio[1] + == (1.0f - weight) * (track_B.m_interval_ratio[1]) + + weight * (track_A.m_interval_ratio[1]) + ); + REQUIRE( + blended.m_interval_ratio[2] + == (1.0f - weight) * (track_B.m_interval_ratio[2]) + + weight * (track_A.m_interval_ratio[2]) + ); + } } } \ No newline at end of file