Implemented sync track blending for tracks with more than 2 markers.

AnimGraphEditor
Martin Felis 2021-11-18 22:37:41 +01:00
parent 554125dde2
commit e2959aaed3
2 changed files with 158 additions and 81 deletions

View File

@ -27,8 +27,7 @@ struct SyncTrack {
int m_num_intervals; int m_num_intervals;
float m_sync_markers[cSyncTrackMaxIntervals]; float m_sync_markers[cSyncTrackMaxIntervals];
float m_interval_start[cSyncTrackMaxIntervals]; float m_interval_start[cSyncTrackMaxIntervals];
float m_interval_end[cSyncTrackMaxIntervals]; float m_interval_ratio[cSyncTrackMaxIntervals];
float m_interval_durations[cSyncTrackMaxIntervals];
void CalcIntervals() { void CalcIntervals() {
int i; int i;
@ -36,20 +35,21 @@ struct SyncTrack {
if (m_num_intervals == 0) { if (m_num_intervals == 0) {
m_num_intervals = 1; m_num_intervals = 1;
m_interval_start[0] = 0.f; m_interval_start[0] = 0.f;
m_interval_end[0] = 1.f; m_interval_ratio[0] = 1.f;
} else { } else {
for (i = 0; i < m_num_intervals; i++) { 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; int end_index = i < m_num_intervals - 1 ? i + 1 : 0;
m_interval_start[i] = m_sync_markers[i]; 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]) { if (interval_end < m_interval_start[i]) {
m_interval_durations[i] = m_interval_end[i] - m_interval_start[i]; interval_end += 1.0f;
} else {
m_interval_durations[i] =
m_interval_end[i] + (1. - m_interval_start[i]);
} }
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; float sync_time = fmodf(abs_time, m_duration) / m_duration;
int interval_index = 0; int interval_index = 0;
while (sync_time >= m_interval_durations[interval_index]) { while (sync_time >= m_interval_ratio[interval_index]) {
sync_time -= m_interval_durations[interval_index]; sync_time -= m_interval_ratio[interval_index];
interval_index++; interval_index++;
} }
return float(interval_index) return float(interval_index) + sync_time / m_interval_ratio[interval_index];
+ sync_time / m_interval_durations[interval_index];
} }
float CalcRatioFromSyncTime(float sync_time) { float CalcRatioFromSyncTime(float sync_time) {
@ -73,10 +72,29 @@ struct SyncTrack {
return fmodf( return fmodf(
m_interval_start[interval] m_interval_start[interval]
+ m_interval_durations[interval] * interval_ratio, + m_interval_ratio[interval] * interval_ratio,
1.0f); 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 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.m_num_intervals == track_B.m_num_intervals); assert(track_A.m_num_intervals == track_B.m_num_intervals);
@ -87,22 +105,34 @@ struct SyncTrack {
result.m_duration = result.m_duration =
(1.0f - weight) * track_A.m_duration + weight * track_B.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++) { for (int i = 0; i < result.m_num_intervals; i++) {
result.m_interval_durations[i] = float interval_duration_A = track_A.m_interval_ratio[i];
(1.0f - weight) * track_A.m_interval_durations[i] float interval_duration_B = track_B.m_interval_ratio[i];
+ weight * track_B.m_interval_durations[i]; result.m_interval_ratio[i] =
(1.0f - weight) * interval_duration_A + weight * interval_duration_B;
result.m_interval_start[i] = if (i < cSyncTrackMaxIntervals) {
(1.0f - weight) * track_A.m_interval_start[i] result.m_interval_start[i + 1] =
+ weight * track_B.m_interval_start[i]; result.m_interval_start[i] + result.m_interval_ratio[i];
if (result.m_interval_start[i + 1] > 1.0f) {
result.m_interval_end[i] = result.m_interval_start[i + 1] =
(1.0f - weight) * track_A.m_interval_end[i] fmodf(result.m_interval_start[i + 1], 1.0f);
+ 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];
} }
return result; return result;

View File

@ -10,21 +10,17 @@ TEST_CASE("Basic", "[SyncTrack]") {
track_A.m_num_intervals = 2; track_A.m_num_intervals = 2;
track_A.m_duration = 2.0; track_A.m_duration = 2.0;
track_A.m_interval_start[0] = 0.f; track_A.m_interval_start[0] = 0.f;
track_A.m_interval_end[0] = 0.7f; track_A.m_interval_ratio[0] = 0.7;
track_A.m_interval_durations[0] = 0.7;
track_A.m_interval_start[1] = 0.7f; track_A.m_interval_start[1] = 0.7f;
track_A.m_interval_end[1] = 1.0f; track_A.m_interval_ratio[1] = 0.3;
track_A.m_interval_durations[1] = 0.3;
SyncTrack track_B; SyncTrack track_B;
track_B.m_num_intervals = 2; track_B.m_num_intervals = 2;
track_B.m_duration = 1.5; track_B.m_duration = 1.5;
track_B.m_interval_start[0] = 0.0f; track_B.m_interval_start[0] = 0.0f;
track_B.m_interval_end[0] = 0.6f; track_B.m_interval_ratio[0] = 0.6;
track_B.m_interval_durations[0] = 0.6;
track_B.m_interval_start[1] = 0.6f; track_B.m_interval_start[1] = 0.6f;
track_B.m_interval_end[1] = 1.0f; track_B.m_interval_ratio[1] = 0.4;
track_B.m_interval_durations[1] = 0.4;
WHEN("Calculating sync time of track_B at 0.5 duration") { WHEN("Calculating sync time of track_B at 0.5 duration") {
float sync_time_at_0_75 = float sync_time_at_0_75 =
@ -69,47 +65,15 @@ TEST_CASE("Basic", "[SyncTrack]") {
WHEN("Blending two synctracks with weight 0.") { WHEN("Blending two synctracks with weight 0.") {
SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B); SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B);
THEN("Result must equal track_A") { THEN("Result must equal track_A") { REQUIRE(track_A == blended); }
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]);
}
} }
WHEN("Blending two synctracks with weight 1.") { WHEN("Blending two synctracks with weight 1.") {
SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B); SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B);
THEN("Result must equal track_B") { THEN("Result must equal track_B") { REQUIRE(track_B == blended); }
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]);
} }
} }
}
TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") { TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") {
SyncTrack track_A; SyncTrack track_A;
@ -121,13 +85,11 @@ TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") {
WHEN("Calculating intervals") { WHEN("Calculating intervals") {
track_A.CalcIntervals(); track_A.CalcIntervals();
CHECK(track_A.m_interval_start[0] == 0.9f); CHECK(track_A.m_interval_start[0] == Catch::Detail::Approx(0.9f));
CHECK(track_A.m_interval_end[0] == 0.2f); CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3f));
CHECK(track_A.m_interval_durations[0] == 0.3f);
CHECK(track_A.m_interval_start[1] == 0.2f); CHECK(track_A.m_interval_start[1] == Catch::Detail::Approx(0.2f));
CHECK(track_A.m_interval_end[1] == 0.9f); CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.7f));
CHECK(track_A.m_interval_durations[1] == 0.7f);
WHEN("Querying ratio at sync time at 1.001") { WHEN("Querying ratio at sync time at 1.001") {
float ratio = track_A.CalcRatioFromSyncTime(1.0001f); 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; SyncTrack track_B;
track_B.m_num_intervals = 2; track_B.m_num_intervals = 3;
track_B.m_duration = 1.0; track_B.m_duration = 1.5;
track_B.m_sync_markers[0] = 0.9; track_B.m_sync_markers[0] = 0.7;
track_B.m_sync_markers[1] = 0.2; 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])
);
}
} }
} }