Skip to content

Commit 1fbdc52

Browse files
committed
Keyframe::GetRepeatFraction(): Binary search, skipping when constant
The old implementation did a linear scan over the values. This was slow with slowly changing keyframes. This new implementation skips over constant (when rounded) segments and performs binary search in (possibly long) interpolated segments to find the X coordinates where a change occurs quickly.
1 parent f00edba commit 1fbdc52

File tree

1 file changed

+109
-26
lines changed

1 file changed

+109
-26
lines changed

src/KeyFrame.cpp

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -379,40 +379,123 @@ void Keyframe::SetJsonValue(Json::Value root) {
379379
// Get the fraction that represents how many times this value is repeated in the curve
380380
// This is depreciated and will be removed soon.
381381
Fraction Keyframe::GetRepeatFraction(int64_t index) const {
382-
// Is index a valid point?
383-
if (index >= 1 && (index + 1) < GetLength()) {
384-
int64_t current_value = GetLong(index);
385-
int64_t previous_repeats = 0;
386-
int64_t next_repeats = 0;
387-
388-
// Loop backwards and look for the next unique value
389-
for (int64_t i = index; i > 0; --i) {
390-
if (GetLong(i) == current_value) {
391-
// Found same value
392-
previous_repeats++;
382+
// Frame numbers (index) outside of the "defined" range of this
383+
// keyframe result in a 1/1 default value.
384+
if (index < 1 || (index + 1) >= GetLength()) {
385+
return Fraction(1,1);
386+
}
387+
assert(Points.size() > 1); // Due to ! ((index + 1) >= GetLength) there are at least two points!
388+
389+
// First, get the value at the given frame and the closest point
390+
// to the right.
391+
int64_t const current_value = GetLong(index);
392+
std::vector<Point>::const_iterator const candidate =
393+
std::lower_bound(begin(Points), end(Points), static_cast<double>(index), IsPointBeforeX);
394+
assert(candidate != end(Points)); // Due to the (index + 1) >= GetLength check above!
395+
396+
// Calculate how many of the next values are going to be the same:
397+
int64_t next_repeats = 0;
398+
std::vector<Point>::const_iterator i = candidate;
399+
// If the index (frame number) is the X coordinate of the closest
400+
// point, then look at the segment to the right; the "current"
401+
// segement is not interesting because we're already at the last
402+
// value of it.
403+
if (i->co.X == index) {
404+
++i;
405+
}
406+
// Skip over "constant" (when rounded) segments.
407+
bool all_constant = true;
408+
for (; i != end(Points); ++i) {
409+
if (current_value != round(i->co.Y)) {
410+
all_constant = false;
411+
break;
412+
}
413+
}
414+
if (! all_constant) {
415+
// Found a point which defines a segment which will give a
416+
// different value than the current value. This means we
417+
// moved at least one segment to the right, thus we cannot be
418+
// at the first point.
419+
assert(i != begin(Points));
420+
Point const left = *(i - 1);
421+
Point const right = *i;
422+
// Binary search for the first value which is different from
423+
// the current value.
424+
bool const increasing = current_value < round(i->co.Y);
425+
int64_t start = left.co.X;
426+
int64_t stop = right.co.X;
427+
while (start < stop) {
428+
int64_t const mid = (start + stop + 1) / 2;
429+
double const value = InterpolateBetween(left, right, mid, 0.01);
430+
bool const smaller = round(value) <= current_value;
431+
bool const larger = round(value) >= current_value;
432+
if ((increasing && smaller) || (!increasing && larger)) {
433+
start = mid;
393434
} else {
394-
// Found non repeating value, no more repeats found
395-
break;
435+
stop = mid - 1;
396436
}
397437
}
438+
next_repeats = start - index;
439+
} else {
440+
// All values to the right are the same!
441+
next_repeats = Points.back().co.X - index;
442+
}
398443

399-
// Loop forwards and look for the next unique value
400-
for (int64_t i = index + 1; i < GetLength(); ++i) {
401-
if (GetLong(i) == current_value) {
402-
// Found same value
403-
next_repeats++;
444+
// Now look to the left, to the previous values.
445+
all_constant = true;
446+
i = candidate;
447+
if (i != begin(Points)) {
448+
// The binary search below assumes i to be the left point;
449+
// candidate is the right point of the current segment
450+
// though. So change this if possible. If this branch is NOT
451+
// taken, then we're at/before the first point and all is
452+
// constant!
453+
--i;
454+
}
455+
int64_t previous_repeats = 0;
456+
// Skip over constant (when rounded) segments!
457+
for (; i != begin(Points); --i) {
458+
if (current_value != round(i->co.Y)) {
459+
all_constant = false;
460+
break;
461+
}
462+
}
463+
// Special case when skipped until the first point, but the first
464+
// point is actually different. Will not happen if index is
465+
// before the first point!
466+
if (current_value != round(i->co.Y)) {
467+
assert(i != candidate);
468+
all_constant = false;
469+
}
470+
if (! all_constant) {
471+
// There are at least two points, and we're not at the end,
472+
// thus the following is safe!
473+
Point const left = *i;
474+
Point const right = *(i + 1);
475+
// Binary search for the last value (seen from the left to
476+
// right) to be different than the current value.
477+
bool const increasing = current_value > round(left.co.Y);
478+
int64_t start = left.co.X;
479+
int64_t stop = right.co.X;
480+
while (start < stop) {
481+
int64_t const mid = (start + stop + 1) / 2;
482+
double const value = InterpolateBetween(left, right, mid, 0.01);
483+
bool const smaller = round(value) < current_value;
484+
bool const larger = round(value) > current_value;
485+
if ((increasing && smaller) || (!increasing && larger)) {
486+
start = mid;
404487
} else {
405-
// Found non repeating value, no more repeats found
406-
break;
488+
stop = mid - 1;
407489
}
408490
}
409-
410-
int64_t total_repeats = previous_repeats + next_repeats;
411-
return Fraction(previous_repeats, total_repeats);
491+
previous_repeats = index - start;
492+
} else {
493+
// Every previous value is the same (rounded) as the current
494+
// value.
495+
previous_repeats = index;
412496
}
413-
else
414-
// return a blank coordinate
415-
return Fraction(1,1);
497+
int64_t total_repeats = previous_repeats + next_repeats;
498+
return Fraction(previous_repeats, total_repeats);
416499
}
417500

418501
// Get the change in Y value (from the previous Y value)

0 commit comments

Comments
 (0)